从最严格的意义上讲,这不是未定义行为,而是实现定义的。因此,尽管不建议您计划支持非主流体系结构,但是您可以做到。
interjay给出的标准引号是一个很好的表示UB,但是在我看来,它只是第二好命,因为它处理指针-指针算术(有趣的是,一个显式是UB,而另一个则不是UB)。有一段直接涉及问题中的操作:
[... expr.post.incr] / [expr.pre.incr]
操作数应为指向完全定义的对象类型的指针。
哦,等等,一个完全定义的对象类型?就这样?我的意思是,真的,键入?因此,您根本不需要任何对象?
需要花费大量的阅读才能真正发现其中的定义可能不够明确。因为到目前为止,它看起来像是完全允许您这样做,没有任何限制。
[basic.compound] 3
声明一个指针可能具有什么类型,而不是其他三个指针,您的操作结果显然将归入3.4:无效指针。
但是,它并没有说不允许您使用无效的指针。相反,它列出了一些非常常见的正常情况(例如存储持续时间结束),在这些情况下指针经常变为无效。因此,这显然是可以允许的事情。确实:
[basic.stc] 4
通过无效的指针值进行的间接传递以及将无效的指针值传递给释放函数均具有未定义的行为。无效指针值的任何其他使用都具有实现定义的行为。
我们在此处执行“其他”操作,因此它不是未定义行为,而是实现定义的,因此通常是允许的(除非实现明确表示不同)。
不幸的是,这还不是故事的结局。尽管最终结果从现在开始没有任何变化,但是随着您搜索“指针”的时间延长,结果变得更加混乱:
[basic.compound]
对象指针类型的有效值表示内存中字节的地址或空指针。如果类型T的对象位于地址A,则无论该值如何获得,都称其指向该对象。
[注意:例如,将数组末尾的地址视为指向该数组元素类型的不相关对象,该对象可能位于该地址。[...]。
读为:好的,谁在乎!只要指针指向内存中的某个位置,我就好吗?
[basic.stc.dynamic.safety]指针值是安全派生的指针[blah blah]
读取为:可以,安全派生。它没有解释这是什么,也没有说我真正需要它。安全地得出结论。显然,我仍然可以拥有非安全派生的指针。我猜想取消引用它们可能不是一个好主意,但是完全可以允许它们。它没有说其他。
一个实现可能具有宽松的指针安全性,在这种情况下,指针值的有效性不取决于它是否是安全得出的指针值。
哦,所以没关系,只是我的想法。但是等等...“可能不会”吗?这意味着,也可能如此。我怎么知道?
备选地,实现可以具有严格的指针安全性,在这种情况下,不是安全导出的指针值的指针值是无效的指针值,除非所引用的完整对象具有动态存储持续时间并且先前已声明为可到达
等待,所以甚至有可能需要调用declare_reachable()
每个指针吗?我怎么知道?
现在,您可以将转换为intptr_t
,它定义明确,给出了安全派生指针的整数表示形式。当然,对于该整数,它是完全合法的,并且定义明确,可以根据需要对其进行递增。
是的,您可以将intptr_t
back 转换为指针,该指针也定义明确。只是,不是原始值,不再保证您有一个安全派生的指针(显然)。总而言之,就标准而言,虽然是实现定义的,但这是100%合法的事情:
[expr.reinterpret.cast] 5
可以将整数类型或枚举类型的值显式转换为指针。指针转换为足够大小的整数并返回相同的指针类型原始值;指针和整数之间的映射否则由实现定义。
抓住
指针只是普通的整数,只有您碰巧将它们用作指针。哦,如果那是真的!
不幸的是,存在一些根本不正确的体系结构,仅生成无效的指针(不取消引用,仅将其放在指针寄存器中)会导致陷阱。
这就是“实现定义”的基础。也就是说,而且增加一个指针,只要你想,事实,请你能当然原因溢出,该标准没有要处理的。应用程序地址空间的末尾可能与溢出的位置不一致,甚至您甚至都不知道特定体系结构上的指针是否存在诸如溢出之类的问题。总而言之,这是一场噩梦般的混乱,与可能的收益没有任何关系。
另一方面,处理一个过去的对象条件很容易:实现必须简单地确保没有对象被分配,以便地址空间中的最后一个字节被占用。因此,这是明确定义的,因为它可以保证有用且微不足道。