carraysfunctionpointers

Difference in function parameter: double pointer VS 2D array


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?


Solution

  • 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