Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

GCC: Casting const pointers to const pointer of array typedef with -Wcast-qual throws warning

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.

like image 741
A. Cadot Avatar asked Feb 03 '19 11:02

A. Cadot


3 Answers

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.

like image 81
Eric Postpischil Avatar answered Oct 22 '22 20:10

Eric Postpischil


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 $ 
like image 24
bruno Avatar answered Oct 22 '22 20:10

bruno


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;
}
like image 1
alk Avatar answered Oct 22 '22 20:10

alk