Pointer to Array with Const Qualifier in C & C++

Pointer to array with const qualifier in C & C++

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.

Const correctness for array pointers?

There is no way to do it except for the cast. This is significant drawback of the idea to pass arrays in this way.

Here is a similar thread where the C rules are compared to the C++ rules. We could conclude from this comparison that the C rules are not so well designed, because your use case is valid but C doesn't allow the implicit conversion. Another such example is conversion of T ** to T const * const *; this is safe but is not allowed by C.

Note that since n is not a constant expression, then int n, int (*arr)[n] does not have any added type safety compared to int n, int *arr. You still know the length (n), and it is still silent undefined behaviour to access out of bounds, and silent undefined behaviour to pass an array that is not actually length n.

This technique has more value in the case of passing non-VLA arrays , when the compiler must report if you pass a pointer to an array of the wrong length.

Difference between const pointer and const array type in function parameter declaration

I get exactly the same messages for both functions in both gcc and clang. You have some typo in the code and didn't make a copy/paste when you posted it here.

I always believed there is no difference in function parameter declaration.

This is true for plain 1D arrays. An array parameter such as const int a[] will get adjusted ("decay") into a pointer to it's first element. It is 100% equivalent to const int* a. Note that the const qualifier belongs to the pointed-at type, not the pointer itself.

Now as it happens, incomplete types such as arrays without a size cannot actually be used as function parameters if not for this array adjustment rule1). It's the very reason why you can even type out an incomplete array type [] without size as a function parameter, because the type gets adjusted before the size of the array matters and the result is not an incomplete type but a pointer (to a complete type).

This equivalence doesn't scale well to 2D arrays however. An array parameter such as const int a[x][y] is an array of arrays. The first item is of array type const int[y]. So it gets adjusted to a pointer to such a type:

const (*int)[y]. This is not compatible with const int* nor with const int**.


1) C17 6.7.6.3

After adjustment, the parameters in a parameter type list in a function declarator that is part of a definition of that function shall not have incomplete type.

const qualifier in c++ in array of pointers

are there any rules for this? like "first const protects data", "second const protects the pointer" and so on?

Yes: const applies to the left, unless there's nothing there, then it applies to the right.

Therefore, this:

int const** ptrArray;

but, as a special case, this is equivalent:

const int** ptrArray;

This means the common pattern const int x; is actually int const x; in disguise.

What is an array of constant pointers in C?

No, the const in char *const argv[] is not redundant.

First, const and "constant" are actually two different things in C, even though the const keyword is obviously derived from the word "constant". A constant expression is one that can be evaluated at compile time. const really means "read-only". For example:

const int r = rand();

is perfectly legal.

Yes, the address of an array -- like the address of any object -- is read-only. But that doesn't mean that the value of the array (which consists of the values of its elements) is read-only, any more than any other object is necessarily read-only.

Consider these three declarations:

char *arr1[10];
char *const arr2[10];
const char *arr3[10];

arr1 is a 10-element array of pointers to char. You can modify the char* elements and you can modify the objects that those elements point to.

arr2 is an array of const (read-only) pointers to char. That means that you can't modify the char* elements of the array (once they're initialized) -- but you can still modify the char objects or arrays that those elements point to.

And arr3 is an array of pointers to const char; you can modify the array elements, but you can't modify what they point to.

Now the fact that you used the name argv suggests that you're talking about the second parameter to main, which has some huge effects on this. The language specifies that main's second parameter is

char *argv[]

or, equivalently,

char **argv

There is no const. You can probably get away with adding one, but it's best to follow the form specified by the standard. (Update: I see from your comment that you're asking about the argv parameter of getopt(), which is defined as char * const argv[].)

And since it's a parameter defined as an array, another rule comes into play: a parameter defined as an array of some type is "adjusted" to a pointer to that type. (This rule applies only to parameters.) This isn't a run-time conversion. A function cannot have a parameter of array type.

The relationship between arrays and pointers in C can be confusing -- and there's a lot of misinformation out there. The most important thing to remember is that arrays are not pointers.

Section 6 of the comp.lang.c FAQ is an excellent explanation of the details.

The const qualifier and Const pointers

In fact these three code snippet

    char *p="Hello";                 /*pointer is variable,so string is*/
*p='M';
p="BYE"; /*works*/
p="Bye"; /*works*/

const char *q="Hello"; /*string is constant pointer is not */
*q='M'; /*Error*/
q="BYE"; /*works*/

char const *s="Hello"; /*string is constant pointer is not */
*s='M'; /*Error*/
s="BYE"; /*works*/

are equivalent in the sense that you may not change the object pointed to by the pointers. The difference is that adding qualifier const to definition of q allows the compiler to find the error at compile time.

You may not modify string literals. Any attempt to modify a string literal results in undefined behaviour of the program. In C string literals have type of non-const character arrays. However adding qualifier const as it is done in the second code snippet allows the compiler to find the error at compile time.

The C Standard (6.4.5 String literals)

7 It is unspecified whether these arrays are distinct provided their
elements have the appropriate values. If the program attempts to
modify such an array, the behavior is undefined.

There is no difference except aesthetic between records

const char *

and

char const *

The both define pointers to constant objects. The pointers themselves are not constant.

However if you would write the following way

char s[] ="Hello";                 /*pointer is variable,so string is*/
char *p = s;
const char *q = s;
*p='M';
*q='M';

then there is a difference between using pointers with or without const qualifier because you may change the character array. For the last statement the compiler will issue an error.

The last two code snippets differ from the first three code snippet in that the last two define constant pointers that is the value of pointers may not be changed. And then you are trying to reassign a constant pointer the compiler issues an error. Constant object shall be initialized when they are defined.

Pointer to constant array

The number of the qualifier const in the declaration of a pointer

static const MEMORY_PREFIX wchar_t** Unicode_text = Greek_text;

does not corresponds to the number of the qualifier in the arrays.

You should write

static const MEMORY_PREFIX wchar_t * const * Unicode_text = Greek_text;

GCC: Casting const pointers to const pointer of array typedef with -Wcast-qual throws warning

This is GCC bug 81631. GCC fails to recognize the cast to const MyType * retains the const qualifier. This may be because, in this “pointer to array of four const uint32_t”, GCC performs a test of whether the array is const whether than of whether the array elements are const.

In some GCC versions, including 8.2, a workaround is to change:

return (const MyType*) a;

to:

return (const void *) a;

A more drastic change that is likely to work in more versions is to use:

return (const MyType *) (uintptr_t) a;

Note About Conversion and Aliasing:

It may be a problem that this code passes a to a function that casts it to const MyType *:

uint8_t a[4*sizeof(uint32_t)];

const MyType* b = foo((const uint8_t*) a);

In many C implementations, MyType, being an array of uint32_t, will require four-byte alignment, but a will only require one-byte alignment. Per C 2018 6.3.2.3 6, if a is not correctly aligned for MyType, the result of the conversion is not defined.

Additionally, this code suggests that the uint_t array a may be used as an array of four uint32_t. That would violate C aliasing rules. The code you show in the question appear to be a sample, not the actual code, so we cannot be sure, but you should consider this.

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

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].



Related Topics



Leave a reply



Submit