Home > Back-end >  const char* const* p param compiled in g but not gcc
const char* const* p param compiled in g but not gcc

Time:12-28

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.

  • Related