I were writing a qsort compare function callback for this scenario:
int matrix[3][4] =
{
{1,2,3,4},
{5,6,7,8},
{9,1,2,3},
};
qsort(matrix, 3, sizeof(int[4]), compare);
Since the item type is int[4] then qsort should pass me int(*)[4] pointers converted to const void*.
So I wrote the function like this:
int compare (const void* obj1, const void* obj2)
{
const int (*ptr1)[4] = obj1;
const int (*ptr2)[4] = obj2;
/* ... */
return 0;
}
gcc 12.2 x86 -Wall -Wextra -std=c17 -pedantic-errors complains about the const int (*ptr1)[4] = obj1; lines:
error: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
Why am I getting this warning? There are no const qualifiers being discarded as far as I can tell. The type being a pointer to an array of 4 const int. Apparently this warning goes way back to older gcc versions too.
Whereas clang and icx with the same options compiles the same code cleanly and the end result works fine on all 3 compilers.
Because by passing -pedantic-errors you are requesting pedantic diagnostics, and, pedantically, const int[4] is not a const-qualified type (it's an array of const elements, and the array itself cannot be qualified). See GCC bug 62198.
gcc-5.1 and newer do not issue this warning in non-pedantic mode.
Related
I was wondering if const qualifying function pointers makes any difference, since the only meaning i could think of is auto const-qualifying its parameters, which is of course not the case.
I created a little example file (test.c):
typedef void* vop(void*);
vop fn;
const vop cfn;
int main(void){
vop *p_fn = fn;
const vop *cp_fn = fn; // <- gives compiler warning
vop *p_cfn = cfn;
const vop *cp_cfn = cfn;
}
and ran
gcc -Wall -Wno-unused-variable -c test.c
which yields the following warning:
warning: initialization makes '__attribute__((const))' qualified function pointer from unqualified [-Wdiscarded-qualifiers]
So it is "ok" to assign a "pointer to const vop" to a variable of type "pointer to vop" which, if it was not a function pointer would yield something like:
warning: initialization discards 'const' qualifier from pointer target type [-Wdiscarded-qualifiers]
But now it warns for the opposite case. So the question arises: What is the difference between const qualified function pointers and those that are not const qualified?
Note: The cppreference has the following paragraph:
If a function type is declared with the const type qualifier (through the use of typedef), the behavior is undefined.
Is the warning i saw a result of that "undefined behaviour" or does this paragraph not apply in this case (and if not, in what case can it be applied)?
A function type cannot have any type qualifier on it, including const. Doing so it undefined behavior.
From section 6.7.3p9 of the C standard:
If the specification of an array type includes any type qualifiers, the element type is so-qualified, not the array type. If the specification of a function type includes any type qualifiers, the behavior is undefined.
This declares a const function type:
const vop cfn;
And this declares a pointer to a const function type:
const vop *cp_fn;
Both of which violate 6.7.3p9.
What is the difference between const qualified function pointers and those that are not const qualified?
const has the usual meaning - one can be modified, the const one may not. Example:
void something();
void something_else();
int main() {
void (*normal_pointer)() = something;
normal_pointer = something_else; // all fine
void (*const const_qualified_pointer)() = something;
const_qualified_pointer = something_else; // error
// for fun, let's aad typedef examples
// similar with a typedef, if you want to
typedef void functype();
functype *pnt = something;
pnt = something_else; // all fine
functype *const cpnt = something;
cpnt = something_else; // error
// note that if typedef is already a pointer... then it's already a pointer
typedef void (*functypepnt)();
functypepnt pnt2 = something;
pnt2 = something_else; // all fine
const functypepnt cpnt2 = something;
cpnt2 = something_else; // error
}
Is the warning i saw a result of that "undefined behaviour" or does this paragraph not apply in this case (and if not, in what case can it be applied)?
Yes. vop is a function type. const vop is undefined behavior. gcc issues a warning and ignores the qualifier.
You may want to const-qualify the pointer itself instead, not the pointed-to-type:
vop *const cp_cfn = fn;
If I'm writing a generic algorithm, am I allowed to alias an array of unknown type as a pointer to an array where each element is the size provided without invoking undefined behavior?
For example, is there UB in the following code?
typedef void (*action_t)(const void *item);
void do(void *array, size_t eltCount, size_t eltSize, action_t action)
{
// Convenient typedef.
typedef char element[eltSize];
element *elts = array;
element *end = elts + eltCount;
for (; elts != end; elts++) {
action(elts);
}
}
I know I can do this:
char *elts = array;
char *end = elts + eltCount * eltSize;
for (; elts != end; elts += eltSize) {
action(elts);
}
But the first bit of code seems more idiomatic to me since the compiler does the pointer arithmetic for me. The function above compiles without warnings using both gcc and clang (relevant compilation flags are -std=c99 -O3 -fstrict-aliasing -pedantic-errors -Wextra -Wall). I'm also wondering about strict aliasing, but as far as I can tell, it seems like I'm not breaking it here since objects are allowed to be used indirectly via char*.
In C, a typedef doesn't introduce a new type. It just names a construct. The name and its definition are interchangeable. (The same is not true for, say, struct, where there is no way to express the structure's definition other than by its name.)
So, as long as you're only talking about some form of char * -- which as you know, is special because any data pointer can be converted to it -- then you're relying on defined behavior.
When I compile the program below with GCC 4.9.2 I get the following warning: passing argument 1 of ‘P’ from incompatible pointer type. However, I don't see anything wrong with the program. Any clues?
typedef int Row[10];
void P(const Row A[])
{
}
int main(void)
{
Row A[10];
P(A);
return 0;
}
Here is the complete output from GCC to stderr:
test.c: In function ‘main’:
test.c:12:4: warning: passing argument 1 of ‘P’ from incompatible pointer type
P(A);
^
test.c:3:6: note: expected ‘const int (*)[10]’ but argument is of type ‘int (*)[10]’
void P(const Row A[])
^
Edit: The program compiles cleanly with Clang 3.5.0 and the options -pedantic -std=c89 -Wall.
Get rid of the typedef and it should become a little bit clearer:
void P (const int A [][10])
{
}
int main(void)
{
int A[10][10];
P(A);
return 0;
}
The problem is that the array in the function parameter "decays" into a pointer of type const int(*) [10], which is a pointer to an array where the items are const.
This pointer type is not compatible with what you pass from main, because that array decays into an array pointer of type int(*)[10].
There is a rule of "pointer-to-type may be converted to qualified-pointer-to-type". Meaning for example int* may be converted to const int* but not the other way around. But that rule does not apply here.
Because the qualified version of "pointer-to-array" is "const-pointer-to-array", and not "pointer-to-const-array", which is what you have here.
Unfortunately this is a weakness in the C language: you cannot have const correctness while using array pointers. The only solution is a very ugly one:
P( (const int(*)[10]) A);
It might be better to skip const correctness completely for cases like this, in favour of readability.
Edit: In C11 you can do like this, which is more type safe but it still relies on the caller performing a cast:
#define const_array_cast(arr, n) _Generic(arr, int(*)[n] : (const int(*)[n])arr )
void P (const int A [][10])
{
}
int main(void)
{
int A[10][10];
P(const_array_cast(A,10));
return 0;
}
I have the following versions of passing 2D array as pointer.
Version 1
#include <stdio.h>
void disp(int a[][5])
{
printf("a[0][3] = %d\n", a[0][3]); /* a[0][3] = 4 */
}
int main ()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
disp(a);
return 0;
}
Version 2
#include <stdio.h>
typedef void(*callDisplay)(int*);
void disp(int a[][5])
{
printf("a[0][3] = %d\n", a[0][3]); /* a[0][3] = 4 */
}
int main ()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
callDisplay fn = (callDisplay) &disp;
fn(a);
return 0;
}
Version 1 rises warning incompatible pointer type. expected int (*)[5] but argument is of type int * as expected. However, (Version 2) calling the same function with pointer is compiling without any such warnings.
gcc options: gcc -O0 -g3 -Wall -c -fmessage-length=0
Could somebody pass light on this?
If you remove the cast when assigning the function pointer you get:
tmp.c: In function ‘main’:
tmp.c:13:22: warning: initialization from incompatible pointer type [enabled by default]
callDisplay fn = &disp;
The cast is suppressing this warning even though by casting to a function pointer of a different type you have invoked undefined behavior when you call the function pointer. Basically, you should never need to cast a function pointer as it will hide any warnings like this.
If you fix the function pointer you get the following code:
typedef void(*callDisplay)(int[][5]);
void disp(int a[][5])
{
printf("a[0][3] = %d\n", a[0][3]); /* a[0][3] = 4 */
}
int main ()
{
int a[10] = {1,2,3,4,5,6,7,8,9,10};
callDisplay fn = &disp;
fn(a);
return 0;
}
Which when you compile you get the same warning as your first example:
tmp.c: In function ‘main’:
tmp.c:14:5: warning: passing argument 1 of ‘fn’ from incompatible pointer type [enabled by default]
fn(a);
^
tmp.c:14:5: note: expected ‘int (*)[5]’ but argument is of type ‘int *’
This function declaration
typedef void(*callDisplay)(int*);
has compatible argument when is called like
fn(a);
The problem is related to this casting
callDisplay fn = (callDisplay) &disp;
it is wrong.
That is the program has undefined behaviour.
According to the C Standard (6.3.2.3 Pointers)
8 A pointer to a function of one type may be converted to a pointer to
a function of another type and back again; the result shall compare
equal to the original pointer. If a converted pointer is used to
call a function whose type is not compatible with the referenced type,
the behavior is undefined.
I have a variable k of type const char *, and a function in glib with the prototype
void g_hash_table_replace(GHashTable *hash_table,
gpointer key,
gpointer value);
gpointer is defined simply as
typedef void* gpointer;
I know that in this case it is, in fact, okay to pass in k as the key in g_hash_table_replace, however gcc gives me the error
service.c:49:3: warning: passing argument 2 of ‘g_hash_table_replace’ discards ‘const’ qualifier from pointer target type [enabled by default]
/usr/include/glib-2.0/glib/ghash.h:70:13: note: expected ‘gpointer’ but argument is of type ‘const char *’
this is with gcc 4.6.0. With 4.5.0 and earlier, a simple cast to (char *) sufficed to supress this warning, but gcc seems to have gotten 'smarter'. I've tried (char *)(void *)k, but it still knows that the variable was originally const. What is the best way to silence this warning without calling strdup(3) on k?
I just tried this with gcc 4.6.1.
#include <glib/ghash.h>
#include <stdio.h>
#include <unistd.h>
const char *k="Testing";
int main(int argc, char **argv)
{
int val = 1024;
GHashTable *hash_table=NULL;
g_hash_table_replace(hash_table,(gpointer) (intptr_t)k, &val);
return 0;
}
Without casts, the error is as you describe above. But if I cast the const char* to intptr_t first as shown above, the warning is suppressed. Can you confirm that you still experience the error with my code sample?