2D 指针数组的引用或值传递

Pass by reference or value for 2D pointer array

提问人:mona jandali 提问时间:8/28/2020 更新时间:8/28/2020 访问量:610

问:

我有一个关于在 C 语言中对 2D 数组(VLA 变体)使用引用传递的问题。似乎大多数示例都演示了,例如此处的#2:如何在C语言中的函数中传递2D数组(矩阵)?表明您不必使用按引用传递约定。只是为了展示我的例子:

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

上面的示例代码在不使用约定的情况下分配 2D 数组进行引用传递,如下所示(请参阅使用 & 分配函数):

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

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

数组 C 内存管理 按引用传递

评论

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

答:

2赞 Vlad from Moscow 8/28/2020 #1

对于初学者来说,您没有二维阵列,而且没有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

也就是说,在函数中,在 main 中声明的指针本身会发生变化,因为它是通过引用传递给函数的。changep

1赞 Eric Postpischil 8/28/2020 #2

该代码创建一个指向 a 的指针,并将其设置为指向已分配的存储。它后面的循环使用指向 的指针填充分配的存储。double** testMatrix = malloc(sizeof(double*) * dim);doubledouble

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

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

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

因此,在传递 to assign 时,您传递了 的值,该值也是对它指向的存储的引用,并且该存储包含对值的存储的引用(以指针的形式)。testMatrixtestMatrixdouble

0赞 John Bode 8/28/2020 #3

首先,为了迂腐,C 按值、句点传递所有函数参数。有时这些值是指针,我们可以通过这些指针修改事物,这就是我们大多数人在 C 语言中谈论“通过引用传递”时的意思,但严格来说,我们正在做的是按值传递指针。

清澈如泥?好。

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

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

表达式的指针类型为 (),而不是数组类型。调用时,传递的指针值是预期的:testMatrixint **assign

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

如果声明一个真正的 2D 数组,例如

int arr[2][2];

你在内存中得到这个:

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

除非它是 or 一元运算符的操作数,或者是用于在声明中初始化字符数组的字符串文本,否则类型为“N-element array of ”的表达式将转换为“pointer to”类型的表达式(“decays”),并且表达式的值将是数组中第一个元素的地址。sizeof&TT

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

同样,这并不是真正的“通过引用传递”,我们是通过值传递指针。但是,实际效果是函数中的形式参数可以下标,函数中对数组元素的任何更改都会反映在调用者传递的数组中。

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

因此,如果我用foo

foo( arr, 2 );

那么函数原型将是

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

在函数参数声明的上下文中,并且与 - 相同,这三个都声明为 ,因此您也可以将该声明写为T a[N]T a[]T *aaT

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 );

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


  1. 不同的指针类型可能具有不同的表示形式,因此 和 的值可能不按位相同,但它们最终表示相同的位置。arr&arr
0赞 vmp 8/28/2020 #4

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

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