0

I have a question about using pass-by-reference for 2D arrays (VLA variants) in C. It seems most of the examples demonstrated, like #2 here: How to pass 2D array (matrix) in a function in C? shows that you don't have to use pass-by-reference convention. Just to show my example:

#include <stdio.h>
#include <stdlib.h>

void assign(double** testMatrix, int* dim){
   for(int row=0; row < *dim; ++row){
       for(int column=0; column< *dim; ++column){
           testMatrix[row][column] = 0;
       }
   }
   
}

int main(void) {
   
   int dim = 200;
   
   double** testMatrix = malloc(sizeof(double*) * dim);
   for(int i=0; i < dim; ++i){
       testMatrix[i] = malloc(sizeof(double) * dim);
   }
   
   assign(testMatrix, &dim);
   
   //deallocate test matrix
   for(int i=0; i< dim; ++i){
       free(testMatrix[i]);
   }
   free(testMatrix);
   
   return 0;
}

the above sample code assigning the 2D array without using conventions for pass-by-reference, like the sample below (see assign function with the &):

#include <stdio.h>
#include <stdlib.h>

void assign(double*** testMatrix, int* dim){
    for(int row=0; row < *dim; ++row){
        for(int column=0; column< *dim; ++column){
            (*testMatrix)[row][column] = 0;
        }
    }
    
}

int main(void) {
    
    int dim = 200;
    
    double** testMatrix = malloc(sizeof(double*) * dim);
    for(int i=0; i < dim; ++i){
        testMatrix[i] = malloc(sizeof(double) * dim);
    }
    
    assign(&testMatrix, &dim);
    
    //deallocate test matrix
    for(int i=0; i< dim; ++i){
        free(testMatrix[i]);
    }
    free(testMatrix);
    
    return 0;
}

My question is how is the first example's 2D array modified without passing the reference of the array?

4 Answers4

2

For starters you do not have a two-dimensional array and moreover a VLA array. You have a pointer of the type double ** that points to an allocated memory.

Within this function

void assign(double** testMatrix, int* dim){
   for(int row=0; row < *dim; ++row){
       for(int column=0; column< *dim; ++column){
           testMatrix[row][column] = 0;
       }
   }
   
}

the pointer itself is not changed. It is the pointed data that are changed and the pointed data are passed to the function by reference using the pointer declared in main.

Within this function

void assign(double*** testMatrix, int* dim){
    for(int row=0; row < *dim; ++row){
        for(int column=0; column< *dim; ++column){
            (*testMatrix)[row][column] = 0;
        }
    }
    
}

there is again the passed pointer by referenced is not changed. So there is no sense to pass the original pointer by reference.

Here is a demonstrative program that shows when you need to pass a pointer by reference to change it itself.

#include <stdio.h>
#include <stdlib.h>

int change( int **p )
{
    int *tmp = realloc( *p, 2 * sizeof( int ) );
    int success = tmp != NULL;
    
    if ( success )
    {
        tmp[1] = 2;
        
        *p = tmp;
    }
    
    return success;
}

int main(void) 
{
    int *p = malloc( sizeof( int ) );
    *p = 1;
    
    printf( "p[0] = %d\n", p[0] );
    
    if ( change( &p ) )
    {
        printf( "p[0] = %d, p[1] = %d\n", p[0], p[1] );
    }
    
    free( p );

    return 0;
}

The program output is

p[0] = 1
p[0] = 1, p[1] = 2

That is within the function change the pointer p itself declared in main is changed because it is passed to the function by reference.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
1

The code double** testMatrix = malloc(sizeof(double*) * dim); creates a pointer to a pointer to a double and sets it to point to allocated storage. The loop that follows it fills in the allocated storage with pointers to double.

Then the function call assign(testMatrix, &dim); passes that first pointer to assign.

Since assign has the address of the allocated storage, it can access the pointers in it. Since it has those pointers, it can access the storage they point to. This answers the question “how is the first example's 2D array modified…”: When you pass a pointer to something, you are passing a means of accessing the thing.

In fact, passing a pointer to something is passing a reference to something. The pointer refers to the thing. (C++ introduce a new feature it called a “reference,” and it is a sort of automatically managed reference. But any way of referring to a thing—giving its address, its name, a description of where to find it, a bibliographic citation, a URL, or a pointer to a structure that has such information—is a kind of reference.)

