While the innermost dimension of a multidimensional array in C readily converts to a pointer:
char (*p)[2][3][4]= //<pointer to an array of 2 or array of 3 of array of 4 char
(char[5/*<- this one decays to a ptr*/][2][3][4]){0};
//^array 5 of array 2 of arry 3 of array 4 of char
;
arrays and pointers are very different in C despite the confusingly identical syntax for indexing and dereferencing (which are both two sides of the same coin as: x[index] is the same as *(x+index) or index[x]).
I think this is a part of C where if you don't have context, the idea that the language maps directly to assembly breaks down most apparently.
Compare
char a[1][1][1][1]={{{{'a'}}}}; //1 byte
char ****b = &(char***){&(char**){&(char*){&(char){'b'}}}}; //1byte+4*ptr_sz
and now the code that ****a generates vs what ****b generates:
char get_a_char(void)
{
return ****a;
}
char get_b_char(void)
{
return ****b;
}
x86-64:
get_a_char:
mov al, BYTE PTR a[rip]
ret
get_b_char:
mov rax, QWORD PTR b[rip]
mov rax, QWORD PTR [rax]
mov rax, QWORD PTR [rax]
mov rax, QWORD PTR [rax]
mov al, BYTE PTR [rax]
ret
When you dereference a multiply indirect pointer (b), you get a pointer chase.
When you dereference/subscript a multidimensional array, then your subscripts (zeros if you're just dereferencing) and the dimensions of the array are used to calculate an offset from a base, so you eventually either get an offsetted pointer (the same pointer if you're just derefing, just with a different type) if you deref/subscript through just some of the dimensions, or the same followed by a fetch from that address if you deref/subscript through all of them.
In your case ptr is int (*)[3] -- a pointer to an array of 3 int but ptr2 is int** -- a pointer to a pointer to int.
When you do ptr2[1][2] you add 1 pointer size, fetch a pointer from there, and then add 2 int (target type) sizes to the fetched pointer
and fetch from there.
That's very different from when you do ptr[1][2] in which case you add one int[3] size to the base pointer and then 2 int sizes and fetch from there (a total of 1 fetch).
The types of ptr obviously cannot be compatible with the type of ptr2.
int **ptr2 = &p;should cause some since you're assigning anint *to anint **.&pis the same aspin this case