以下程序的行为是否未定义?
#include <stdio.h>
int main(void)
{
int arr[2][3] = { { 1, 2, 3 },
{ 4, 5, 6 }
};
int *ptr1 = &arr[0][0]; // pointer to first elem of { 1, 2, 3 }
int *ptr3 = ptr1 + 2; // pointer to last elem of { 1, 2, 3 }
int *ptr3_plus_1 = ptr3 + 1; // pointer to one past last elem of { 1, 2, 3 }
int *ptr4 = &arr[1][0]; // pointer to first elem of { 4, 5, 6 }
// int *ptr_3_plus_2 = ptr3 + 2; // this is not legal
/* It is legal to compare ptr3_plus_1 and ptr4 */
if (ptr3_plus_1 == ptr4) {
puts("ptr3_plus_1 == ptr4");
/* ptr3_plus_1 is a valid address, but is it legal to dereference it? */
printf("*ptr3_plus_1 = %d\n", *ptr3_plus_1);
} else {
puts("ptr3_plus_1 != ptr4");
}
return 0;
}
根据§6.5.6 ¶8:
此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向数组对象的最后一个元素之后的位置...如果指针操作数和结果都指向同一数组对象或者指向数组对象的最后一个元素之后的位置,则评估不会产生溢出;否则,行为是未定义的。如果结果指向数组对象的最后一个元素之后的位置,则不能将其用作评估的一元*运算符的操作数。 由此,似乎上述程序的行为是未定义的;
ptr3_plus_1
指向从中派生的数组对象的末尾之后的地址,并对此地址进行解引用会导致未定义的行为。进一步地,Annex J.2 暗示这是未定义行为:
在 Stack Overflow 的问题 One-dimensional access to a multidimensional array: well-defined C? 中有关于这个问题的一些讨论。在这里的共识似乎是通过一维下标访问二维数组的任意元素的这种方式确实是未定义行为。即使一个对象在给定下标的情况下似乎是可访问的(例如,在声明 int a[4][5] 的情况下给出左值表达式a[1][7]),数组下标越界也是不合法的 (6.5.6)。
问题在于,我认为这样形成指针
ptr3_plus_2
的地址甚至都是不合法的,所以用这种方式访问任意二维数组元素也是不合法的。但是,使用指针算术形成指针ptr3_plus_1
的地址是合法的。此外,根据§6.5.9 ¶6,比较这两个指针ptr3_plus_1
和ptr4
是合法的:
因此,如果如果两个指针都是空指针、都是指向同一对象(包括指向对象及其开头的子对象的指针)或函数的指针、都是指向同一数组对象的最后一个元素的指针,或者一个是指向一个数组对象结束之后的位置,另一个是指向紧随第一个数组对象后面的另一个不同数组对象的开始位置,则两个指针相等。
ptr3_plus_1
和ptr4
都是有效的指针,比较相等且必须指向同一地址(无论如何,ptr4
指向的对象必须与ptr3
指向的对象在内存中相邻,因为数组存储必须是连续的),那么看起来*ptr3_plus_1
和*ptr4
一样有效。
这是否是未定义的行为,如§6.5.6 ¶8和附录J.2所述,还是一个特殊情况?
澄清
尝试访问二维数组的最后一行之外的元素是未定义行为,这似乎是明确的。我感兴趣的问题是,是否可以通过使用前一行的元素的指针和指针算术来形成新指针来访问中间行的第一个元素。对我来说,附录J.2中的另一个示例可能更清楚地说明了这一点。
在第6.5.6节第8段中明确指出,尝试对指向数组结尾后一个位置的指针进行解引用会导致未定义行为,那么是否可能将类型为T[][]的二维数组第一行结尾后的指针作为T *类型的指针,并将其指向T[]类型的数组的第一个元素,也就是T类型的对象呢?
ptr3_plus_1
是一个有效的指针,它指向数组的末尾,因此不能被解引用;但是ptr3_plus_1
也是一个有效的指针,它指向数组的第一个元素,因此应该可以被解引用。我正在努力协调我认为存在的明显矛盾。也许答案是ptr3_plus_1
不能被说成是指向第二个数组的第一个元素的指针。 - ad absurdum