5

EDIT: problem explained more in depth here (thank you @Eric Postpischil). It seems to be a bug in GCC.

First, let me start with some context: the code I'm writing is using an API I can't change, on a GCC version I can't change, with compilation flags I'm not allowed to remove, and when I'm done with it must have precisely zero warnings or #pragmas.

EDIT: no unions either.

EDIT2: assume the build system also uses -Wall -ansi -pedantic and every other warnings under the sun. I'll confirm the GCC version tomorrow but I'm fairly certain it's not above GCC 7. In the meantime I'm testing with GCC 6.3.

EDIT3: I'm marking the issue as 'answered'. For completeness' sake, I'm adding some more information below:

I've checked the compiler version being used, and it's not pretty. We're using Mingw and a gcc.exe --version tells me it's GCC 3.4.5.

Furthermore, compilation flags include wall wextra wcast-qual wpointer-arith wconversion wsign-conversion along with others that are not relevant to the problem at hand.

The problem

Consider the following code:

#include "stdio.h"
#include "stdint.h"

typedef uint32_t MyType[4];

const MyType* foo(const uint8_t* a)
{
    return (const MyType*) a;
}

void myapi_foo(const MyType* d) {}

int main()
{
    uint8_t a[4*sizeof(uint32_t)];

    const MyType* b = foo((const uint8_t*) a);

    myapi_foo(b);

    return 0;
}

Compiled with GCC and the -Wcast-qual flag, this code will throw the following warning:

warning: cast discards ‘const’ qualifier from pointer target type [-Wcast-qual] return (const MyType*) a;

EDIT: to clarify, the error is on this line:

return (const MyType*) a;

The cause of the problem

I know the root cause of the problem is the typedef type MyType which is in fact an array. Sadly, I do not have the luxury of modifying this typedef, nor the API function myapi_foo and its dubious choice of parameter type. To be honest, I don't really understand why is the compiler so unhappy about this cast, so clarifications are more than welcome.

The question

What would be the cleanest way of indicating to the compiler everything should be treated as a pointer to const data?

Discarded and potential solutions

Here are a few 'solutions' that I have found but left me unsatisfied:

  • Remove the -Wcast-qual flag. I cannot do that due to code quality rules.
  • Add a #pragma to turn off the warning around that part of the code (as shown here). Similarly I'm not allowed to do that.
  • Cast the pointer to an integer, then cast back to a pointer (as shown here) return (const MyType*) (uint32_t) a;. It's very crude, but using uint32_t as memory addresses has precedent in this project so I might have to use it as a last ditch effort.
  • EDIT: @bruno suggested using an union to side-step the problem. This is a portable and fairly elegant solution. However, the aforementioned code quality rules downright bans the use of unions.
  • EDIT: @Eric Postpischil and @M.M suggested using a (const void*) cast return (const void*) a;, which would work regardless of the value of sizeof(MyType*). Sadly it doesn't work on the target.

Thank you for your time.

