为啥我们在传递动态二维数组时不需要列数?

Posted

技术标签:

【中文标题】为啥我们在传递动态二维数组时不需要列数?【英文标题】:Why we don't need number of column when passing the dynamic 2d array?为什么我们在传递动态二维数组时不需要列数? 【发布时间】:2017-06-15 06:21:03 【问题描述】:

假设我有两个数组,我将它们传递给一个函数:

void func(int arr1[][4], int **arr2)  // <- I need to give n in only one, why?
...

int main() 
    int n = 5, m = 4;
    int arr1[n][m];
    int **arr2 = (int**)malloc(n * sizeof(int*));
    for(int i = 0;i < n;i++)
        arr2[i] = (int*)malloc(m * sizeof(int));
    func(arr1, arr2);
    return 0;

为什么我们不能以类似的方式处理两个数组传递?

编辑:代码中有错误。

【问题讨论】:

arr2 不是数组,而是指针,实际上是指向指针的指针。 在任何一种情况下,函数都需要以某种方式知道指向数据的大小。无论语法如何,都没有办法解决。所以理想情况下,你会写类似void func(size_t x, size_t y, int arr[x][y]) 另请参阅Correctly allocating multi-dimensional arrays 以消除您对动态二维数组的误解。 注意main中的arr1不能传递给func;它需要一个int arr1[][5],但实际上你在main() 中有int arr1[5][4];(除了它是一个可变限定数组,而不是一个固定大小的常规数组)。一个痛苦的世界等着你。 另外,您的代码是严格的 C 代码(C99 或 C11 代码)。 C++ 编译器不需要在main() 中允许您的数组。如果 nm 是 const 限定的,那么问题会有所不同。除非您指定 -pedantic,否则 GCC/G++ 允许使用可变限定数组,但世界上还有其他 C++ 编译器。 【参考方案1】:

实际上与您所说的相反:您不必传递行数。假设数组索引是这样工作的:

int arr[MAX_ROW][MAX_COL]; /* with both 3 */

           col
     --------------->
    | 0,0   0,1   0,2
row | 1,0   1,1   1,2
    V 2,0   2,1   2,2

当您传递int arr[][MAX_COL] 时,编译器知道当您使用arr[row][col] 之类的地址时,下一行将从哪里开始。

如果您使用指针手动执行此操作,它看起来像:&amp;arr[0][0] + row * MAX_COL + col。在该示例中,您还必须知道数组的列大小MAX_COL 才能计算下一行。

原因是数组在内存中是连续的。上面的数组在内存中表示如下:

|     row = 0     |     row = 1     |     row = 2     |
| 0,0   0,1   0,2 | 1,0   1,1   1,2 | 2,0   2,1   2,2 |

编译器还必须知道行偏移量,因为当您将声明为int arr[MAX_SIZE] 的数组传递给函数void foo (int arr[]) 时,它会衰减为指向数组int* arr 开头的指针。在数组数组(二维数组)的情况下,它也衰减为指向其第一个元素的指针,该指针指向单个数组int (*arr)[MAX_COL]

简而言之:使用int arr[][MAX_COL],编译器拥有使用arr[row][col] 寻址数组所需的所有信息。

【讨论】:

我理解你的回答,它确实有帮助。简而言之,如果我可以建议的话,更好的是,“以更简单的方式,int **arr2 是使用指针形成的二维整数网格,而 int arr1[][] 是整数的一维数据结构,其行结束位置的信息。“如果我理解错了,请纠正我。 @AbhishekAgrawal:不知道二维网格和一维数据结构是什么意思。实际上所有的数组,1D、2D、3D等都是连续存储在内存中的。 指针是连续的,因为每行都是在每次迭代中使用malloc分配的,所以第一行最后一个元素的地址和第二行第一个元素的地址应该有跳转? 是的,你是对的!请记住:int** arr != int arr[row][col]。指向指针的指针不是固定大小的二维数组see here。如前所述,固定大小的二维数组等同于int (*arr)[MAX_COL],它是指向单个数组的指针。指向指针 int** arr 的指针并不保证是连续的,因为您说它是动态分配的。【参考方案2】:

