cconstantsvolatiletype-declarationrestrict-qualifier

An explanation on rules and interpretations of CVR type qualifications in C


I was going through const, volatile and restrict type qualifier pages on cppreference. I had lots of doubts and confusions about the explanation/examples given there.

  1. This was an example given there:
char *p = 0;
const char *pp = p;  // OK

char **q = 0;
const char **qq = q;  // ERROR

The explanation for this was

for two types to be compatible, their qualifications must be identical.

But if the bottom has incompatible qualifications, then how top one is compatible? Similar statements were there in both C and V pages.

  1. What is the difference among:
typedef int A[5];
const A x = {1, 2, 3, 4, 5};

const int x[5] = {1, 2, 3, 4, 5};

When I ran them, I did not see any difference. But the page says,

If an array type is declared with the const type qualifier (through the use of typedef), the array type is not const-qualified, but its element type is. (until C23)

An array type and its element type are always considered to be identically const-qualified. (since C23)

I did not get what these two statements are meaning! Similar statements were there in all C, V and R pages.

  1. Is this an error?
typedef int A[2][3];
const A x = {{4, 5, 6}, {7, 8, 9}};
void *unqual_ptr = x;

That page says this is OK till C23. But gcc 11.2 is giving me error in std=C89 itself!

  1. What is the meaning of qualifications used in this way?
void f(int m, int n, float a[restrict m][n], float b[restrict m][n]);

A good explanation on how to read/understand and write such complex qualifications would be appreciated.


Solution

  • Question 1

    for two types to be compatible, their qualifications must be identical.

    But if the bottom has incompatible qualifications, then how top one is compatible?

    char * and const char * are not compatible types. char *p = 0; const char *pp = p; is allowed because initialization allows assigning a pointer to a non-qualified type to a pointer to a qualified type that would be compatible without the qualifiers.

    Initialization inherits its rules from the rules for assignment, and the specific wording for this case is in 6.5.16.1 1:

    … the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) 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;…

    The “type the left operand would have after lvalue conversion” is the unqualified version of the type, per 6.3.2.1 2:

    … an lvalue that does not have array type is converted to the value stored in the designated object (and is no longer an lvalue); this is called lvalue conversion. If the lvalue has qualified type, the value has the unqualified version of the type of the lvalue;…

    The type pp points to is const char. After lvalue conversion, this would be char. And p points to char, and char is of course compatible with char. Further, the type pp points to has all the qualifiers of the type p points to, so this initialization is allowed.

    In char **q = 0; const char **qq = q;, qq points to const char *. After lvalue conversion, that would still be const char *, because lvalue conversion would remove the qualifiers of the pointer but not of the type pointed to. So this is not compatible with the type that q points to, char *.

    Question 2

    typedef int A[5];
    const A x = {1, 2, 3, 4, 5};
    
    const int x[5] = {1, 2, 3, 4, 5};
    

    When I ran them, I did not see any difference.

    You do not show any experiment that might detect a difference between these, so it is hard to comment.

    But the page says,

    If an array type is declared with the const type qualifier (through the > use of typedef), the array type is not const-qualified, but its element type > is. (until C23)

    This says that in const A x;, the const is transferred from the array to its elements, so the type of x is “array of 5 const int”.

    An array type and its element type are always considered to be identically const-qualified. (since C23)

    This says a change is expected in a forthcoming version of the C standard, expected in 2023, that will make the type of x be “const array of 5 const int”.

    Question 3

    typedef int A[2][3];
    const A x = {{4, 5, 6}, {7, 8, 9}};
    void *unqual_ptr = x;
    

    That page says this is OK till C23. But gcc 11.2 is giving me error in std=C89 itself!

    The page appears to be wrong. C 2018 6.5.16.1 1 says:

    … the left operand has atomic, qualified, or unqualified pointer type, and (considering the type the left operand would have after lvalue conversion) one operand is a pointer to an object type, and the other is a pointer to a qualified or unqualified version of void, and the type pointed to by the left has all the qualifiers of the type pointed to by the right;…

    That case does not allow void *unqual_ptr = x; since the left operand does not have the const qualifier that the right operand does. And none of the other cases in that paragraph would apply.

    Question 4

    What is the meaning of qualifications used in this way?

    void f(int m, int n, float a[restrict m][n], float b[restrict m][n]);
    

    In declarations of function parameters, qualifiers may appear inside the [ and ] of the top-level array declarator. A function parameter that is declared as an array is automatically adjusted to be a pointer instead. Any qualifiers inside the [ and ] are then applied to the pointer instead of the array or its element type. So float a[restrict m][n] declares a to be a restrict-qualified pointer to an array of n float, as if it had been declared float (* restrict a)[n].