Is GCC warning on const qualifier correct? - c

Consider the follow code, which arose from this question:
const int (*foo(const char *a))[1]
{ return (const int (*)[1]) a; }
When compiled with GCC 8.2 (and older versions) using -Wcast-qual, GCC warns:
source>:2:15: warning: cast discards 'const' qualifier from pointer target type [-Wcast-qual]
{ return (const int (*)[1]) a; }
^
Is this warning correct? Clearly the destination type has a const qualifier in it.
It is on the element type rather than on the thing immediately pointed to by the pointer, which is the array type. However, the warning remains even if we use typedef int T[1]; and replace the cast with (const T *). Additionally, per C 2018 6.7.3 10, qualifiers on an array type apply to the element type, not the array type, so the type is the same either way.
Clang does not show this warning.
If we change the cast to (const void *):
const int (*foo(const char *a))[1]
{ return (const void *) a; }
then the warning vanishes. If we add -pedantic to the compilation switches, we get a different warning about const:
source>:2:15: warning: return discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
{ return (const void *) a; }
^~~~~~~~~~~~~~~~
This looks like the same warning except it is about the implied conversion from the return expression to the function return type, whereas the previous warning was about the explicit conversion in the cast. But this one appears only with -pedantic. Why?

This is GCC bug 81631. GCC fails to recognize the cast to a pointer to an array retains the const qualifier, due to complications with qualifiers applied to an array actually applying to the array elements.

Related

why "passing argument of incompatible pointer type" warning [duplicate]

Consider following program:
int main()
{
int array[9];
const int (*p2)[9] = &array;
}
It compiles fine in C++ (See live demo here) but fails in compilation in C. By default GCC gives following warnings. (See live demo here).
prog.c: In function 'main':
prog.c:4:26: warning: initialization from incompatible pointer type [enabled by default]
const int (*p2)[9] = &array;
But If I use -pedantic-errors option:
gcc -Os -s -Wall -std=c11 -pedantic-errors -o constptr constptr.c
it gives me following compiler error
constptr.c:4:26: error: pointers to arrays with different qualifiers are incompatible in ISO C [-Wpedantic]
Why it fails in compilation in C but not in C++? What C & C++ standard says about this?
If I use const qualifier in array declaration statement it compiles fine in C also. So, what is happening here in above program?
GCC-gnu
In GNU C, pointers to arrays with qualifiers work similar to pointers to other qualified types. For example, a value of type int (*)[5] can be used to initialize a variable of type const int (*)[5]. These types are incompatible in ISO C because the const qualifier is formally attached to the element type of the array and not the array itself.
C standard says that (section: §6.7.3/9):
If the specification of an array type includes any type qualifiers, the element type is so- qualified, not the array type.[...]
Now look at the C++ standard (section § 3.9.3/5):
[...] Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. An array type whose elements are cv-qualified is also considered to have the same cv-qualifications as its elements. [ Example:
typedef char CA[5];
typedef const char CC;
CC arr1[5] = { 0 };
const CA arr2 = { 0 };
The type of both arr1 and arr2 is “array of 5 const char,” and the array type is considered to be const- qualified. —endexample]
Therefore, the initialization
const int (*p2)[9] = &array;
is assignment of type pointer to array[9] of int to pointer to array[9] of const int. This is not similar to assigning int * to a const int * where const is applied directly to the object type the pointer points to. This is not the case with const int(*)[9] where, in C, const is applied to the elements of the array object instead of the object the pointer points to. This makes the above initialization incompatible.
This rule is changed in C++. As const is applied to array object itself, the assignment is between same types pointer to const array[9] of int instead of type pointer to array[9] of int and pointer to array[9] of const int.

Using const with pointer to array in formal parameter [duplicate]