Thus, in passing testMatrix to assign, you passed the value of testMatrix, which is also a reference to the storage it points to, and that storage contains references (in the form of pointers) to the storage for the double values.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

First of all, just for pedantry's sake, C passes all function arguments by value, period. Sometimes those values are pointers, and we can modify things through those pointers, which is what most of us mean when we talk about "pass by reference" in C, but strictly speaking what we are doing is passing a pointer by value.

Clear as mud? Okay.

Next, your testMatrix is not a 2D array - it's a pointer to the first of a sequence of pointers. In memory it looks something like this:

            int **      int *                    int
            +---+       +---+                    +---+
testMatrix: |   | ----> |   | testMatrix[0] ---> |   | testMatrix[0][0]
            +---+       +---+                    +---+
                        |   | testMatrix[1] --+  |   | testMatrix[0][1]
                        +---+                 |  +---+
                         ...                  |   ...
                                              |   
                                              |  +---+
                                              +->|   | testMatrix[1][0]
                                                 +---+ 
                                                 |   | testMatrix[1][1]
                                                 +---+
                                                  ...

The expression testMatrix has pointer type (int **), not array type. When you call assign you are passing a pointer value, which is what it expects:

void assign(double** testMatrix, int* dim)

If you declare a true 2D array such as

int arr[2][2];

you get this in memory:

      int
      +---+
 arr: |   | arr[0][0]
      +---+ 
      |   | arr[0][1]
      +---+
      |   | arr[1][0]
      +---+
      |   | arr[1][1]
      +---+

Unless it is the operand of the sizeof or unary & operators, or is a string literal used to initialize a character array in a declaration, an expression of type "N-element array of T" is converted ("decays") to an expression of type "pointer to T" and the value of the expression will be the address of the first element of the array.

The expression arr has type "2-element array of 2-element array of int"; unless it is the operand of the sizeof or unary & operators, it "decays" to an expression of type "pointer to 2-element array of int", or int (*)[2]. If you pass the expression arr to a function, the function will actually receive a pointer to the first element of the array, not a copy of the array.

Again, this isn't really "pass by reference", we're passing a pointer by value. However, the practical effect is that the formal parameter in the function can be subscripted, and any changes to array elements in the function are reflected in the array passed by the caller.

This is why you almost never need to use the & operator when passing an array expression as a function parameter - the function is already receiving the address of the first element of the array. Yes, the address of an array is the same as the address of its first element, so the value of arr and &arr would be the same1, they'd just have different types (int (*)[2] vs. int (**)[2]).

So if I call a function foo with

foo( arr, 2 );

then the function prototype will be

void foo( int (*arr)[2], int rows )

In the context of a function parameter declaration, T a[N] and T a[] are identical to T *a - all three declare a as a pointer to T, so you can also write that declaration as

void foo( int arr[][2], int rows )

Again, this is only true for function parameter declarations.

VLAs work mostly like regular arrays - what you can do in the function prototype is write

void foo( int rows, int cols, int arr[rows][cols] )

This works if you declare a VLA like

int rows = get_rows();
int cols = get_cols();
int myvla[rows][cols];

foo( rows, cols, myvla );

Again, what foo receives is a pointer to the first element of myvla, not a copy of the array.


  1. Different pointer types may have different representations, so the values of arr and &arr may not be bitwise identical, but they do ultimately represent the same location.
John Bode
  • 119,563
  • 19
  • 122
  • 198
0

You might understand the difference when you try to alter the address(which is the kind of value a pointer store!) inside the function.

//here you create a copy of the matrix address, so changing 
//this address here wont change the real address
void f1(double **m) 
{
     //m is a copy of the address
     m = malloc(sizeof(double*)*200);
}
//Here we change the address of what as passed, it will assign a new address
//to the matrix that was passed
void f2(double ***m)
{
    //when you dereference with one * you get the real address and any changes
    // on *m will reflect on the parameter passed
    *m = malloc(sizeof(double*)*200);
}

int main(void) {

  int dim = 200;
  double** testMatrix = malloc(sizeof(double*) * dim);
  for(int i=0; i < dim; ++i){
    testMatrix[i] = malloc(sizeof(double) * dim);
  }
  double **other = testMatrix;
  f1(testMatrix);
  printf("point to the same place ? %d\n", other == testMatrix); 
  f2(&testMatrix);
  printf("point to the same place ? %d\n", other == testMatrix);       
  return 0;
}
vmp
  • 2,370
  • 1
  • 13
  • 17