A. Cadot
  • 63
  • 7
  • You should specify which GCC version you are using and which line the error is reported on. – Eric Postpischil Feb 03 '19 at 12:31
  • As specified in the warning message, the error is on *return (const MyType*) a;* I don't have the specific GCC version at hand but I can reproduce the issue with GCC 6.3.0. I'll update the OP tomorrow with the information I can share. – A. Cadot Feb 03 '19 at 12:38
  • 1
    Is `return (const void *) a;` acceptable to your coding standard? – M.M Feb 03 '19 at 12:45
  • @M.M yes, it would be accepted. However, as I pointed out below Eric Postpischil's post, it doesn't appear to work on GCC 6.3 so it may not work on the version I have to use. – A. Cadot Feb 03 '19 at 13:03
  • I feel really uncomfortable with you code, do we agree that `MyType *` is a pointer to an array, thus a `uint32_t *[4]` in your case? thus the first cast sees wrong. Btw to me the best solution in those cases is just to ignore the typedef and return `const uint32_t*`; – OznOg Feb 03 '19 at 13:03
  • Yes, this typedef to array and this pointer to the typedef is complete garbage... but as I mentioned I can't modify this part of the code because it's part of an API. Furthermore, I can't ignore the typedef because it's in the type of the argument of `myapi_foo` – A. Cadot Feb 03 '19 at 13:09
  • 1
    [I posted a question about the odd GCC behavior.](https://stackoverflow.com/questions/54507856/is-gcc-warning-on-const-qualifier-correct) – Eric Postpischil Feb 03 '19 at 21:39
  • 1
    I added another workaround that does work in GCC 6.3. – Eric Postpischil Feb 04 '19 at 13:59

3 Answers3

2

This is GCC bug 81631. GCC fails to recognize the cast to const MyType * retains the const qualifier. This may be because, in this “pointer to array of four const uint32_t”, GCC performs a test of whether the array is const whether than of whether the array elements are const.

In some GCC versions, including 8.2, a workaround is to change:

return (const MyType*) a;

to:

return (const void *) a;

A more drastic change that is likely to work in more versions is to use:

return (const MyType *) (uintptr_t) a;

Note About Conversion and Aliasing:

It may be a problem that this code passes a to a function that casts it to const MyType *:

uint8_t a[4*sizeof(uint32_t)];

const MyType* b = foo((const uint8_t*) a);

In many C implementations, MyType, being an array of uint32_t, will require four-byte alignment, but a will only require one-byte alignment. Per C 2018 6.3.2.3 6, if a is not correctly aligned for MyType, the result of the conversion is not defined.

Additionally, this code suggests that the uint_t array a may be used as an array of four uint32_t. That would violate C aliasing rules. The code you show in the question appear to be a sample, not the actual code, so we cannot be sure, but you should consider this.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
  • This code is indeed a very simplified reproduction of the problem. Essentially, the API calls upon hardware using 32-bits words, but the application uses a more generic uint8_t* approach. The concerns about aliasing are pertinent. In this case, both the architecture and defensive code I haven't included in the sample guarantee `a` may not be out of alignment (also on this specific hardware out of alignment accesses have a defined behavior so we're triple safe). – A. Cadot Feb 03 '19 at 12:55
  • Sadly, this solution doesn't appear to work on GCC 6.3: `main.c:30:12: warning: return discards ‘const’ qualifier from pointer target type [-Wdiscarded-qualifiers] return (const void*) a;` I will see tomorrow if this workaround works on the target compiler. – A. Cadot Feb 03 '19 at 12:57
  • 1
    @A.Cadot: [Godbolt shows it does work in GCC 6.3.](https://godbolt.org/z/4qd_yZ) Can you reproduce the failure in Godbolt? – Eric Postpischil Feb 03 '19 at 14:33
  • The -pedantic flag made it throw a warning, even on [Godbolt](https://godbolt.org/z/CLqnfF). This being said I'm not certain it's part of the compilation environment, I'll know tomorrow when I get back on the real target. – A. Cadot Feb 03 '19 at 14:45
1

You can do that :

const MyType* foo(const uint8_t* a)
{
    union {
      const uint8_t* a;
      const MyType* b;
    } v;

    v.a = a;
    return v.b;
}

w.c being your modified file :

pi@raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi@raspberrypi:/tmp $ 

That works whatever the compiler (no #pragma) or the respective size of int and pointer(no cast between int and pointer), but I am not sure this is very elegant ;-)

It is strange to have that foo function and at the same time compile with Wcast-qual, it's contradictory


Edit, If you cannot use union you can also do that

const MyType* foo(const uint8_t* a)
{
    const MyType* r;

    memcpy(&r, &a, sizeof(a));
    return r;
}

Compilation :

pi@raspberrypi:/tmp $ gcc -pedantic -Wall -Wcast-qual w.c
pi@raspberrypi:/tmp $ 
bruno
  • 32,421
  • 7
  • 25
  • 37
  • Thank you for your answer. The *foo* function is for illustration. When the problem first came up the cast was right in the call to *myapi_foo* I'm taking note of the solution using unions, however I'm fairly certain they are also banned by our coding rules. – A. Cadot Feb 03 '19 at 11:52
  • @A.Cadot ok, you can use my _foo_ definition for a _cast_ without warning. Banned ? I do not make any assumption except of course `const uint8_t*` is in fact a `const MyType*` – bruno Feb 03 '19 at 11:54
  • does this still violates the strict aliasing rule? – iBug Feb 03 '19 at 11:57
  • The coding standard I have to use are essentially a security-focused version of MISRA. If the client says unions shall not be used, then I'd rather write the cast in ASM than put unions in my code. It's that kind of client... – A. Cadot Feb 03 '19 at 11:59
  • @A.Cadot I edit my answer, is _memcpy_ allowed ? ;-) – bruno Feb 03 '19 at 12:06
  • Thank you. Yes, memcpy is allowed (well, at least a custom implementation of it). We'll see what get past the review process... – A. Cadot Feb 03 '19 at 12:20
  • 1
    It should be noted the C standard generally permits pointers of different types to have different representations, so reinterpreting the bytes of a pointer through either a union or `memcpy` as a different type of pointer is not guaranteed by the standard to work. It will in many implementations, of course, but, since this question asks about a strict environment in which more detailed rules than normal must be followed, the additional requirement should be noted. – Eric Postpischil Feb 03 '19 at 12:50
1

If nothing works, you might like to use the uintptr_t hammer, if the implmentation provides it. It is optional by the C11 Standard:

const MyType* foo(const uint8_t* a)
{
    uintptr_t uip = (uintptr_t) a; 

    return (const MyType*) uip;
}
alk
  • 69,737
  • 10
  • 105
  • 255
  • Yes, this is very close to one of the solutions I mentioned in the OP (casting to `uint32_t`, which is what I would have to use as `uintptr_t` is not defined). The copy is actually not required, you can use a double cast: `return (const MyType*) (uint32_t) a;` – A. Cadot Feb 03 '19 at 16:14
  • Well `unit32_t` might case a warning as well, if not on 32 bits. – alk Feb 03 '19 at 16:16
  • @A.Cadot: "*as uintptr_t is not defined)*" Interesting. Which platform are you on, please? Also this fact would make my think, *why* it is missing ... - should there be a problem with converting a pointer to an integer on this platform? – alk Feb 03 '19 at 16:17
  • It's embedded development, the standard headers are simply unavailable. Common standard types like `uint32_t` are re-defined in custom headers and I'm fairly certain `uintptr_t` is not one of them. I could re-define it to be `uint32_t` (since it's a 32 bits architecture) but this project already have a lot of memory addresses hardcoded or stored as `uint32_t` (manual MMU configuration, etc.) so there's no point adding a typedef just to remove the warning. So the 'why' is sadly a boring 'because that's how the project does it'. – A. Cadot Feb 03 '19 at 16:31
  • I should also mention that anything standardized after 1990 is heresy as far as out client is concerned. – A. Cadot Feb 03 '19 at 16:43