其实正好相反,你可以只省略一个索引(多维数组的情况下),最里面的一个。

这是因为,数组在作为函数参数传递时,会衰减为指向第一个元素的指针。引用C11,第 6.3.2.1 章

除了当它是sizeof 运算符、_Alignof 运算符或 一元 &amp; 运算符,或者是用于初始化数组的字符串文字,该表达式具有 type ''array of type''转换为一个类型为''pointer to type''的表达式 到数组对象的初始元素,并且不是左值。 [...]

因此,像这样的符号

 void func(int arr1[][5], int **arr2)    //an array of array of 5 ints

 void func(int (*arr1) [5], int **arr2)  //pointer to the starting element of an array of 5 ints

是等价的。

【讨论】:

请多解释一下:'衰减到指向第一个元素的指针'。 不是叫最外层(可省略)吗?我相信它指的是内存布局,而不是标记之间的代码位置。 @AbhishekAgrawal 实际上,函数数组 parameter 被“调整”为指向数组元素类型的指针。所以int[42]调整int*。数组 decay 是当您使用数组作为 argument 调用此类函数时发生的情况。而“二维数组”是数组的数组。 @cmaster 谢谢,这是一个错字。 :)【参考方案3】:

您实际上只有一个整数数组(即int arr1[][5])和一个指向整数指针的指针,即int **arr2。即使像arr1[10][5] 这样的数组,当作为参数传递给函数时,会衰减到指向元素所在内存开头的指针,内存布局和编译器处理的方式也存在(很大)差异访问这些指针。

顺便说一句,主要应该是int n=5,m=4;int arr1[m][n],而不是int n=5,m=4;int arr1[n][m]

关于内存布局:

int [10][5] 形式的二维整数数组表示为 10 个连续的“行”,每行包含 5 个“列”(即整数值)。这个数组的大小是10 * 5 * sizeof(int),一个“行”的大小是5 * sizeof(int)

指向 int int **p 的指针只是一个指针;它的大小是sizeof(int**),即使你已经“分配”了一系列整数指针lile p = malloc(10 * sizeof (int*));请注意sizeof(int *) 中的“*”,因为您创建的是指向整数的指针序列,而不是整数序列。这是内存布局的主要区别:它不是整数的二维数组,而是整数指针的一维数组。如果实际上已经为“10”个整数分配了 10 个“行”,那么每一行可能位于内存的不同部分。管理这样一个(扩展的)10x5 整数值所需的空间是“10*sizeof(int*) + 10*5*sizeof(int)”。

关于访问:

假设有一个int arr[][5] 类型的变量,它是一个二维整数数组,其中列的大小为5,行数未确定。非正式地,像int x = arr[3][4] 这样的访问被转换为对数组的(3*5 + 4)th 元素的访问,即“行乘以行大小加上列”;请注意 - 基于此公式 - 编译器不需要知道数组实际有多少行。

相反,让我们假设一个类型为int **p 的变量。您可以将x = p[3][4] 之类的访问视为等同于int *r = p[3]; int x = r[4];注意rint * 类型,即它是一个指针,r[4] 然后取消引用这个指针并返回一个整数值。

这是相当非正式的描述。 然而主要问题是arr[][5] 的内存布局仅包含连续的整数值,而int **arrr 可能是一个指针序列(甚至只是一个这样的指针),它们中的每一个都可能指向一系列整数值(或只是一个整数值)。

【讨论】:

以上是关于为啥我们在传递动态二维数组时不需要列数?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中获取二维动态数组的行数和列数

为啥在使用单指针传递给函数时必须对二维数组进行类型转换?

动态创建二维素组

C语言函数传递二维数组

如果行和列的差大于 1,为啥我不能打印二维数组?

c语言中怎样实现对二维数组元素进行赋值并输出。