C / C ++multidimensional array内部

我有一个关于C / C ++如何在内部存储使用符号foo[m][n]声明的multidimensional array的问题。 我不质疑纯指针指针等…我是问,因为速度的原因…

纠正我,如果我错了,但语法上foo是一个指针数组,它们自己指向一个数组

 int foo[5][4] *(foo + i) // returns a memory address *( *(foo + i) + j) // returns an int 

我从很多地方听说过,C / C ++编译器在后台将foo[m][n]转换为一维数组(用i * width + j计算所需的一维索引)。 但是,如果这是真的,那么以下将成立

 *(foo + 1) // should return element foo[0][1] 

因此,我的问题是: foo[m][n]是否(总是)作为一个一维数组存储在内存中? 如果是这样,为什么上面的代码工作如图所示。

是的,C / C ++将多维(矩形)数组存储为连续的内存区域。 但是,你的语法是不正确的。 要修改元素foo[0][1] ,以下代码将起作用:

 *((int *)foo+1)=5; 

显式转换是必要的,因为foo+1 &foo[1]是完全一样的,与foo[0][1]并不完全相同。 *(foo+1)是指向平坦内存区域中第五个元素的指针。 换句话说, *(foo+1)基本上是foo[1]**(foo+1)foo[1][0] 。 这里是如何为你的二维数组布局内存:

在这里输入图像描述

一个二维数组:

 int foo[5][4]; 

只不过是一个数组的数组:

 typedef int row[4]; /* type "row" is an array of 4 ints */ row foo[5]; /* the object "foo" is an array of 5 rows */ 

这里没有指针对象,无论是显式的还是隐式的。

数组不是指针。 指针不是数组。

常常引起混淆的是,在大多数情况下,数组expression式被隐式转换为指向其第一个元素的指针。 (另外一个规则是,看起来像一个数组参数声明实际上是一个指针声明,但是在这个例子中不适用。)一个数组对象是一个数组对象; 声明这样一个对象不会创build任何指针对象。 引用一个数组对象可以创build一个指针 (数组的第一个元素的地址),但是没有指针对象存储在内存中。

数组对象foo作为5个连续的元素存储在内存中,其中每个元素本身是一个由4个连续的int元素组成的数组; 整个事情因此被存储为20个连续的int对象。

索引操作符是根据指针运算来定义的; x[y]相当于*(x + y) 。 通常,左操作数将是指针expression式数组expression式; 如果它是一个数组expression式,则该数组将隐式转换为一个指针。

所以foo[x][y]相当于*(foo[x] + y) ,这相当于*(*(foo + x) + y) 。 (请注意,不需要强制转换。)幸运的是,您不必这样写, foo[x][y]更容易理解。

请注意,您可以创build一个可以使用相同的foo[x][y]语法访问的数据结构,但是其中foo实际上是指向int指针的指针。 (在这种情况下,每个[]运算符的前缀已经是一个指针expression式,并且不需要被转换)。但是要做到这一点,你必须声明foo作为指针指针 – INT:

 int **foo; 

然后分配和初始化所有必要的内存。 这比int foo[5][4]更灵活,因为您可以dynamic地确定每行的行数和大小(甚至存在)。

comp.lang.c FAQ的第6部分很好地解释了这一点。

编辑:

为了回应Arrakis的评论,重要的是要记住types表示之间的区别。

例如,这两种types:

 struct pair { int x; int y;}; typedef int arr2[2]; 

很可能在内存中有相同的表示forms(连续的两个int对象),但访问这些元素的语法是完全不同的。

同样,typesint[5][4]int[20]具有相同的内存布局(20个连续的int对象),但访问元素的语法不同。

可以((int*)foo)[10] foo[2][2]那样访问foo[2][2] (把二维数组看作是一维数组)。 有时这样做是有用的,但严格来说,行为是不确定的。 你可能会放弃它,因为大多数C实现不做数组边界检查。 另一方面,优化编译器可以假设你的代码的行为是被定义的,如果没有的话就会产生任意的代码。

C数组(甚至是multidimensional array)是连续的,也就是说int [4][5]的数组在结构上等同于int [20]types的数组。

但是,根据C语言的语义,这些types仍然是不相容的。 特别是下面的代码违反了C标准:

 int foo[4][5] = { { 0 } }; int *p = &foo[0][0]; int x = p[12]; // undefined behaviour - can't treat foo as int [20] 

原因是C标准是(可能有意)措辞使得边界检查实现成为可能:由于p是从types为int [5] foo[0]派生的,所以有效索引必须在范围0..5 (如果实际访问元素,则为0..4 )。

许多其他编程语言(Java,Perl,Python,JavaScript等)使用锯齿形数组来实现multidimensional array。 在C中通过使用指针数组也是可能的:

 int *bar[4] = { NULL }; bar[0] = (int [3]){ 0 }; bar[1] = (int [5]){ 1, 2, 3, 4 }; int y = bar[1][2]; // y == 3 

但是,锯齿状的数组不是连续的,指向的数组不需要大小一致。

由于将数组expression式隐式转换为指针expression式,因此索引锯齿形和非锯齿形数组看起来是相同的,但是实际的地址计算将会非常不同:

 &foo[1] == (int (*)[5])((char *)&foo + 1 * sizeof (int [5])) &bar[1] == (int **)((char *)&bar + 1 * sizeof (int *)) &foo[1][2] == (int *)((char *)&foo[1] + 2 * sizeof (int)) == (int *)((char *)&foo + 1 * sizeof (int [5]) + 2 * sizeof (int)) &bar[1][2] == (int *)((char *)bar[1] + 2 * sizeof (int)) // no & before bar! == (int *)((char *)*(int **)((char *)&bar + 1 * sizeof (int *)) + 2 * sizeof (int)) 

int foo[5][4];

foo不是一个指针数组; 它是一个数组的数组。 下图将有所帮助。

在这里输入图像描述