将多维数组作为函数参数传递

时间:2018-08-31 18:03:12

标签: c arrays

我们知道,当将数组作为函数参数传递时,只有第一维的大小可以为空,其他维必须指定。

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!!!

这背后的逻辑是什么?有人可以解释主要原因吗?

3 个答案:

答案 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表达式指定的元素。

C 2011 online draft

可爱。这是什么意思?

让我们从声明某个任意类型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] )

您开始看到图案了吗?

当多维数组表达式“衰减”到指针表达式时,只有第一个(最左边的)维“丢失”,因此在函数原型声明中,只有最左边的维可以留空。


  1. 由于下标[]的优先级高于一元*的优先级,因此A *arr[M]将被解释为“指向A的指针的M元素数组”,这不是我们想要的。我们必须将*运算符与标识符进行显式分组,以正确地将其声明为指向数组的指针。