I have the following program that compiles fine in g but not in gcc.
g() takes a pointer to an array of char*, and should not modify anything that p points to.
Anyway to make this work properly in C?
% cat t.c
void g(const char* const* p) {}
int main()
{
char** p = 0;
g(p);
}
% g t.c
% gcc t.c
t.c: In function ‘main’:
t.c:5:5: warning: passing argument 1 of ‘g’ from incompatible pointer type [-Wincompatible-pointer-types]
g(p);
^
t.c:1:6: note: expected ‘const char * const*’ but argument is of type ‘char **’
void g(const char* const* p) {}
^
%
CodePudding user response:
The error message tells you every thing you need to know. Make p as the same signature as the argument of g.
const char* const* p = 0;
But if you need whatever p is pointing to to be a readonly then this type specification is enough for the assignment of p and g argument (although it will be possible to change whatever the array is pointing to):
char* const* p = 0;
CodePudding user response:
Situation in C
In C qualifiers are only allowed to be implicitly added for the top-most pointer level:
6.3.2.3 Pointers
2 For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.
So you can only add const to the top-most pointer implicitly, e.g.:
char** p = 0;
char* const* other = p; // ok
const char** other = p; // not ok: adding const not at top-level
The reasoning for this is that it would allow you to get a non-const pointer to a const value if you jump through a few hoops:
const char c = 'C';
char* ptr;
const char **ptrPtr = &ptr; // this is not legal
*ptrPtr = &c;
At first glance that piece of code looks reasonable - you have ptrPtr
that points to ptr
that points to c
.
The problem is that you can modify c
through ptr
now (which is not const-qualified), e.g.:
*ptr = 'X'; // will change c
Here's an FAQ entry about this if you want to read a bit more about it.
You can still force it though if you want with an explicit cast:
void g(const char* const* p) {}
int main()
{
char** p = 0;
g((const char* const*)p);
}
Situation in C
C is a bit more lenient in how much constness can implicitly be added.
7.3.6 Qualification conversions
(1)A qualification-decomposition of a type T is a sequence of cvi and Pi such that T is
“cv0 P0 cv1 P1 ⋯ cvn-1 Pn−1 cvn U” for n ≥ 0,
where each cvi is a set of cv-qualifiers ([basic.type.qualifier]), and each Pi is “pointer to” ([dcl.ptr]), “pointer to member of class Ci of type” ([dcl.mptr]), “array of Ni”, or “array of unknown bound of” ([dcl.array]). If Pi designates an array, the cv-qualifiers cvi 1 on the element type are also taken as the cv-qualifiers cvi of the array.(2) Two types T1 and T2 are similar if they have qualification-decompositions with the same n such that corresponding Pi components are either the same or one is “array of Ni” and the other is “array of unknown bound of”, and the types denoted by U are the same.
(3) The qualification-combined type of two types T1 and T2 is the type T3 similar to T1 whose qualification-decomposition is such that:
- (3.1) for every i > 0, cv3i is the union of cv1i and cv2i,
- (3.2) if either P1i or P2i is “array of unknown bound of”, P3i is “array of unknown bound of”, otherwise it is P1i, and
- (3.3) if the resulting cv3i is different from cv1i or cv2i, or the resulting P3i is different from P1i or P2i, then const is added to every cv3k for 0 < k < i,
where cvji and Pji are the components of the qualification-decomposition of Tj.
Which essentially just boils down just to:
- you can always add const to the top level
- if you add const to a lower level, all levels up from that level must also be const
this "const-propagation" is necessary to avoid exactly that edge-case that is present in C: getting a non-const pointer to a const object.
a few examples: godbolt
int i = 1;
int* ip = &i;
int** ipp = &ip;
int*** ippp = &ipp;
const int* t1 = ip; // ok
const int* const* t2 = ipp; // ok
const int* const* const* t3 = ippp; // ok
// const int** t4 = ipp; // not ok
// const int*** t5 = ippp; // not ok
// const int* const** t6 = ippp; // not ok
int* const* t7 = ipp; // ok
int* const* const* t8 = ippp; // ok
// int* const** t9 = ippp; // not ok
}
Conclusion
So that is the reason why your code compiles with g but not with gcc:
C allows you to add const-qualifiers for multiple pointer levels implicitly (as long as you follow da rules outlined above), while C only allows you to add const to the top-level pointer implicitly.