递增指向0大小的动态数组的指针是否未定义? 渔获物

AFAIK,虽然我们无法创建大小为0的静态内存数组,但可以使用动态数组来做到这一点:

int a[0]{}; // Compile-time error
int* p = new int[0]; // Is well-defined

如我所读,p的作用就像一个过去的元素。我可以打印p指向的地址。

if(p)
    cout << p << endl;
  • 尽管我确定我们不能像使用迭代器(过去元素)那样取消引用该指针(过去元素),但是我不确定的是是否要递增该指针{{1 }}?是否像迭代器一样具有未定义的行为(UB)?

    p
zwlyuan 回答:递增指向0大小的动态数组的指针是否未定义? 渔获物

允许指向数组元素的指针指向有效元素,或者指向末尾的元素。如果您以超过末尾一个以上的方式递增指针,则行为是不确定的。

对于大小为0的数组,p已经指向末尾一个,因此不允许对其进行递增。

有关+运算符,请参见C ++ 17 8.7 / 4(++具有相同的限制):

  

f表达式P指向具有n个元素的数组对象x[i]的元素x,表达式P + JJ + P(其中{{1 }}的值为J,如果0≤i+j≤n,则指向(可能是假设的)元素j;否则,行为是不确定的。

,

我想你已经有了答案;如果您看得更深一些:您已经说过,增加一个后端迭代器就是UB,那么:这个答案是在什么迭代器中?

迭代器只是一个具有指针的对象,递增该迭代器实际上是在递增其拥有的指针。因此,在许多方面,迭代器都是根据指​​针进行处理的。

  

int arr [] = {0,1,2,3,4,5,6,7,8,9};

     

int * p = arr; // p指向arr中的第一个元素

     

++ p; // p指向arr [1]

     

正如我们可以使用迭代器遍历向量中的元素一样,我们可以使用指针遍历数组中的元素。当然,要做到这一点,我们需要获取指向第一个元素的指针,以及指向最后一个元素的指针。如我们所见,我们可以使用数组本身或获取第一个元素的地址来获取指向第一个元素的指针。我们可以通过使用数组的另一个特殊属性来获得一个场外指针。我们可以将不存在的元素的地址放在数组的最后一个元素之后:

     

int * e =&arr [10]; //指针刚好超过arr中的最后一个元素

     

在这里,我们使用下标运算符索引不存在的元素; arr有十个元素,因此arr中的最后一个元素位于索引位置9。我们只能用该元素的地址作为地址,以初始化e。像场外迭代器(第3.4.1页,第106页)一样,场外指针也不指向元素。结果,我们可能无法取消引用或增加尾端指针。

这是Lipmann编写的C ++入门5版。

所以是UB不要这样做。

,

从最严格的意义上讲,这不是未定义行为,而是实现定义的。因此,尽管不建议您计划支持非主流体系结构,但您可以 做到这一点。

interjay给出的标准引号是一个很好的表示UB,但它在我看来只是第二好命,因为它处理指针-指针算术(有趣的是,一个明显是UB,而另一个是UB。 t)。有一段直接涉及问题中的操作:

  

[expr.post.incr] / [expr.pre.incr]
  daccess-ods.un.org daccess-ods.un.org操作数应该是指向完全定义的对象类型的指针。

哦,等一下,一个完全定义的对象类型?就这样?我的意思是真的 type 吗?这样就根本不需要对象了?
需要花费大量阅读才能真正发现其中的定义可能不太明确的提示。因为到目前为止,它看起来像是完全允许您这样做,没有任何限制。

[basic.compound] 3声明一个指针可能具有什么类型的指针,而与其他三个指针都不存在,则您的操作结果显然将落在3.4之下:无效的指针。 br /> 但是,它并没有说不允许您使用无效的指针。相反,它列出了一些非常常见的正常情况(例如存储持续时间结束),在这些情况下指针经常变为无效。因此,这显然是可以允许的事情。确实:

  

[basic.stc] 4
  通过无效指针值进行的间接传递以及将无效指针值传递给释放函数均具有未定义的行为。任何其他使用无效指针值的行为都有实现定义的行为。

我们在此执行“其他任何操作”,因此它不是未定义行为,而是实现定义的,因此通常是 allowable (除非实现明确表示不同)。

不幸的是,这还不是故事的结局。尽管最终结果从现在开始没有任何变化,但随着您搜索“指针”的时间延长,结果变得更加混乱:

  

[basic.compound]
  对象指针类型的有效值表示内存中字节的地址或空指针。如果类型T的对象位于地址A [...]上指向该对象,则无论如何获取值
  [注意:例如,将数组末尾的地址视为指向该数组元素类型的不相关对象,该对象可能位于该地址。 [...]]。

阅读为:好的,谁在乎!只要指针指向内存中的某个地方,我就很好吗?

  

[basic.stc.dynamic.safety]   指针值是安全派生的指针[blah blah]

阅读为:好的,可以安全导出。它没有解释这是什么,也没有说我真正需要它。安全地得出结论。显然,我仍然可以拥有非安全派生的指针。我猜想取消引用它们可能不是一个好主意,但是完全可以允许它们。没有其他说明。

  

实现可能具有宽松的指针安全性,在这种情况下,指针值的有效性不取决于它是否是安全得出的指针值。

哦,也许没关系,就是我的想法。但是等等...“可能不会”吗?也就是说,也可以。我怎么知道?

  

或者,实现可能具有严格的指针安全性,在这种情况下,除非安全地引用了完整对象并且先前已声明其为可到达的对象,否则不是安全导出的指针值的指针值是无效的指针值。 / p>

等等,所以甚至有可能我需要在每个指针上调用declare_reachable()吗?我怎么知道?

现在,您可以转换为明确定义的intptr_t,以给出安全派生的指针的整数表示。当然,对于该整数,它是完全合法的,并且定义明确,可以根据需要对其进行递增。
是的,您可以将intptr_t转换回同样定义良好的指针。只是,不是原始值,不再保证您有一个安全派生的指针(显然)。总而言之,就标准而言,虽然是实现定义的,但这还是100%合法的事情:

  

[expr.reinterpret.cast] 5
  整数类型或枚举类型的值可以显式转换为指针。指针转换为足够大小的整数并返回相同的指针类型原始值;指针和整数之间的映射是由实现定义的。

渔获物

指针只是普通的整数,只有您碰巧将它们用作指针。哦,只要那是真的!
不幸的是,存在一些架构,其中根本不是真的,仅生成无效的指针(不将其取消引用,仅将其保存在指针寄存器中)会导致陷阱。

因此,这是“实现定义”的基础。那,以及随便你想可以随意增加指针的事实当然会导致溢出,这是标准不希望处理的。应用程序地址空间的末尾可能与溢出的位置不一致,甚至您甚至都不知道特定体系结构上的指针是否存在诸如溢出之类的问题。总而言之,这是一场噩梦般的混乱,与可能的收益没有任何关系。

另一方面,处理上一个对象的情况很容易:实现必须简单地确保没有对象被分配,以便占用地址空间的最后一个字节。因此,这是明确定义的,因为它可以保证有用且微不足道。

本文链接:https://www.f2er.com/3169987.html

大家都在问