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