我不确定history标记是否相关,但可以随意添加。 我认为原因是历史原因,这就是我建议的原因。
为什么我不能声明如下的函数签名?
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]) {
...
}
答案 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
的等效声明。关于此,有两点需要注意:
void foo(int (*array)[])
是指针而不是数组。允许指针指向不完整的类型。但是,使用此声明访问元素的唯一方法是array
形式。这是合法的,因为(*array)[i]
运算符可以取消引用指向不完整类型的指针,因此*
是数组的第一行,然后*array
是 i th 该行的元素。
您无法正确访问数组的其他行,因为这需要(*array)[i]
这样的表达式,这需要在array[j][i]
上执行指针算法,而指针算术要求指针指向一个对象完整类型(因为,为了让编译器找出超出指向的对象的位置,它必须知道对象有多大,所以它必须有关于它们大小的完整信息)。