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.

8
  • You should specify which GCC version you are using and which line the error is reported on. Commented Feb 3, 2019 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. Commented Feb 3, 2019 at 12:38
  • 1
    Is return (const void *) a; acceptable to your coding standard? Commented Feb 3, 2019 at 12:45
  • 1
    I posted a question about the odd GCC behavior. Commented Feb 3, 2019 at 21:39
  • 1
    I added another workaround that does work in GCC 6.3. Commented Feb 4, 2019 at 13:59

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

4 Comments

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).
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: Godbolt shows it does work in GCC 6.3. Can you reproduce the failure in Godbolt?
The -pedantic flag made it throw a warning, even on Godbolt. 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.
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 $ 

7 Comments

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 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*
does this still violates the strict aliasing rule?
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...
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.
|
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;
}

5 Comments

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;
Well unit32_t might case a warning as well, if not on 32 bits.
@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?
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'.
I should also mention that anything standardized after 1990 is heresy as far as out client is concerned.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.