In understand that, in case of a declaration, the following two lines are not equivalent (in terms of allocation)
double ** v1;
double v2[3][3];
But what about when these are used as function argument?
void func1(double** v1)
void func2(double v2[3][3])
Except for the hint about the number of values in func2
, is there a functional difference?
Strictly speaking, double **arr
and double arr[3][3]
are two really different types.
double **arr
is not an array, it's just a pointer to a pointer to double
. You have
no more information. You cannot know beforehand that arr
is an array of arrays of doubles. So doing arr[1][2]
would mean "skip the first double
pointer and dereference the second pointer as an array taking the third element (index 2)".
So, x = arr[1][2]
could be translated as the following pseudocode:
tmp = memory[<arr_address> + sizeof(double *)]
x = memory[<tmp_address> + 2*sizeof(double)]
double arr[3][3]
, on the contrary, is a constant size array, and more assumptions can be made. It is guaranteed to be contiguous in memory and doing arr[1][2]
means "skip the first array of 3 double
, then skip two double
of the second array and look at the third (index 2)".
So, x = arr[1][2]
could be translated as the following pseudocode:
x = memory[<arr_address> + (3 + 2)*sizeof(double)]
As a practical example, consider the following program:
int func1(int** v1) {
return v1[1][2];
}
int func2(int v2[3][3]) {
return v2[1][2];
}
int main(void) {
int const_arr[3][3]; // Values are not important.
int **double_ptr; // We don't really run this code!
func1(const_arr);
func2(double_ptr);
}
When compiled, it gives the following (very relevant) warnings:
arrayparam.c: In function ‘main’:
arrayparam.c:13:8: warning: passing argument 1 of ‘func1’ from incompatible pointer type [-Wincompatible-pointer-types]
func1(const_arr);
^~~~~~~~~
arrayparam.c:1:5: note: expected ‘int **’ but argument is of type ‘int (*)[3]’
int func1(int** v1) {
^~~~~
arrayparam.c:14:8: warning: passing argument 1 of ‘func2’ from incompatible pointer type [-Wincompatible-pointer-types]
func2(double_ptr);
^~~~~~~~~~
arrayparam.c:5:5: note: expected ‘int (*)[3]’ but argument is of type ‘int **’
int func2(int v2[3][3]) {
You can see very clearly from the generated assembly, that the two functions do completely different things, and this assembly code is really just the same as the pseudocode I wrote above:
func1()
:
664: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi
668: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] ; load the array
66c: 48 83 c0 08 add rax,0x8 ; go 1*8 = 8 bytes forward (sizeof(int*))
670: 48 8b 00 mov rax,QWORD PTR [rax] ; dereference that pointer
673: 8b 40 08 mov eax,DWORD PTR [rax+0x8] ; go 2*4 = 8 bytes forward (2*sizeof(int)) and take the value there
func2()
:
67c: 48 89 7d f8 mov QWORD PTR [rbp-0x8],rdi
680: 48 8b 45 f8 mov rax,QWORD PTR [rbp-0x8] ; load the array
684: 48 83 c0 0c add rax,0xc ; go 3*4 = 12 bytes forward
688: 8b 40 08 mov eax,DWORD PTR [rax+0x8] ; go another 2*4 = 8 bytes forward and take the value there