Consider following program:
int main()
{
int array[9];
const int (*p2)[9] = &array;
}
It compiles fine in C++ (See live demo here) but fails in compilation in C. By default GCC gives following warnings. (See live demo here).
prog.c: In function 'main':
prog.c:4:26: warning: initialization from incompatible pointer type [enabled by default]
const int (*p2)[9] = &array;
But If I use -pedantic-errors option:
gcc -Os -s -Wall -std=c11 -pedantic-errors -o constptr constptr.c
it gives me following compiler error
constptr.c:4:26: error: pointers to arrays with different qualifiers are incompatible in ISO C [-Wpedantic]
Why it fails in compilation in C but not in C++? What C & C++ standard says about this?
If I use const qualifier in array declaration statement it compiles fine in C also. So, what is happening here in above program?
GCC-gnu
In GNU C, pointers to arrays with qualifiers work similar to pointers to other qualified types. For example, a value of type int (*)[5] can be used to initialize a variable of type const int (*)[5]. These types are incompatible in ISO C because the const qualifier is formally attached to the element type of the array and not the array itself.
C standard says that (section: §6.7.3/9):
If the specification of an array type includes any type qualifiers, the element type is so- qualified, not the array type.[...]
Now look at the C++ standard (section § 3.9.3/5):
[...] Cv-qualifiers applied to an array type attach to the underlying element type, so the notation “cv T,” where T is an array type, refers to an array whose elements are so-qualified. An array type whose elements are cv-qualified is also considered to have the same cv-qualifications as its elements. [ Example:
typedef char CA[5];
typedef const char CC;
CC arr1[5] = { 0 };
const CA arr2 = { 0 };
The type of both arr1 and arr2 is “array of 5 const char,” and the array type is considered to be const- qualified. —endexample]
Therefore, the initialization
const int (*p2)[9] = &array;
is assignment of type pointer to array[9] of int to pointer to array[9] of const int. This is not similar to assigning int * to a const int * where const is applied directly to the object type the pointer points to. This is not the case with const int(*)[9] where, in C, const is applied to the elements of the array object instead of the object the pointer points to. This makes the above initialization incompatible.
This rule is changed in C++. As const is applied to array object itself, the assignment is between same types pointer to const array[9] of int instead of type pointer to array[9] of int and pointer to array[9] of const int.

Cast "pointer to const" to "pointer to const VLA"

In this snippet, a pointer to VLA is used for easier access to a big lookup table :
#pragma GCC diagnostic warning "-Wcast-qual"
char
lookup(int a, int b, int c, char const *raw, int x, int y, int z)
{
typedef char const (*DATA_PTR)[a][b][c];
DATA_PTR data = (DATA_PTR)raw;
return (*data)[x][y][z];
}
GCC 6.2.0 chokes on it while Clang 4.0.0(trunk) compiles just fine, both with -Wcast-qual enabled.
In function 'lookup':
warning: cast discards 'const' qualifier from pointer target type [-Wcast-qual]
DATA_PTR data = (DATA_PTR)raw;
^
The code runs as expected either way.
My guess is GCC confuses a "pointer to VLA of const elements" and "pointer to const VLA" but I'm reaching ...
Is there a way to shut up GCC without fiddling with warnings ?
Is this a GCC bug ?
EDIT1:
Details on the actual code :
struct table {
int a;
int b;
int c;
char *raw;
};
char
lookup2(struct table const *table, int x, int y, int z)
{
typedef char const(*DATA_PTR)[table->a][table->b][table->c];
DATA_PTR data;
data = (DATA_PTR)table->raw; // GCC ok
data = (DATA_PTR)(char const *)table->raw; // GCC raises -Wcast-qual
return (*data)[x][y][z];
}
EDIT2:
So there it is ... the C11 standard draft says in 6.7.3/9 :
If the specification of an array type includes any type qualifiers, the element type is so-qualified, not the array type.
See #hvd answer.
One hack to silence -Wcast-qual :
DATA_PTR data = (DATA_PTR)(intptr_t)raw;
This is a long-standing issue in C. It's the same reason why
int array[2];
const int (*ptr)[2] = &array;
is invalid in C (but would be valid in C++): this declares a pointer to an array of const-qualified integers, which is not a const-qualified array of integers, so the normal rule that a pointer to an type can be implicitly converted to a pointer to the const-qualified version of that type does not apply.
In your case, you're converting from const char * (a pointer to a const-qualified type) to char const (*)[a][b][c] (a pointer to a non-const-qualified type), which -Wcast-qual is supposed to warn about.
clang just never bothered to implement this particular oddity of C, it treats C code with the C++ semantics, which say that an array of const elements is itself const-qualified as well.
You'd normally be able to work around it by wrapping the array in a struct:
typedef struct { char d[a][b][c]; } const *DATA_PTR;
but this is not an option for VLAs. I do not believe there is a suitable workaround other than not using multi-dimensional arrays at all here, or not using -Wcast-qual.

Store function address into global variable

