A few days ago I stumbled on a code where an extensive use of conversions from pointer to type to pointer to array of type was made to give a bi-dimensional view of a linear vector in memory. A simple example of such a technique is reported below for clarity:
#include <stdio.h>
#include <stdlib.h>
void print_matrix(const unsigned int nrows, const unsigned int ncols, double (*A)[ncols]) {
// Here I can access memory using A[ii][jj]
// instead of A[ii*ncols + jj]
for(int ii = 0; ii < nrows; ii++) {
for(int jj = 0; jj < ncols; jj++)
printf("%4.4g",A[ii][jj]);
printf("\n");
}
}
int main() {
const unsigned int nrows = 10;
const unsigned int ncols = 20;
// Here I allocate a portion of memory to which I could access
// using linear indexing, i.e. A[ii]
double * A = NULL;
A = malloc(sizeof(double)*nrows*ncols);
for (int ii = 0; ii < ncols*nrows; ii++)
A[ii] = ii;
print_matrix(nrows,ncols,A);
printf("\n");
print_matrix(ncols,nrows,A);
free(A);
return 0;
}
Given that a pointer to type is not compatible with a pointer to array of type, I would like to ask if there are risks associated with this casting, or if I can assume that this casting will work as intended on any platform.
It is guaranteed that a multidimensional array
T arr[M][N]has the same memory layout as a single-dimensional array with the same total number of elementsT arr[M * N]. The layout is the same because arrays are contiguous (6.2.5p20), and becausesizeof array / sizeof array[0]is guaranteed to return the number of elements in the array (6.5.3.4p7).However, it does not follow that it is safe to cast a pointer to type to a pointer to array of type, or vice versa. Firstly, alignment is an issue; although an array of a type with fundamental alignment must also have fundamental alignment (by 6.2.8p2) it is not guaranteed that the alignments are the same. Because an array contains objects of the base type, the alignment of the array type must be at least as strict as the alignment of the base object type, but it can be stricter (not that I’ve ever seen such a case). However, this is not relevant for allocated memory, as
mallocis guaranteed to return a pointer suitably allocated for any fundamental alignment (7.22.3p1). This does mean that you cannot safely cast a pointer to automatic or static memory to an array pointer, although the reverse is allowed:Next, it is not guaranteed that casting between array and non-array types actually results in a pointer to the correct location, as the casting rules (6.3.2.3p7) do not cover this usage. It’s highly unlikely though that this would result in anything other than a pointer to the correct location, and a cast via
char *does have guaranteed semantics. When going from a pointer to array type to pointer to base type, it’s better to just indirect the pointer:What are the semantics of array subscripting? As is well known, the
[]operation is just syntactic sugar for addition and indirection, so the semantics are those of the+operator; as 6.5.6p8 describes, the pointer operand must point to a member of an array that is large enough that the result falls within the array or just past the end. This is a problem for casts in both direction; when casting to a pointer to array type, the addition is invalid as there does not exist a multidimensional array at that location; and when casting to a pointer to base type, the array at that location only has the size of the inner array bound:This is where we start to see actual issues on common implementations, not just theory. Because an optimiser is entitled to assume that undefined behavior does not occur, accessing a multidimensional array through a base object pointer can be assumed not to alias any elements outside those in the first inner array:
The optimiser can assume access to
a[2][3]does not alias(*a)[i]and hoist it outside the loop:This will of course give unexpected results if
fis called withn = 50.Finally it’s worth asking whether this applies to allocated memory. 7.22.3p1 specifies that the pointer returned by
malloc“may be assigned to a pointer to any type of object with a fundamental alignment requirement and then used to access such an object or an array of such objects in the space allocated“; there’s nothing about further casting the returned pointer to another object type, so the conclusion is that the type of the allocated memory is fixed by the first pointer type the returnedvoidpointer is cast to; if you cast todouble *then you can’t further cast todouble (*)[n], and if you cast todouble (*)[n]you can only usedouble *to access the firstnelements.As such, I’d say that if you want to be absolutely safe you should not cast between pointer and pointer to array types, even with the same base type. The fact that layout is the same is irrelevant except for
memcpyand other accesses via acharpointer.