1

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?

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Ben
  • 1,519
  • 23
  • 39

1 Answers1

2

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
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128