【问题标题】:Is it still legal to do pointer arithmetic on a deleted array?对已删除的数组进行指针运算仍然合法吗?
【发布时间】:2014-01-01 10:04:13
【问题描述】:

今天我写了一些看起来像这样的东西:

void foo(std::vector<char>&v){
    v.push_back('a');
    char*front=&v.front();
    char*back=&v.back();
    size_t n1=back-front+1;
    v.push_back('b');//This could reallocate the vector elements
    size_t n2=back-front+1;//Is this line valid or Undefined Behavior ?
}

如果在我推回 'b' 时发生重新分配,我还能计算我的两个指针的差吗?

看了几遍标准的相关段落,我仍然无法在这一点上下定决心。

C++11 5.7.6: 当两个指向同一个数组对象的元素的指针相减时,结果是 两个数组元素的下标。结果的类型是实现定义的有符号整数 类型;此类型应与标头 (18.2) 中定义为 std::ptrdiff_t 的类型相同。作为 对于任何其他算术溢出,如果结果不适合提供的空间,则行为未定义。 换句话说,如果表达式 P 和 Q 分别指向数组对象的第 i 个和第 j 个元素, 表达式 (P)-(Q) 具有值 i - j,前提是该值适合 std::ptrdiff_t 类型的对象。 此外,如果表达式 P 指向数组对象的一个​​元素,或者指向数组对象的最后一个元素之后的一个元素 一个数组对象,表达式 Q 指向同一个数组对象的最后一个元素,表达式 ((Q)+1)-(P) 与 ((Q)-(P))+1 和 -((P)-((Q)+1)) 具有相同的值,如果这 表达式 P 指向数组对象的最后一个元素,即使表达式 (Q)+1 没有 指向数组对象的一个​​元素。 除非两个指针都指向同一个数组对象的元素,或者 超过数组对象的最后一个元素,行为未定义。

我当然知道它有效,我只是想知道它是否合法。

【问题讨论】:

  • 被删除的对象不是不再是对象了吗?
  • 由于front 和back 可能在push_back 之后悬空,这意味着没有数组,如果没有数组,则两者无法指向它。这可能会调用 UB。
  • 没有人注意到应该是back-front,而不是front-back?
  • @Henrik 我更正了代码
  • @jbruni 这是一个实现细节。这个问题与标准有关。

标签: c++ c++11 language-lawyer undefined-behavior pointer-arithmetic


【解决方案1】:

指向已删除对象的指针是有毒的:除了给它们一个新值之外,不要触摸 then 。内存跟踪系统可以通过使用回收的指针值来捕获。不过,我不知道是否存在这样的系统。

相关引用是 3.7.4.2 [basic.stc.dynamic.deallocation] 第 4 段:

如果标准库中的释放函数的参数是一个非空指针值的指针,则释放函数将释放指针引用的存储,使所有指向 部分的指针无效释放存储空间。使用无效指针值(包括将其传递给释放函数)的影响是未定义的。

当调整std::vector&lt;...&gt; 的大小时,它会跳过许多环(分配器),并且默认情况下最终会调用一个释放函数。

【讨论】:

  • 货物崇拜编程。
  • @jbruni:我没有时间添加相关报价。但是,我的陈述是建立在标准之上的。报价现已添加。
  • +1:但是是在减去两个指针使用这些指针吗? ;)
  • @Yakk:由于引用不使用 pointer 而是 pointer value,所以我认为解释的余地​​很小。
  • @jbruni 我不明白货物崇拜如何适用。你可能还有别的意思吗?
【解决方案2】:

严格来说,是UB。但是您始终可以将您的 char * 指针转换为 uintptr_t(前提是它存在),然后安全地减去结果整数。

void foo(std::vector<char>&v){
    v.push_back('a');
    auto front= uintptr_t (&v.front());
    auto back = uintptr_t (&v.back());
    size_t n1=back-front+1;
    v.push_back('b');//This could reallocate the vector elements
    size_t n2=back-front+1;
}

【讨论】:

  • 但是,现在不能保证结果是有意义的。
  • 但是,为什么?这可能是定义的行为,但几乎可以肯定不是某人想要的。
  • @R.MartinhoFernandes:我实际上在将指针转换为整数产生的值与指针值无关的系统上工作过;例如,(uintptr_t)p1 - (uintptr_t)p0 可能会产生与(char*)p1 - (char*)p0 完全不同且毫无意义的结果。
  • @KeithThompson,这是什么原因?
  • @chill:Cray 矢量系统(C90、T90、SV1)。这些机器是 64 位字寻址的,没有硬件支持 8 位字节访问,因此机器是指向 64 位字的 64 位指针。但是系统运行的是 Unix,因此必须支持 8 位字节(C 允许 CHAR_BIT==64,但 POSIX 不允许,互操作性将是一场噩梦)。因此,C 编译器通过将 3 位字节偏移存储在指针的其他未使用的 3 个高位中来实现字节指针在软件中。
【解决方案3】:

这种特殊情况是安全的,但丑陋且具有误导性。

v.push_back('b');//This could reallocate the vector elements 行可能会导致您的容器重新分配。在这种情况下,下一行将使用不存在的 front 和 back 指针。即使是悬空指针,计算两个地址的差异也是安全的。不安全的是取消引用它们。

正确的解决方案是使用vector::count() 函数将始终保持同步。如果你(出于某种原因)不想打电话给vector::count(),你应该至少使用++n1。

【讨论】:

  • 只有在它停止存在之前才是安全的。将其视为安全假设编译器不会变得更智能。
  • 指针算术仍然是指针算术。使用指针值是安全的。在进行指针运算时,没有(智能)场景会导致取消引用指针(和崩溃)。正如我所指出的,正确的解决方案是使用vector::count()
  • 谁说过崩溃?智能编译器可能会导致其他不良行为(例如,完全删除涉及此类错误指针的操作,因为它显然必须是死代码:遵循 C++ 规则的行为良好的程序不会执行它,因此它必须是死代码)。未定义的行为与崩溃无关。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-26
  • 1970-01-01
  • 2015-08-18
  • 1970-01-01
  • 2012-05-21
  • 2011-04-30
  • 2015-08-09
相关资源
最近更新 更多