为啥我们在传递二维数组作为参数时需要指定列大小?

Posted

技术标签:

【中文标题】为啥我们在传递二维数组作为参数时需要指定列大小?【英文标题】:Why do we need to specify the column size when passing a 2D array as a parameter?为什么我们在传递二维数组作为参数时需要指定列大小? 【发布时间】:2012-09-30 13:53:47 【问题描述】:

为什么我的参数不能是

void example(int Array[][]) /*statements*/

为什么我需要指定数组的列大小?比如说,3

void example(int Array[][3])/*statements*/

我的教授说它是强制性的,但我在开学前就在编码,我记得当我把它作为参数时没有语法或语义错误?还是我错过了什么?

【问题讨论】:

需要计算您实际访问的项目的相对偏移量。数组本身只是一个线性块,而不是你必须做 (row*colwidth + col) (这有效,顺便说一句),一旦编译器知道二阶数量级,它就会为你完成偏移量。跨度> 但是如果我不声明偏移量(col 大小),编译器会默认设置什么? 要么你当时没有使用 2+d 数组,要么不是 C。 【参考方案1】:

在描述参数时,数组总是衰减为指向其第一个元素的指针。

当您将声明为int Array[3] 的数组传递给函数void foo(int array[]) 时,它会衰减为指向数组开头的指针,即int *Array;。顺便说一句,您可以将参数描述为int array[3]int array[6] 甚至int *array - 所有这些都是等效的,您可以毫无问题地传递任何整数数组。

在数组数组(二维数组)的情况下,它也衰减为指向其第一个元素的指针,这恰好是一维数组,即我们得到int (*Array)[3]

在此处指定大小很重要。例如,如果它不是强制性的,那么编译器将无法知道如何处理表达式Array[2][1]

要取消引用编译器需要计算我们需要的项目在连续内存块中的偏移量(int Array[2][3] 是一个连续的整数块),这对于指针来说应该很容易。如果a 是指针,则a[N] 扩展为start_address_in_a + N * size_of_item_being_pointed_by_a。在函数内部的表达式Array[2][1](我们要访问此元素)中,Array 是指向一维数组的指针,并且适用相同的公式。查找size_of_item_being_pointed_by_a 需要最后一个方括号中的字节数。如果我们只有Array[][],就不可能找到它,因此不可能取消引用我们需要的数组元素。

如果没有大小,指针算法将不适用于数组数组。 Array + 2 会产生什么地址:将地址提前Array 2 个字节(错误)或将指针提前3* sizeof(int) * 2 字节?

【讨论】:

"(注意,指针数组而不是它的第一个元素)" 这没有意义或不清楚。当数组衰减为指针时,它总是成为指向其第一个元素的指针。二维数组的第一个元素是一维数组;因此二维数组衰减为指向一维数组的指针。 int (*)[3] 类型是指向数组的指针的类型。 谢谢,你说得对,听起来很不清楚。我已将其更正为更全面,我希望。 @EricPostpischil 是的,我记得之前把它搞混了……这确实是因为你不能声明一个不完整的数组类型。旧评论已删除。 这个答案没有给出 int Array[][] 不被接受为参数声明的原因。它正确地指出,如果没有第二维,Array[i][j] 无法在函数内部求值,因为需要第二维的长度来计算Array[i][j] 的位置。但这并不排除以这种方式声明参数——我们可以将参数声明为int (*Array)[],编译器会接受这一点,即使我们仍然无法在函数内部评估Array[i][j]…… …The actual reason is a rule in the standard that the element type for an array declarator must be a complete type.【参考方案2】:

在 C/C++ 中,即使是 2-D 数组也是按顺序存储的,在内存中一行接一行。所以,当你有(在一个单一的功能):

int a[5][3];
int *head;

head = &a[0][0];
a[2][1] = 2; // <--

您实际使用a[2][1] 访问的元素是*(head + 2*3 + 1),因为依次,该元素位于0 行的3 个元素和1 行的3 个元素之后,然后再进一步索引一个.

如果你声明这样的函数:

void some_function(int array[][]) ...

从语法上讲,它不应该是一个错误。但是,当您现在尝试访问array[2][3] 时,您无法判断应该访问哪个元素。另一方面,当你有:

void some_function(int array[][5]) ...

你知道array[2][3],可以确定你实际上是在访问内存地址*(&amp;array[0][0] + 2*5 + 3)处的元素因为函数知道第二维的大小。

还有另一个选项,如前所述,您可以声明如下函数:

void some_function(int *array, int cols)  ... 

因为这样,您调用函数时使用的“信息”与以前相同——列数。你访问数组元素的方式有点不同:你必须在通常写array[i][j]的地方写*(array + i*cols + j),因为array现在是一个指向整数的指针(而不是指针)。

当您声明这样的函数时,您必须小心地使用实际为数组声明的列数调用它,而不仅仅是使用。所以,例如:

int main()
   int a[5][5];
   int i, j;

   for (i = 0; i < 3; ++i)
       for (int j=0; j < 3; ++j)
           scanf("%d", &a[i][j]);
       
   

   some_function(&a[i][j], 5); // <- correct
   some_function(&a[i][j], 3); // <- wrong

   return 0;

