Question

This came up from this answer to a previous question of mine. Is it guaranteed for the compiler to treat array[4][4] the same as array[16]?

For instance, would either of the below calls to api_func() be safe?

void api_func(const double matrix[4][4]);

// ...

{
  typedef double Matrix[4][4];

  double* array1 = new double[16];
  double array2[16];

  // ...

  api_func(reinterpret_cast<Matrix&>(array1));
  api_func(reinterpret_cast<Matrix&>(array2));
}
Was it helpful?

Solution

From the C++ standard, referring to the sizeof operator:

When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.

From this, I'd say that double[4][4] and double[16] would have to have the same underlying representation.

I.e., given

sizeof(double[4]) = 4*sizeof(double)

and

sizeof(double[4][4]) = 4*sizeof(double[4])

then we have

sizeof(double[4][4]) = 4*4*sizeof(double) = 16*sizeof(double) = sizeof(double[16])

I think a standards-compliant compiler would have to implement these the same, and I think that this isn't something that a compiler would accidentally break. The standard way of implementing multi-dimensional arrays works as expected. Breaking the standard would require extra work, for likely no benefit.

The C++ standard also states that an array consists of contiguously-allocated elements, which eliminates the possibility of doing anything strange using pointers and padding.

OTHER TIPS

I don't think there is a problem with padding introduced by having a multi-dimensional array.

Each element in an array must satisfy the padding requirements imposed by the architecture. An array [N][M] is always going to have the same in memory representation as one of [M*N].

Each array element should be laid out sequentially in memory by the compiler. The two declarations whilst different types are the same underlying memory structure.

@Konrad Rudolph:

I get those two (row major/column major) mixed up myself, but I do know this: It's well-defined.

int x[3][5], for example, is an array of size 3, whose elements are int arrays of size 5. (§6.5.2.1) Adding all the rules from the standard about arrays, addressing, etc. you get that the second subscript references consecutive integers, wheras the first subscript will reference consecutive 5-int objects. (So 3 is the bigger number; you have 5 ints between x[1][0] and x[2][0].)

I would be worried about padding being added for things like Matrix[5][5] to make each row word aligned, but that could be simply my own superstition.

A bigger question is: do you really need to perform such a cast?

Although you might be able to get away with it, it would still be more readable and maintainable to avoid altogether. For example, you could consistently use double[m*n] as the actual type, and then work with a class that wraps this type, and perhaps overloads the [] operator for ease of use. In that case, you might also need an intermediate class to encapsulate a single row -- so that code like my_matrix[3][5] still works as expected.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top