以下程序的行为是否未定义?
#include 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 = %dn", *ptr3_plus_1); } else { puts("ptr3_plus_1 != ptr4"); } return 0; }
根据§6.5.6¶8 :
此外,如果表达式P指向数组对象的最后一个元素,则表达式(P)+1指向一个超过数组对象的最后一个元素….如果指针操作数和结果都指向元素相同的数组对象,或者一个超过数组对象的最后一个元素,评估不应产生溢出; 否则,行为未定义。 如果结果指向数组对象的最后一个元素之后,则不应将其用作已计算的一元*运算符的操作数。
由此看来,上述程序的行为似乎未定义; ptr3_plus_1
指向一个地址,该地址超出派生它的数组对象的末尾,并且取消引用此地址会导致未定义的行为。
此外, 附件J.2表明这是未定义的行为:
数组下标超出范围,即使一个对象显然可以使用给定的下标访问(如左边的表达式[1] [7],给出声明int a [4] [5] )(6.5.6)。
Stack Overflow问题中有一些关于这个问题的讨论, 一维访问多维数组:定义明确的C? 。 这里的共识似乎是通过一维下标对二维数组的任意元素的这种访问确实是未定义的行为。
我认为问题在于,形成指针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中的clear语句,试图取消引用一个指向一个数组末尾的位置的指针会导致未定义的行为,并指出指针经过第一行的末尾类型为T [] []的二维数组也是类型为T *的指针,指向类型为T的对象,即类型为T []的数组的第一个元素?
因此,如果
ptr3_plus_1
和ptr4
都是有效的指针,那么它们必须指向相同的地址并且必须指向相同的地址
他们是。
似乎
*ptr3_plus_1
与*ptr4
一样有效。
它不是。
指针是相等的,但不相等。 平等和等价之间区别的一个众所周知的例子是负零:
double a = 0.0, b = -0.0; assert (a == b); assert (1/a != 1/b);
现在,公平地说,两者之间存在差异,因为正零和负零具有不同的表示,典型实现上的ptr3_plus_1
和ptr4
具有相同的表示。 这不保证,并且在它们具有不同表示的实现上,应该清楚您的代码可能会失败。
即使在典型的实现中,虽然有很好的论据可以说相同的表示意味着等价的值,但据我所知,官方解释是标准不能保证这一点,因此程序不能依赖它,因此实现可以假设程序不这样做并相应地进行优化。
调试实现可能使用“胖”指针。 例如,指针可以表示为元组(地址,基数,大小)以检测越界访问。 关于这种表述的标准绝对没有错误或相反。 因此,任何使指针超出[base,base + size]范围的指针算法都会失败,并且[base,base + size]之外的任何解除引用也会失败。
请注意,base和size 不是 2D数组的地址和大小,而是指针指向的数组(在本例中为行)。
在这种情况下听起来可能听起来微不足道,但在决定某个指针构造是否为UB时,通过这个假设的实现精神上运行您的示例是有用的。
以上就是c/c++开发分享是UB访问一个二维数组行结束的元素吗?相关内容,想了解更多C/C++开发(异常处理)及C/C++游戏开发关注计算机技术网(www.ctvol.com)!)。
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/560418.html