为什么我必须在将二维数组传递给C函数时提供维度?

时间:2014-02-11 02:33:29

标签: c memory-management multidimensional-array

我不确定标记是否相关,但可以随意添加。 我认为原因是历史原因,这就是我建议的原因。

为什么我不能声明如下的函数签名?

void foo(int doubly_indexed_array[][]) {
  ...
}

给出了

$ gcc mem.c
mem.c:4: error: array type has incomplete element type

为什么必须声明其中一个尺寸,如下所示?

void foo(int doubly_indexed_array[][10]) {
  ...
}

4 个答案:

答案 0 :(得分:6)

您需要声明第二个而不仅仅是一个。它与内存布局有关,2-d数组连续存储在内存中,这意味着所有第二维数组都是连续的。

因此对于int[2][2],内存布局看起来像(假设初始化为0):

[[0, 0][0, 0]]

编译器必须知道在第一维上编制索引时增加指针的数量。因此,如果int数组被命名为a,

a[i][j]确实是(address of a) + i*sizeof(int)*second_dimension + j*sizeof(int)

所有这些都需要在编译时知道,因此编译器可以生成代码。

答案 1 :(得分:2)

C中的数组就像指针一样,它不包含大小。因此,如果您不提供最后一个维度,编译器将不知道如何计算元素的地址

TYPE array[A][B];
&array[a][b] = (char*)array + a*sizeof(array[a]) + b
             = (char*)array + a*(B*sizeof(array[a][b])) + b
             = (char*)array + a*B*sizeof(TYPE) + b

如您所见,如果未声明B,那么当您访问array[a][b]时,它有3个未知变量需要解决,这是2维索引和B。这就是编译器需要最后一个维度大小来生成代码的原因。类似地,它需要n维数组的最后n-1维度

答案 2 :(得分:2)

本质上,C中的所有数组都是一维的。为了能够通过它的索引访问元素,C现在需要元素的类型。

考虑一维的一维数组。由于C知道int的大小(比如4个字节),因此它知道要访问元素50,它只是将50 * 4 = 200个字节添加到数组的基址。所以它只需要知道基本地址和元素的类型,而不是元素的总数(因为C不会检查超出范围的访问,否则需要整体大小)。

现在二维数组实际上是一维数组,其元素本身就是数组。要访问“外部”数组中的元素,您需要知道它的“类型”,它是某种类型和大小的数组。

考虑声明为int a[100][10]的二维数组。由于C知道“外”数组的类型是10个整数的数组,因此它可以通过向基址添加50 * 4 * 10来计算偏移50处的元素(其本身是数组)的位置。请注意,“内部”数组的大小是查找元素位置所必需的。从那时起,它与前一个示例一样,在请求的int元素的“inner”数组中找到位置。

总的来说,您必须声明除最外层之外的所有维度的大小,以便C能够正确访问该阵列。

答案 3 :(得分:2)

声明void foo(int array[][])违反了C 2011(N1570)6.7.6.2 1,它解决了数组声明,并且部分地说“元素类型不应该是不完整的或函数类型。”因为{{1} }是array数组的数组,其元素类型是int的数组,并且它是不完整的,因为未指定该数组中int的数量。

与其他答案相反,此时编译器需要此数字 。您可以进行int的等效声明。关于此,有两点需要注意:

  • 它没有违反6.7.6.2中的规则,因为它声明void foo(int (*array)[])是指针而不是数组。允许指针指向不完整的类型。
  • 如果允许第一个声明,它实际上与此声明相同,因为6.7.6.3说“参数声明为” type “数组”应调整为“合格指向 type “,...
  • 的指针

但是,使用此声明访问元素的唯一方法是array形式。这是合法的,因为(*array)[i]运算符可以取消引用指向不完整类型的指针,因此*是数组的第一行,然后*array i th 该行的元素。

您无法正确访问数组的其他行,因为这需要(*array)[i]这样的表达式,这需要在array[j][i]上执行指针算法,而指针算术要求指针指向一个对象完整类型(因为,为了让编译器找出超出指向的对象的位置,它必须知道对象有多大,所以它必须有关于它们大小的完整信息)。