【问题标题】:Pass by reference or value for 2D pointer array通过引用或值传递二维指针数组
【发布时间】:2020-12-16 16:45:33
【问题描述】:

我有一个关于在 C 中对 2D 数组(VLA 变体)使用 pass-by-reference 的问题。似乎大多数示例都演示了,比如这里的 #2:How to pass 2D array (matrix) in a function in C? 表明您不必使用 pass -by-reference 约定。只是为了展示我的例子:

#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;
}

上面的示例代码分配了二维数组,没有使用传递引用的约定,就像下面的示例(请参阅带有 & 的分配函数):

#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;
}

我的问题是如何在不传递数组引用的情况下修改第一个示例的二维数组?

【问题讨论】:

  • 第一个观察结果是 testMatrix,尽管它的名字,只是一个一维数组(指针)。
  • @TamilSelvanV:问题的代码中没有数组变量。
  • @EricPostpischil 是的,你是对的,我的措辞不准确:testMatrix 是一个简单的指针,指向动态分配的指针数组的第一个元素......

标签: arrays c memory-management pass-by-reference pass-by-value


【解决方案1】:

当您尝试更改函数内部的地址(这是一种指针存储的值!)时,您可能会理解其中的区别。

//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;
}

【讨论】:

    【解决方案2】:

    首先,为了学究气,C 通过值,句点传递 all 函数参数。有时这些值是指针,我们可以通过这些指针来修改东西,这就是我们大多数人在 C 中谈论“按引用传递”时的意思,但严格来说我们所做的是按值传递指针 .

    一清二楚?好的。

    接下来,您的 testMatrix 不是二维数组 - 它是指向指针序列中第一个的指针。在内存中它看起来像这样:

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

    表达式 testMatrix 具有指针类型 (int **),而不是数组类型。当您调用 assign 时,您传递的是一个指针值,这是它所期望的:

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

    如果你声明一个真正的二维数组,例如

    int arr[2][2];
    

    你在记忆中得到这个:

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

    除非它是sizeof 或一元&amp; 运算符的操作数,或者是用于在声明中初始化字符数组的字符串字面量,否则为“N 元素”类型的表达式 T" 的数组被转换 ("decays") 为 "pointer to T" 类型的表达式,表达式的值将是数组第一个元素的地址。

    表达式 arr 的类型为“int 的 2 元素数组的 2 元素数组”;除非它是sizeof 或一元&amp; 运算符的操作数,否则它会“衰减”为“指向int 的2 元素数组的指针”或int (*)[2] 类型的表达式。如果您将表达式arr 传递给函数,该函数实际上将接收指向数组第一个元素的指针,不是数组的副本。

    同样,这并不是真正的“按引用传递”,我们是在传递一个指针按值。但是实际效果是函数中的形参可以下标,函数中数组元素的任何变化都会体现在调用者传递的数组中。

    这就是为什么在将数组表达式作为函数参数传递时几乎不需要使用&amp; 运算符的原因 - 函数已经接收数组第一个元素的地址。是的,数组的地址与其第一个元素的地址相同,所以arr和&amp;arr的值将是相同的1,它们'只会有不同的类型(int (*)[2] vs. int (**)[2])。

    所以如果我调用一个函数foo

    foo( arr, 2 );
    

    那么函数原型就是

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

    在函数参数声明的上下文中,T a[N] 和 T a[] 与 T *a 相同 - 所有三个都将 a 声明为指向 T 的指针,因此您也可以将该声明写为

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

    同样,这仅对于函数参数声明是正确的。

    VLA大部分像普通数组一样工作 - 你可以在函数原型中做的就是写

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

    如果你声明一个 VLA 类似

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

    同样,foo 接收的是指向myvla 的第一个元素的指针,而不是数组的副本。


    1. 不同的指针类型可能有不同的表示,所以arr和&amp;arr的值在位上可能不相同,但它们最终表示的是同一个位置。

    【讨论】:

      【解决方案3】:

      对于初学者,您没有二维数组,而且还没有 VLA 数组。您有一个 double ** 类型的指针,它指向已分配的内存。

      在这个函数中

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

      指针本身没有改变。改变的是指向的数据,指向的数据使用main中声明的指针通过引用传递给函数。

      在这个函数中

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

      又是通过引用传递的指针没有改变。所以通过引用传递原始指针是没有意义的。

      这是一个演示程序,显示何时需要通过引用传递指针来更改它本身。

      #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;
      }
      

      程序输出是

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

      即在函数change 中,main 中声明的指针p 本身发生了变化,因为它是通过引用传递给函数的。

      【讨论】:

        【解决方案4】:

        代码double** testMatrix = malloc(sizeof(double*) * dim); 创建一个指向double 的指针并将其设置为指向已分配的存储。紧随其后的循环使用指向double 的指针填充分配的存储空间。

        然后函数调用assign(testMatrix, &amp;dim); 将第一个指针传递给assign。

        由于assign 有分配存储的地址,它可以访问其中的指针。由于它有这些指针,它可以访问它们指向的存储。这回答了“第一个示例的二维数组是如何修改的……”的问题:当您传递指向某事物的指针时,您传递的是一种访问该事物的方法。

        实际上,传递指向某事物的指针就是传递对某事物的引用。指针指向事物。 (C++ 引入了一个称为“引用”的新特性,它是一种自动管理的引用。但是任何引用事物的方式——给出它的地址、它的名称、在哪里找到它的描述、一个书目引用、URL 或指向具有此类信息的结构的指针——是一种引用。)

        因此,在将testMatrix 传递给assign 时,您传递了testMatrix 的值,这也是对其指向的存储的引用,并且该存储包含对存储的引用(以指针的形式) double 值。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-07-29
          • 2021-10-09
          • 2010-09-25
          • 2013-05-19
          • 2020-06-29
          相关资源
          最近更新 更多