Consider the following code:
void f() {};
void* v = f;
int i = f;
int main() { }
Why storing function address into int variable gives me an error:
error: initializer element is not a compile-time constant
But for void* variable doesn't?
Variables declared at file scope ("global") have static storage duration.
All variables with static storage duration must be initialized to a compile-time constant. In C, other variables (not even const ones) don't count as compile-time constants, hence the error.
In addition, you cannot assign a pointer to an integer, doing so is not valid C. You have to manually convert the pointer to an integer type first, by casting it.
In addition, the cast void* v = f; is not well-defined. The C standard only specifies casts between void* and pointer to object type.
Now what you should do to get a function's address is this:
#include <stdint.h>
uintptr_t i = (uintptr_t) f;
int main (void)
{ ...
uintptr_t is guaranteed to be large enough to contain the address of any pointer.
When I compile this, I get:
$ gcc foo.c
foo.c:3:5: warning: incompatible pointer to integer conversion initializing 'int' with an expression of type 'void ()' [-Wint-conversion]
int i = f;
^ ~
foo.c:3:9: error: initializer element is not a compile-time constant
int i = f;
^
1 warning and 1 error generated.
Really, I think that warning ought to be an error. You're trying to put an address into an integer, and that's generally bad form.
That said, if the compiler goes ahead and makes the conversion, the result is not a compile-time constant (because it's a converted value). Thus the error.
Although you're not asking about C++, I was curious as to how the two languages differ, so I checked how my compiler behaved. In that language, both assignments from f are illegal, for good reason. void* is not a pointer to a parameter-less function, and int isn't either.
$ g++ foo.c
clang: warning: treating 'c' input as 'c++' when in C++ mode, this behavior is deprecated
foo.c:2:7: error: cannot initialize a variable of type 'void *' with an lvalue of type 'void ()'
void* v = f;
^ ~
foo.c:3:5: error: cannot initialize a variable of type 'int' with an lvalue of type 'void ()'
int i = f;
^ ~
2 errors generated.
The initializations of both v and i are illegal.
However, some compilers allow conversion from a function pointer to a void* as a compiler extension.
Try compiling with your warnings cranked up (as you should by habit), and you may get warnings on the void* v = f; line as well.
A correct way to store a pointer to the function f would be
void (*p)() = f;
If you really wanted to put the underlying bits of a pointer into an int you could try this:
typedef union _UPTRINT
{
void *ptr;
int i;
} UPTRINT;
Then assign the function pointer to UPTRINT::ptr and access it as i (assuming that pointers and ints are the same size on your platform; adjust the type of i as necessary).

Intel C++ Compiler warning 167 when non-const argument is passed as const parameter [duplicate]

This question already has answers here:
Why isn't it legal to convert "pointer to pointer to non-const" to a "pointer to pointer to const"
(5 answers)
Closed 10 years ago.
I have a large codebase that recently moved from Microsoft's compiler to the Intel C++ Compiler. Our team's goal is compilation without warnings in the mainline. Since the switch, one instance of warning 167 has confounded me. If I compile the following code:
int foo(const int pp_stuff[2][2])
{
return 0;
}
int foo2(const int pp_stuff[][2])
{
return 0;
}
int main(void)
{
int stuff[2][2] = {{1,2},{3,4}};
foo(stuff);
foo2(stuff);
return 0;
}
The ICC will give me warnings:
1>main.c(17): warning #167: argument of type "int (*)[2]" is incompatible with parameter of type "const int (*)[2]"
1> foo(stuff);
1> ^
1>
1>main.c(18): warning #167: argument of type "int (*)[2]" is incompatible with parameter of type "const int (*)[2]"
1> foo2(stuff);
Why should this be a warning? It is common practice to pass a non-const variable as a const parameter, and the types & dimensions are identical.
To those who have marked this a duplicate question, I urge you to reconsider. If someone else encounters this warning, they would have to know that in C arguments are converted as if by assignment in prototyped functions, and then search for a question that is strictly about assignment. Even though the answer ends up being the same clause from C90/C99, the question is, I think, pretty different.
Cast your variable as const when you pass it to the function that requires const.
foo( (const int (*)[2]) stuff );
Why can't I pass a char ** to a function which expects a const char **?
Similar question
The value of stuff array is of type int (*)[2].
int foo(const int pp_stuff[2][2])
is equivalent to
int foo(const int (*pp_stuff)[2])
In the function call, it is as if you were assigning a value of type int (*)[2] to a variable of type const int (*)[2].
Arguments are converted as if by assignment in prototyped functions. And C let you assign two pointers if:
both operands are pointers to qualified or unqualified versions of compatible types, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;
Here int (*)[2] and const int (*)[2] are not qualified/unqualified versions of the same type. The qualifier applies to int not to the pointer.
Use:
int foo(int (* const pp_stuff)[2])
if you want to make the pointer const and not the int elements.

Resources