@dbush has written a good, correct answer explaining what's guaranteed and allowed. The TL;DR is basically: yes it is contiguous but still no guarantees are made that allow you to reliably access any (sub) array out of bounds. Any pointer to an item needs to point within a valid array in order to use pointer arithmetic or the []
operator on it.
This answer adds some possible work-arounds to solve the problem.
One work-around is to use a union
and "type pun" between a 2D array and a 1D array:
#include <stdio.h>
typedef union
{
int arr_2d [3][4];
int arr_1d [3*4];
} arr_t;
int main (void)
{
arr_t a =
{
.arr_2d =
{
{ 1, 2, 3, 4},
{ 5, 6, 7, 8},
{9, 10, 11, 12}
}
};
printf("%d %d\n", a.arr_1d[6], a.arr_1d[(1*4)+2]); // well-defined, union type punning
return 0;
}
Another universal work-around is that it's fine to inspect any variable in C as a chunk of raw data by using character types, in which case you can regard the whole variable as an array of bytes:
#include <stdio.h>
int main (void){
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
unsigned char* ptr = (unsigned char*)a;
// well-defined: byte-wise pointer arithmetic on a character type
// well-defined: dereferencing as int, since the effective type of the memory location is int:
int x = *(int*)&ptr[sizeof(int)*6];
int y = *(int*)&ptr[sizeof(int[4])*1 + sizeof(int)*2];
printf("%d %d\n", x, y);
return 0;
}
Or in case you are a fan of obscure macros, a rewrite of the previous example (not really recommended):
#include <stdio.h>
#define GET_ITEM(arr, x, y) \ // see 1)
_Generic( **(arr), \
int: *(int*) &((unsigned char*)(arr))[sizeof(*arr)*(x) + sizeof(int)*(y)] )
int main (void){
int a[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
unsigned char* ptr = (unsigned char*)a;
// well-defined:
printf("%d %d\n", GET_ITEM(a,0,6), GET_ITEM(a,1,2));
return 0;
}
1) Explanation: _Generic for type safety. Cast to a character type, do byte-wise pointer arithmetic based on the size of a 1D array of int for x and the size of an int for y. Precedence goes as [] over &. The & is to get an address, then cast to an int* and dereference. The value will be returned by the macro.