为什么在C中展平多维数组是非法的?
我的书(Kenneth Reek的《C上的指针》(Pointers on C))说,以下内容是非法的,尽管它可以正常工作为什么在C中展平多维数组是非法的?,c,C,我的书(Kenneth Reek的《C上的指针》(Pointers on C))说,以下内容是非法的,尽管它可以正常工作 int arr[5][5]; int *p=&arr[2][2]; p=p+3; // As array is stored in row major form I think this //should make p point to arr[3][0] 这本书说把一行留到下一行是违法的。但我不明白为什么。是的。这在C中是非法的。事
int arr[5][5];
int *p=&arr[2][2];
p=p+3; // As array is stored in row major form I think this
//should make p point to arr[3][0]
这本书说把一行留到下一行是违法的。但我不明白为什么。是的。这在C中是非法的。事实上,这样做是在向编译器撒谎
p
指向元素arr[2][2]
(并且是指向int
类型的指针),即第三行的第三个元素。语句p=p+3
将把指针p
增加到arr[2][5]
,这相当于arr[3][0]
但是,每当在某些架构上将内存分配为
2
(2
n
)的幂时,这将失败。现在,在这种情况下,内存分配将取整为2
n
,也就是说,在您的情况下,每行将取整为64
字节。请参阅一个测试程序,其中分配的内存为10个整数的5次分配。在某些机器上,内存分配是16字节的倍数,因此每次分配请求的40字节被舍入为48字节:
#include <stdio.h>
#include <stdlib.h>
extern void print_numbers(int *num_ptr, int n, int m);
extern void print_numbers2(int **nums, int n, int m);
int main(void)
{
int **nums;
int n = 5;
int m = 10;
int count = 0;
// Allocate rows
nums = (int **)malloc(n * sizeof(int *));
// Allocate columns for each row
for (int i = 0; i < n; i++)
{
nums[i] = (int *)malloc(m * sizeof(int));
printf("%2d: %p\n", i, (void *)nums[i]);
}
// Populate table
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
nums[i][j] = ++count;
// Print table
puts("print_numbers:");
print_numbers(&nums[0][0], n, m);
puts("print_numbers2:");
print_numbers2(nums, n, m);
return 0;
}
void print_numbers(int *nums_ptr, int n, int m)
{
int (*nums)[m] = (int (*)[m])nums_ptr;
for (int i = 0; i < n; i++)
{
printf("%2d: %p\n", i, (void *)nums[i]);
for (int j = 0; j < m; j++)
{
printf("%3d", nums[i][j]);
}
printf("\n");
}
}
void print_numbers2(int **nums, int n, int m)
{
for (int i = 0; i < n; i++)
{
printf("%2d: %p\n", i, (void *)nums[i]);
for (int j = 0; j < m; j++)
printf("%3d", nums[i][j]);
printf("\n");
}
}
Win7上的样本输出;通用条款4.8.1:
这本书之所以说它是非法的,是因为指针算法保证只对指向同一数组中元素的指针有效,或者只对超过末尾的元素有效
arr
是由5个元素组成的数组,其中每个元素都是由5个整数组成的数组。因此,从理论上讲,如果希望在arr[i]
中有指向数组元素的指针,则只能执行指针算术,生成&arr[i][0..4]
或arr[i]+5
范围内的指针,保持i
恒定
例如,假设arr是一个由5个整数组成的一维。然后一个指针p
只能指向&arr[0..4]
或arr+5
(一个在末尾)。多维数组也是如此
带int-arr[5][5]
,您只能执行指针算术,这样您的指针总是在&arr[i][0..4]
或arr[i]+5
范围内-这就是规则所说的。这可能会让人困惑,因为这些是数组中的数组,但无论发生什么,规则都是一样的。从概念上讲,arr[0]
和arr[1]
是不同的数组,即使您知道它们在内存中是连续的,在arr[0]
和arr[1]
的元素之间执行指针算术也是非法的。请记住,从概念上讲,arr[i]
中的每个元素都是不同的数组
但是,在您的示例中,p+3
将指向一个超出arr[2][2]
末尾的位置,因此在我看来它仍然有效。这是一个糟糕的示例选择,因为它会使p
精确地指向结束后的一个,使其仍然有效。如果作者选择了p+4
,那么这个例子将是正确的
不管怎样,我从来没有遇到过使用类似方法在C中展平多维数组的问题
还可以看到这个问题,它还有其他有用的信息:我对这个问题思考了一会儿,我会尽力解释我认为他是从哪里来的,尽管没有读这本书,充其量只是猜测 首先,从技术上讲,你提议(或他提议)的增量并不违法;取消对它的引用。该标准允许您将指针前进到数组序列的最后一个元素的前面,该数组序列的最后一个元素是用来求值的,但不用于解引用。将其更改为
p=p+4
,两者都是非法的
除此之外,阵列的线性封装外形不承受,ar[2]
有一个类型,它是int[5]
。如果你不相信,考虑下面的,所有这些都是正确类型的:
int ar[5][5];
int (*sub)[5] = ar+2; // sub points to 3rd row
int *col = *sub + 2; // col points to 3rd column of third row.
int *p = col + 3; // p points to 5th colum of third row.
这是否落在ar[3][0]
上并不重要,因为您超出了参与指针数学的维度的声明大小。结果在法律上不能取消引用,如果它大于3个偏移量,甚至也不能进行法律评估
记住,要寻址的数组是ar[2]
;不仅是ar
,而且声明相同的大小为5。它与其他两个相同类型的数组相对应,这与当前正在进行的寻址无关。我相信,作为重复提出的问题本应被选为彻底解决的问题。特别是,对C99§6.5.6,p8的引用,虽然冗长,但如下所示:
将具有整数类型的表达式添加到或减去时
对于指针,结果具有指针操作数的类型。如果
指针操作数指向数组对象的元素,数组
如果足够大,则结果将指向与
原始元素,例如
结果和原始数组元素等于整数表达式。
换句话说,如果表达式P指向
数组对象,表达式(P)+N(等价地,N+(P))和(P)-N
(其中N的值为N)分别指向i+N和
我−数组对象的第n个元素,,前提是它们存在。此外,如果
表达式P指向数组对象的最后一个元素
表达式(P)+1点超过数组对象的最后一个元素,
如果表达式Q指向数组最后一个元素的前面一个
对象,表达式(Q)-1指向数组的最后一个元素
对象如果指针操作数和结果都指向元素
同一数组对象的,或超过数组最后一个元素的
对象时,评估不应产生溢出否则
行为未定义。如果结果指向最后一个元素上方的一个
对于数组对象,不应将其用作
int ar[5][5];
int (*sub)[5] = ar+2; // sub points to 3rd row
int *col = *sub + 2; // col points to 3rd column of third row.
int *p = col + 3; // p points to 5th colum of third row.