【讨论】:

但是编译器不知道数组的列数吗?当我尝试这个时:#define COL 5int an_array[][COL] = 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 ;int test(int (*arr)[1]) return 0;int main() test(an_array); 我收到一条错误消息,告诉我传递给函数的数组的列数与声明中指定的列数不匹配测试()。【参考方案3】:

C 2018 6.7.6.2 规定了数组声明符的语义,第 1 段对它们给出了约束,包括:

元素类型不能是不完整类型或函数类型。

在诸如void example(int Array[][]) 的函数声明中,Array[] 是一个数组声明符。所以它必须满足其元素类型不能不完整的约束。它在该声明中的元素类型是int [],由于未指定大小,因此不完整。

没有根本原因 C 标准不能删除对即将调整为指针的参数的约束。生成的类型int (*Array)[] 是一个合法声明,被编译器接受,并且可以以(*Array)[j] 的形式使用。

然而,声明int Array[][] 表明Array 至少与二维数组相关联,因此应以Array[i][j] 的形式使用。即使声明int Array[][]被接受并调整为int (*Array)[],也无法将其用作Array[i][j],因为下标运算符要求其指针操作数是指向完整类型的指针,而这一要求是不可避免的因为需要计算元素的地址。因此,保持对数组声明符的约束是有意义的,因为它与预期的表达式一致,即参数将是一个二维数组,而不仅仅是指向一个一维数组的指针。

【讨论】:

【参考方案4】:

实际上无论是二维数组还是一维数组,它都以一行的形式存储在内存中。所以说编译器应该在哪里断行,指示下一个数字在我们应该在下一行中提供列大小。并且适当地打破行将给出行的大小。

我们来看一个例子:

int a[][3]= 1,2,3,4,5,6,7,8,9,0 ;

这个数组a在内存中存储为:

  1  2  3  4  5  6  7  8  9  0

但由于我们将列大小指定为 3,内存会在每 3 个数字后拆分。

#include<stdio.h>

int main() 
   int a[][3]=1,2,3,4,5,6,i,j;
   for(i=0;i<2;i++)
   
       for(j=0;j<3;j++)
       
           printf("%d  ",a[i][j]);
       
       printf("\n");
   


输出:

 1  2  3  
 4  5  6  

在另一种情况下,

int a[3][]=1,2,3,4,5,6,7,8,9,0;

编译器只知道有3行,但不知道每行的元素个数,所以无法分配内存,会报错。

#include<stdio.h>

int main() 
   int a[3][]=1,2,3,4,5,6,i,j;
   for(i=0;i<3;i++)
   
       for(j=0;j<2;j++)
       
           printf("%d  ",a[i][j]);
       
       printf("\n");
   


输出:

 c: In function 'main':
    c:4:8: error: array type has incomplete element type 'int[]'
    int a[3][]=1,2,3,4,5,6,i,j;
        ^

【讨论】:

【参考方案5】:

关于这个有一个类似的帖子。你可以参考下面的链接。 Creating Array in C and passing pointer to said array to function 希望对您有所帮助。

另一方面,编译器需要第二维,以便将“数组”从一个指针移动到下一个指针,因为整个内存以线性方式排列

【讨论】:

谢谢你!虽然当我搜索传递二维数组时它没有出现【参考方案6】:

当您在内存中创建二维数组anytype a[3][4] 时,您实际创建的是3 4 anytype 对象的连续块。

a[0][0] a[0][1] a[0][2] a[0][3] a[1][0] a[1][1] a[1][2] a[1][3] a[2][0] a[2][1] a[2][2] a[2][3]

现在下一个问题是,为什么会这样?因为,与语言的规范和结构保持一致,anytype a[3][4] 实际上扩展为anytype (*a)[4],因为数组衰减为指针。事实上,它也扩展到anytype (*(*a)),但是,您现在已经完全失去了二维数组的大小。所以,你必须帮助编译器。

如果您向程序询问a[2],程序可以按照与一维数组完全相同的步骤进行操作。它可以简单地返回the 3rd element of sizeof(object pointed to),这里指向的对象是大小为4的anytype对象。

【讨论】:

因为数组只是指针”——你需要小心地做出全局声明。 char a[x]char *b 不同。虽然当数组作为函数参数传递时,该语句可能适用于第一级间接,但它不适用于范围内的a——它是一个数组。 @DavidC.Rankin 你说得对,我稍微修改了措辞以使其更准确,但仍然没有深入细节。 " 事实上,这也扩展为 anytype (*(*a))" 这仍然不正确。它将扩展为anytype (*a)[],它是一个指向不完整类型数组的指针。

以上是关于为啥我们在传递二维数组作为参数时需要指定列大小?的主要内容,如果未能解决你的问题,请参考以下文章

c语言中怎么用二维数组作为函数参数

C语言函数传递二维数组

opencl核函数怎么传二维数组实参

如何向 Shader 传递一个巨大的数组

如何将二维数组类型 char(字符串)作为函数参数传递?

java 如何将二维数组的一列作为参数传进去 求代码