为什么在C中展平多维数组是非法的?

我的书(Kenneth Reek的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] 

该书说,将一行留到下一行是非法的。 但我无法理解为什么。

我在这上面写了一段时间,我会尽力解释我认为他来自哪里,虽然没有读过这本书,但这将是最好的猜想。

首先,从技术上讲,你提出(或他提议)的增量并非违法; 解除引用它是。 该标准允许您将指针前进到数组序列的最后一个元素,从中获取它以进行评估,但不是用于取消引用。 将其更改为p = p + 4两者都是非法的。

除此之外,arrays的线性足迹不能承受, 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 ,并且说 – 同样被宣布为size = 5。 它与其他两个同类arrays的支持与当前正在进行的寻址无关。 我相信克里斯托夫对提出的重复问题的回答应该是选择用于彻底解决的问题。 特别是,对C99§6.5.6,p8的引用虽然冗长,但如下所示:

当一个具有整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。 如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果元素和原始数组元素的下标的差异等于整数表达式。 换句话说,如果表达式P指向数组对象的第i个元素,则表达式(P)+ N(等效地,N +(P))和(P)-N(其中N具有值n)指向分别为数组对象的第i + n和第i-n个元素, 只要它们存在 。 此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素,如果表达式Q指向一个超过数组对象的最后一个元素,表达式(Q)-1指向数组对象的最后一个元素。 如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。 如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数。

对于垃圾邮件很抱歉,但我认为粗体突出显示与您的问题相关。 通过按原样进行寻址, 您将离开arrays被寻址 ,并因此进入UB。 简而言之,它(通常)有效,但不合法。

这本书说它是非法的原因是因为指针算法只能用于指向同一数组中元素的指针,或者只能指向结尾的元素。

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中展平多维数组的问题。

另外看到这个问题,它还有其他有用的信息: 对多维数组的一维访问:定义明确的C?

是。 它在C中是非法的。事实上,这样做是为了让你的编译器。 p指向元素arr[2][2] (并且是指向int类型的指针),即第三行的第3个元素。 陈述p=p+3; 将指针p递增到arr[2][5] ,这相当于arr[3][0]
但是,只要内存在某些架构上被分配为22 n )的幂,这就会失败。 现在在这种情况下,内存分配将向上舍入到2 n ,即,在您的情况下,每行最多可以舍入到64个字节。
查看一个测试程序,其中分配的内存是5个10个整数的分配。 在某些机器上,内存分配是16个字节的倍数,因此请求的40个字节被舍入到每个分配48个字节:

 #include  #include  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"); } } 

Mac OS X 10.8.5上的示例输出; GCC 4.8.1:

  0: 0x7f83a0403a50 1: 0x7f83a0403a80 2: 0x7f83a0403ab0 3: 0x7f83a0403ae0 4: 0x7f83a0403b10 print_numbers: 0: 0x7f83a0403a50 1 2 3 4 5 6 7 8 9 10 1: 0x7f83a0403a78 0 0 11 12 13 14 15 16 17 18 2: 0x7f83a0403aa0 19 20 0 0 21 22 23 24 25 26 3: 0x7f83a0403ac8 27 28 29 30 0 0 31 32 33 34 4: 0x7f83a0403af0 35 36 37 38 39 40 0 0 41 42 print_numbers2: 0: 0x7f83a0403a50 1 2 3 4 5 6 7 8 9 10 1: 0x7f83a0403a80 11 12 13 14 15 16 17 18 19 20 2: 0x7f83a0403ab0 21 22 23 24 25 26 27 28 29 30 3: 0x7f83a0403ae0 31 32 33 34 35 36 37 38 39 40 4: 0x7f83a0403b10 41 42 43 44 45 46 47 48 49 50 

Win7上的示例输出; GCC 4.8.1:

在此处输入图像描述