我们知道,当将数组作为函数参数传递时,只有第一维的大小可以为空,其他维必须指定。
void my_function(int arr[5][10][15]); // OKAY!
void my_function(int arr[][10][15]); // OKAY!
void my_function(int arr[][][15]); // WRONG!!!
void my_function(int arr[][][]); // WRONG!!!
这背后的逻辑是什么?有人可以解释主要原因吗?
答案 0 :(得分:8)
将数组传递给函数是一种幻想:使用数组时,数组会立即衰减指向指针。也就是说,在此示例中:
int foo[10];
foo[1];
编译器对此的解释方式,在foo[1]
中,foo
首先转换为指向foo
的元素0的指针,然后下标与{{1 }}。
但是,这仅适用于数组的第一维。假设:
*(pointer_to_foo + 1)
这将25个整数元素连续放置在内存中。它与int foo[5][5];
不同,并且不能转换为int** foo
,因为int** foo
表示一定数量的指针到一定数量的指针,并且它们不必在内存中是连续的
使用数组类型作为函数参数是合法的,但是编译器将其与您指定了指向数组元素类型的指针相同地构建:
int** foo
但是,如果您传递多维数组会发生什么呢?
int foo(int bar[10]); // identical to `int foo(int* bar)`
仅阵列的第一维可以衰减。 int foo(int bar[5][5]); // identical to what?
的等效签名为:
int foo
其中int foo(int (*bar)[5]); // identical to `int foo(int bar[5][5])`
是指向5个整数的数组的指针。堆叠更多的尺寸不会改变想法,只有第一个尺寸会衰减,而其他尺寸则需要具有已知的尺寸。
换句话说,您可以跳过第一维,因为编译器并不关心它的大小,因为它会立即衰减。但是,后续维度不会在调用站点衰减,因此您需要知道它们的大小。
答案 1 :(得分:5)
C和C ++中的数组连续存储在内存中。对于多维数组,这意味着您有一个数组数组。
遍历数组时,不必一定要知道数组中有多少个元素。但是,您确实需要知道数组每个元素的大小,才能跳转到正确的元素。
这就是int arr[][][15]
不正确的原因。它说arr
是类型为int [][15]
的未知大小的数组。但这意味着您不知道每个数组元素的大小,因此您也不知道每个数组元素在内存中的位置。
答案 2 :(得分:1)
从标准的两位开始:
6.3.2.1左值,数组和函数指示符
...
3除非它是sizeof
运算符,_Alignof
运算符或 一元&
运算符,或者是用于初始化数组的字符串文字,该表达式具有 类型“ 类型的数组”转换为类型为“指向类型的指针”的表达式 数组对象的初始元素,不是左值。如果数组对象具有 注册存储类,其行为是不确定的。
...
6.7.6.3函数声明符(包括原型)
...
7将参数声明为“ 类型的数组”声明调整为“ type ”,其中类型限定符(如果有的话)是在“ {em}”的[
和]
中指定的类型限定符 数组类型派生。如果关键字static
也出现在关键字的[
和]
中 数组类型推导,则对于每次调用该函数,对应的值 实参应提供对数组第一个元素的访问,至少要有尽可能多的元素 由size表达式指定的元素。
可爱。这是什么意思?
让我们从声明某个任意类型T
的数组开始:
T arr[N];
表达式 arr
的类型为“ T
的N元素数组”(T [N]
)。除非该表达式是以上6.3.2.1/3中指定的一元&
,sizeof
或_Alignof
运算符的操作数,否则将转换表达式的类型(“衰减” )指向“ T
的指针”(T *
),表达式的值就是第一个元素的地址-即&arr[0]
。
当我们将arr
作为参数传递给函数时,如
foo( arr );
foo
实际收到的是一个指针,而不是一个数组,您可以将函数原型写为
void foo( T *arr )
或...
为方便起见,C允许您使用数组表示法声明形式参数:
void foo( T arr[N] )
或
void foo( T arr[] )
在这种情况下,根据上面的6.7.6.3/7,T arr[N]
和T arr[]
都被“调整”为T *arr
,并且所有三种形式都将arr
声明为指针(对于函数参数声明,仅 为true)。
对于一维阵列,这很容易看到。但是多维数组呢?
让我们将T
替换为数组类型A [M]
。我们的声明现在变成
A arr[N][M]; // we're creating N M-element arrays.
表达式arr
的类型是“ A
的M元素数组的N元素数组”(A [M][N]
)。根据上述规则,此“衰减”类型为pointer to *M-element array* of
A " (
A(*)[M]`)。所以当我们打电话
foo( arr );
相应的原型是
void foo( A (*arr)[M] )1
也可以写为
void foo( A arr[N][M] )
或
void foo( A arr[][M] );
由于T arr[N]
和T arr[]
被调整为T *arr
,因此A arr[N][M]
和A arr[][M]
被调整为A (*arr)[M]
。
让我们将A
替换为另一个数组类型R [O]
:
R arr[N][M][O];
表达式arr
的类型衰减为R (*)[M][O]
,因此foo
的原型可以写为
void foo( R (*arr)[M][O] )
或
void foo( R arr[N][M][O] )
或
void foo( R arr[][M][O] )
您开始看到图案了吗?
当多维数组表达式“衰减”到指针表达式时,只有第一个(最左边的)维“丢失”,因此在函数原型声明中,只有最左边的维可以留空。
[]
的优先级高于一元*
的优先级,因此A *arr[M]
将被解释为“指向A
的指针的M元素数组”,这不是我们想要的。我们必须将*
运算符与标识符进行显式分组,以正确地将其声明为指向数组的指针。