N 1923: Compatibility of Pointers to Arrays with Qualifiers


Submitter: Martin Uecker
Submission Date: 2015-04-01

Summary

This report is about the long-standing issue of not being able to convert a pointer to an array with certain element type to a pointer to an array with the corresponding constant-qualified element type. Although this seems similar, this is different to converting pointers to pointers to types with and without qualifiers, e.g. that it is unsafe to assign a value of type T** to a variable of const T** as illustrated in 6.5.16.1(6). The later is unsafe because there is an intermediate pointer which can be manipulated to write to a const-qualified object. Such a scenario is not possible for pointers to (one or multi-dimensional) arrays, where there is no such double indirection (and which are more similar to pointers to structs).

In the current C standard, qualifiers are always attached to the element type of an array. It is not possible to declare an array type which is const:

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.
The standard has an explicit rule which allows conversions of pointers to add qualifiers to the target type.
6.3.2.3(3): 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.

This second rule does not apply if the target is an array, because the qualifier is on the element type - not the array itself. This causes practical problems when using pointers to arrays. A practical example is the follow code for matrix transpose:


void transpose(int N, int M, double out[M][N], const double in[N][M])
{
	for (int i = 0; i < N; i++)
		for (int j = 0; j < M; j++)
			out[j][i] = in[i][j];
}

const double a[2][2] = { ... };
double o[2][2];
transpose(2, 2, o, a); // Ok - because a is already constant

double b[2][2];
transpose(2, 2, o, b); // <-- passing incompatible pointer type
In practice, programmer work around this problem either by not using the 'const' qualifier or by not using multi-dimensional arrays and instead doing explicit pointer arithmetic. Both options are error prone. Other examples where the pointers to arrays with and without qualifier on the element type are currently incompatible are assignment, pointer subtraction, and conditional expressions:

double x[2][2];
const double (*xp)[2] = x; /* initialization from incompatible pointer */
double* v = 0;
const double* w = 0;
(void)(1 ? v : w);	// ok - no array
const double z[2][2];
(void)(1 ? x : z); /* incompatible pointers */
Finally, the current behaviour allows unsafe writes to arrays with const qualifier via conversion to a void pointer:

const int foo[5];
memset(&foo, 0, sizeof foo);

Suggested Technical Corrigendum

There seem to be two different ways to fix this issue:

A. Change pointer conversion rules

Rules for conversion, assignment, ... of pointers could be adapted to treat pointers to arrays with different qualifiers as compatible and include rules to prevent the loss of a const qualifier. This could be done using a general rule which states that pointer to arrays with qualifiers on the element type should always behave in the same way as pointers with a qualifier directly on the pointed-to type (as done in C++). Alternatively, individual rules and constraints could be modified to take pointers to arrays into account. This would require changes in: Compatibility of pointers is also a constraint in:

(there might be other places I missed).

B. Move qualifier to the array

Another option - which would represent a more fundamental change - is to move the qualifier to the array itself, i.e. change 6.7.3(9). In addition, if the array with qualifier is converted to a pointer, the qualifier must then be moved to the pointed-to type. This would affect 6.3.2.1(3). The required change would be similar in spirit to 6.5.2.3(3/4) which has rules on how accessing a member of a structure or union with const qualifier on the overall struct/union yields a const-qualified type of the member.

Additional Information

1. Programs which become legal

Programs where pointers to arrays are converted (passed as arguments, assigned, initialized, subtracted, or used in conditional operator) to a pointer to an array with const qualifier will now be allowed. In my experience, such programs already work with most compilers, but now produce warnings about 'incompatible pointer types'. (Note: The changed rules will not affect the semantics of pointers to pointers and will not make the unsafe scenario described in 6.5.16.1(6) possible).

2. Programs which beome illegal

Conversion of a pointer to an array with 'const' qualifier to a 'void*' (without qualifier) will not be allowed anymore (see memset example above). Also the type of the conditional expression in the following example should probably change:

void* v;
const int (*i)[3];
foo = (1 ? v : i); // <-- will have type 'const void*'

3. Compatibility with C++

The proposed changes would also increase compatibility with C++. The relevant part of the standard (according to draft N3690) which makes this work in C++ is:
3.9.3(5) ... An array type whose elements are cv-qualified is also considered to have the same cv-qualifications as its elements.

4. Experience with GCC 5

Starting with version 5, the proposed changes have been implemented in the GNU C compiler as an extension. Previously existing warnings about incompatible types when converting pointers to arrays with different qualifiers are now generated only with the '-pedantic' compiler flag. Additional warnings have been added which warn about loss of a const qualifiers in such cases. The change in the semantic for the conditional operator has not been implemented.

5. Open Question

Does this make sense for other type qualifiers?