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

问题描述 投票:2回答:2

我对C相当陌生,只是将我将HEAP内存中分配的2D数组传递给函数时的实际情况感到困惑。我编写了具有三个功能的代码,A,B,C演示了我的问题。

本质上,当我在函数A的堆栈空间中创建2d数组时,我能够将该2d数组指针传递给需要参数(int size, int (*arr)[size])且功能正常的函数B。我的理解是'int size'变量是必需的,以便现在让arr指针每个增量应跳多少空间

但是,当我在函数A的HEAP空间中创建2d数组时,将其传递给函数B似乎会丢失数据的位置(请参见代码)。但是,如果我将此HEAP空间2d数组传递给具有参数(int **arr)的function-C,则可以正常工作。

如果有人可以尝试解释为什么在将HEAP空间2d数组传递给function-C时为什么不需要指定大小会很棒。另外,当我将在STACK空间中创建的2d数组传递给函数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);
}
c multidimensional-array parameter-passing heap-memory stack-memory
2个回答
3
投票

数组/指针转换

了解您的缺陷在于围绕数组的使用和指针的使用。在C语言中,数组是对象的一种独特类型。引起混乱的一个原因是,数组在访问时被转换为指向其第一个元素的指针。 (数组/指针转换)这受C11 Standard - 6.3.2.1 Other Operands - Lvalues, arrays, and function designators(p3)控制(请注意没有发生数组/指针转换的4个异常)

这里的键是类型。当您声明2D数组时,例如

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

在访问时,它将转换为指针-但是是什么类型? C语言中的2D数组是1D数组的数组。数组/指针转换仅适用于第一个间接级别。因此,在访问时arrSTACK被转换为指向数组的指针 int[5]。因此其类型为int (*)[5]。由于类型控件指针算术 arrSTACK + 1推进了五个整数值,因此它指向组成arrSTACK的第二个一维数组的开头(第二行)

指针

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

因此,让我们看一下指针算术与arrHEAP的工作方式的不同。取消引用arrHEAP时,将使用pointer-to-pointer(例如arrHEAP[0])。如果有pointer-to-pointer-to int并取消引用它,则剩下的是pointer-to-pointer >> 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时,第一个分配的指针与另一个指针没有任何保证的关系。以后可以替换或重新分配它们。

这意味着,如果尝试提供arrSTACKfunction_C(int **arr),则编译器将针对不兼容的指针类型生成警告-因为它们是。相反,如果尝试将arrHEAP设置为function_B(int size, int (*arr)[size]),由于指针类型不兼容,同样会发出警告-因为它们是。

即使对象和数组在其他函数中的使用方式看起来像是可行的,因为您本质上是以相同的方式对两个索引建立索引,编译器也无法让一个不兼容的类型通过-这不是编译器的工作。

编译器只能根据您在编写代码时对它的承诺进行操作。对于function_B(int size, int (*arr)[size]),您保证要发送包含5 int的1D数组的2D数组。使用function_C(int **arr),您答应了编译器将提供

pointer-to-pointer-to

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]指向的位置开始。


1
投票
void function_B(int size (int (*arr)[size])中,arr指向某个行,该行中包含一定数量的int。要知道任何行在哪里,编译器需要知道每行有多少int。例如,对于10行12 int,第3行在3•12 int之后开始。
© www.soinside.com 2019 - 2024. All rights reserved.