在 C 中,当使用 malloc 创建二维数组时,为什么在传递给函数时不需要指定二维数组大小?

In C why do I NOT need to specify 2D array size when passing into function when the 2D array is created with malloc?

我是 C 的新手,当我将分配在 HEAP 内存中的二维数组传递给函数时,我只是对实际发生的事情感到困惑。我编写了具有三个函数的代码,A、B、C 演示了我的问题。

本质上,当我在函数 A 的堆栈 space 中创建一个二维数组时,我能够将该二维数组指针传递给需要参数 (int size, int (*arr)[size]) 的函数 B,并且工作正常。我的理解是 'int size' 变量需要让 arr 指针现在多少 space 它应该跳每个增量

但是,当我在函数 A 的 HEAP space 中创建二维数组时,将其传递给函数 B 似乎丢失了数据的位置(请参阅代码)。但是,如果我将这个 HEAP space 二维数组传递给具有参数 (int **arr) 的函数 C,它就可以正常工作。

如果有人能解释为什么我在将 HEAP space 二维数组传递给函数 C 时不需要指定大小,那就太好了。另外,当我将在 STACK space 中创建的二维数组传递给 function-C 时,它崩溃了,这是为什么?

这是展示我的问题的示例代码 (Output is this):

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

void function_A(int num)
{
    // allocating HEAP space for 2D array
    int **arrHEAP = (int **)malloc(2*sizeof(int*)); 
    arrHEAP[0] = (int *)malloc(5*sizeof(int));
    arrHEAP[1] = (int *)malloc(5*sizeof(int));
    for(int i=0;i<2;i++) // initialising
        for(int j=0;j<5;j++)
            arrHEAP[i][j] = num++;
    function_B(5, arrHEAP); // prints random data
    function_C(arrHEAP); // prints correctly, works

    // allocating STACK space for 2D array and initialising
    int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};
    function_B(5, arrSTACK); // prints correctly, works
    //function_C(arrSTACK); // if I were to run this it crashes the program, why?
}
void function_B(int size, int (*arr)[size])
{
    for(int i=0;i<2;i++)
        for(int j=0;j<5;j++)
            printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
void function_C(int **arr)
{
    for(int i=0;i<2;i++)
        for(int j=0;j<5;j++)
            printf("HEAP row is %d, value is %d:\n", i, arr[i][j]);
}
int main()
{
    function_A(1);
}

Array/Pointer 转换

你理解的缺陷围绕着数组的使用和指针的使用。在 C 语言中,数组是一种不同类型的对象。造成混淆的其中一个原因是数组在访问时被转换为指向其第一个元素的指针。 (array/pointer 转换)这由 C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3) 控制(注意没有发生 array/pointer 转换的 4 个例外)

这里的关键是类型。当你声明一个二维数组时,例如

int arrSTACK[2][5] = {{100, 200, 300, 400, 500},{600,700,800,900,1000}};

在访问时它将被转换为指针——但是什么类型? C 中的二维数组是一维数组的数组。 Array/pointer 转换仅适用于第一级间接寻址。因此在访问时 arrSTACK 被转换为 指向数组 int[5] 的指针。所以它的类型是int (*)[5]。由于类型控制 指针算法 arrSTACK + 1 推进五个整数值,因此它指向构成 arrSTACK 的第二个一维数组的开头(第二行)

指针

int **arrHEAP 声明了一个指针。一个 指针到指针 int。它与数组无关。但是,可以对 指针到指针 进行索引,就像对 2D 数组进行索引以寻址存储在内存中的各个整数一样。这是二维数组与通过为指针分配存储然后为整数分配存储并将每个包含整数的块的起始地址分配给您分配的指针之一而创建的对象之间的唯一相似之处。这里不能保证 arrHEAP 的所有元素在内存中都是连续的,因为它们在二维数组中是连续的。

那么让我们看看指针运算与 arrHEAP 的区别。当您取消引用 arrHEAP 时, 指针到指针 (例如 arrHEAP[0])取消引用会产生什么类型?如果你有一个 pointer-to-pointer-to int 并且你取消引用它你剩下 pointer-to int.因此对于数组,取消引用导致类型 pointer-to int[5],但是对于 arrHEAP[0] 结果只是一个 pointer-to int(没有 5 -- 它只是指向 int 的指针)。那么指针算术有何不同呢? arrSTACK + 1 将指针前进 5 * sizeof(int) 字节(20 字节)。使用 arrHEAP + 1 前进到分配的指针块中的下一个指针 (1 指针 8 字节)。

这就是为什么不能将一个函数传递给另一个函数的原因。期望数组的函数理解 arrSTACK[0]arrSTACK[1] 相隔 20 字节,而指针 arrHEAP[0]arrHEAP[1] 只是 8-字节分开。这是您生成的指针不兼容警告和错误的症结所在。

那么就无法保证 arrSTACK 的所有值在内存中都是连续的。你知道 arrSTACK[1] 总是从数组开始算起 20 字节。对于 arrHEAP,从邻接的角度来看,第一个分配的指针与另一个指针没有保证的关系。它们以后可以被替换或重新分配。

这意味着如果您尝试将 arrSTACK 提供给 function_C(int **arr),编译器将为不兼容的指针类型生成警告——因为它们是。相反,如果您尝试将 arrHEAP 提供给 function_B(int size, int (*arr)[size]),它同样会再次发出警告,因为指针类型不兼容——因为它们是。

即使对象和数组在另一个函数中的使用方式看起来可行,因为您基本上以相同的方式对两者进行索引,编译器也不能让一个不兼容的类型通过——这不是编译器工作。

编译器只能根据您在编写代码时对它做出的承诺进行操作。对于 function_B(int size, int (*arr)[size]),您承诺要发送包含 5 int 的一维数组的二维数组。使用 function_C(int **arr),您向编译器承诺您将提供 指向指向的指针 int。当编译器发现您试图将错误的对象作为参数传递时,它会发出警告,您应该注意该警告,因为 arrHEAP 中的第二个整数块的开头不能保证为 6 int 远离 arrHEAP 的开头 - 在那里找不到它。

void function_B(int size (int (*arr)[size])中,arr指向某处有某行数int。要知道任何行在哪里,编译器需要知道每行中有多少 int。例如,10 行 12 int,第 3 行在 3•12 int.

之后开始

void function_C(int **arr)中,arr指向的地方有指向int行的指针。要知道任何行在哪里,编译器只需加载这些指针之一。例如,第 3 行从指针 arr[3] 指向的位置开始。