将多维数组传递给C中的函数

我有这样的function:

void myfunc(int** arr, int n) { int i, j; for(i=0; i<n; ++i) { for(j=0; j<n; ++j) { printf("%d,", *(arr + i*n + j) ); // Print numbers with commas } printf("\n"); // Just breakline } } 

在其他函数中,我有一个像这样的二维数组:

 int main() { int seqs[8][8] = { {0, 32, 36, 52, 48, 16, 20, 4}, {0, 16, 20, 52, 48, 32, 36, 4}, {0, 32, 36, 44, 40, 8, 12, 4}, {0, 8, 12, 44, 40, 32, 36, 4}, {0, 32, 36, 38, 34, 2, 6, 4}, {0, 2, 6, 38, 34, 32, 36, 4}, {0, 32, 36, 37, 33, 1, 5, 4}, {0, 1, 5, 37, 33, 32, 36, 4} }; // Call to myfunc myfunc(seqs, 8); // This is the idea } 

但编译器抛出这个错误:

 lab.c: In function 'main': lab.c:75:5: warning: passing argument 1 of 'myfunc' from incompatible pointer type [enabled by default] lab.c:4:6: note: expected 'int **' but argument is of type 'int (*)[8]' 

将此数组( seqs )传递给函数( myfunc )的正确方法是什么?

在C99或C11中,你会这样做:

 void myfunc(int n, int arr[n][n]) { for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) printf("%d,", arr[i][j]); printf("\n"); } } 

请注意,大小在数组之前,而不是在数组之后。 此function可以正常使用:

 int main(void) { int seqs[8][8] = { { 0, 32, 36, 52, 48, 16, 20, 4 }, { 0, 16, 20, 52, 48, 32, 36, 4 }, { 0, 32, 36, 44, 40, 8, 12, 4 }, { 0, 8, 12, 44, 40, 32, 36, 4 }, { 0, 32, 36, 38, 34, 2, 6, 4 }, { 0, 2, 6, 38, 34, 32, 36, 4 }, { 0, 32, 36, 37, 33, 1, 5, 4 }, { 0, 1, 5, 37, 33, 32, 36, 4 }, }; myfunc(8, seqs); int matrix3x3[3][3] = { { 1, 2, 3 }, { 2, 4, 6 }, { 3, 6, 9 } }; myfunc(3, matrix3x3); } 

有人问我:

你的例子确实看起来确实好多了,但它是否定义明确? 是否真的保证在int arr[n][n]之前进行评估? function参数的评估顺序不是未指定的行为吗?

旧标准(ISO / IEC 9899:1999)在§6.7.5.2* 数组声明符*中说:

¶5 如果size是一个不是整数常量表达式的表达式:如果它出现在函数原型范围的声明中,则将其视为*被替换为* ; 否则,每次评估它时,其值应大于零。 变长数组类型的每个实例的大小在其生命周期内不会改变。 如果size表达式是sizeof运算符的操作数的一部分,并且更改size表达式的值不会影响运算符的结果,则无法指定是否计算size表达式。

它给出了一个例子(它是非规范性文本,因为它是一个例子,但强烈表明了预期的内容):

示例4可变修改(VM)类型的所有声明必须在块范围或函数原型范围内。 使用staticextern存储类说明符声明的数组对象不能具有可变长度数组(VLA)类型。 但是,使用static存储类说明符声明的对象可以具有VM类型(即指向VLA类型的指针)。 最后,使用VM类型声明的所有标识符必须是普通标识符,因此不能是结构或联合的成员。

 extern int n; int A[n]; // invalid: file scope VLA extern int (*p2)[n]; // invalid: file scope VM int B[100]; // valid: file scope but not VM void fvla(int m, int C[m][m]); // valid: VLA with prototype scope void fvla(int m, int C[m][m]) // valid: adjusted to auto pointer to VLA { typedef int VLA[m][m]; // valid: block scope typedef VLA struct tag { int (*y)[n]; // invalid: y not ordinary identifier int z[n]; // invalid: z not ordinary identifier }; int D[m]; // valid: auto VLA static int E[m]; // invalid: static block scope VLA extern int F[m]; // invalid: F has linkage and is VLA int (*s)[m]; // valid: auto pointer to VLA extern int (*r)[m]; // invalid: r has linkage and points to VLA static int (*q)[m] = &B; // valid: q is a static block pointer to VLA } 

还有其他示例显示可变修改的函数参数。

此外,在§6.9.10 函数定义中 ,它说:

¶10进入函数时,将评估每个可变修改参数的大小表达式,并将每个参数表达式的值转换为相应参数的类型,就像通过赋值一样。 (数组表达式和函数指示符作为参数在调用之前转换为指针。)

数组和指针不一样。 同样,二维数组和指针指针也不是一回事。 seqs的类型不匹配函数参数,可以将myfunc的签名修改为:

 void myfunc(int arr[][8], int n) 

或者,您可以将seqs修改为指向指针的实际指针。

您将int**声明为具有静态大小的数组,因此其签名为int *[8] 。 调用myfunc时,可以通过强制转换数组来消除编译器错误。

 myfunc((int **)seqs, 8); 

在实际程序中,这样的数组很可能是动态生成的,您不必这样做。

你从函数中的数组中访问值的方式,会给你一个简单的int *而不是int **的正确元素。 用*(arr + i*n + j)i*n会将您带到正确的行,而j会将您带到列中。 因此,将函数标题行更改为:

 void myfunc(int* arr, int n) { 

…(带一个’*’),并传递第一个元素的地址而不是裸数组名称,如下所示:

 myfunc(&seqs[0][0], 8); 

..或第一行:

 myfunc(seqs[0], 8); 

将二维数组定义为

 int seqs[8][8]; 

seqs的类型是8个elements数组,其中每个elementarray of 8 integers的类型array of 8 integers 。 当你将seqs传递给myfuncseqs被隐式转换为指向其第一个元素的指针(这是将数组传递给函数时会发生的情况),即键入* elementelement的类型为int [8] 。 因此, seqs被隐式转换为int *[8]类型 – 指向8个整数数组的指针。

在你的函数myfunc ,参数arr的类型明显是与你在main调用它时传递的类型不同的类型。 它们是不同的类型,具有不同的指针算法。 编译器抱怨结果。

您应该将arr的类型更改为int *[8]

 void myfunc(int arr[][8], int n); 

arr不是这里的数组,而是指向8个整数数组的指针,你也可以将myfunc声明为

 void myfunc(int *arr[8], int n); 

它们完全一样。

编辑

关于你在这里的评论,你不能做以下事情,并期望事情正常工作。

 // in main myfunc((int **)seqs, 8); // the above call is essentially doing this int **arr = (int **)seqs; 

seqsarr是不兼容的类型和类型转换seqs抑制编译器发出的警告,这更糟糕,因为类型转换不能纠正事情,并使它看起来好像没事。

让我们看看为什么。 (在函数调用之后。)

 seqs (type: array of 8 int[8] type; value: address of the location seqs[0][0]) *seqs (type: int[8] type, value: address of the location seqs[0][0]) arr (type: pointer to a pointer to an int; value: address of the location seqs[0][0]) *arr (type: pointer to an int; value: seqs[0][0]) **arr (type: int; value: value at the address seqs[0][0]) 

可以使用assert宏检查上述assert 。 所以我们看到,当我们做什么时,我们实际上在做的是将值seqs[0][0]作为内存地址并尝试访问该位置的值。 这显然是错误的! 它可能导致未定义的行为或最可能的分段错误。

没有办法使seqs的值(它在arr的初始化中计算的值)即使通过类型转换也像int **一样。 这也表明我们应该小心使用类型转换值,除非我们知道并确定我们正在做什么,否则不应该这样做。

因此,您必须更改myfunc函数签名以执行所需操作。