为什么GCC在这里优化了分配?

时间:2018-12-15 10:42:00

标签: c++ gcc g++ language-lawyer c++17

我有一个类offset_ptr,它的工作原理类似于指针,但是存储它指向的内存地址作为其自身地址this的偏移量。这是一个已删除所有内容的版本,不需要演示该问题:

template <typename T>
struct offset_ptr {
  using offset_t = int64_t;
  static constexpr auto const NULLPTR_OFFSET =
      std::numeric_limits<offset_t>::max();

  offset_ptr(T const* p)
      : offset_{p == nullptr ? NULLPTR_OFFSET
                             : static_cast<offset_t>(
                                   reinterpret_cast<uint8_t const*>(p) -
                                   reinterpret_cast<uint8_t const*>(this))} {}

  T* get() {
    return 
        offset_ == NULLPTR_OFFSET
            ? nullptr
            : reinterpret_cast<T*>(reinterpret_cast<uint8_t*>(this) + offset_);
  }

  offset_t offset_;
};

此代码不适用于GCC -O2-O3

int* get() {
  offset_ptr<int> ptr = static_cast<int*>(malloc(sizeof(int)));
  auto p = ptr.get();
  *p = 110;  // WOW - please do not optimize me away :-(
  return p;
}

(为了简化起见,故意省略了内存管理和错误检查!)

这在生成的程序集中也可见: https://godbolt.org/z/PfZEJM

任务丢失了。

如上方的Godbolt编译器资源管理器链接所示,它在以下情况下有效

  • 分配的值直接在函数本身中直接使用
  • offset_ptr位于堆上,而不是堆栈上
  • 完全不使用offset_ptr

它适用于:

  • C语(有和没有优化)
  • MSVC(调试和发布模式)
  • GCC(当前版本和旧版本)-O0-O1(但对于-O2-O3来说

GCC和Clang地址以及UB清理程序版本在执行时不表示任何问题(除了泄漏的内存)。

有人可以指出C ++标准文档中的一段,指出该代码中包含UB(这可能是GCC积极优化分配的原因)吗?还是GCC中的错误?

编辑: 删除nullptr中的offset_ptr检查会有所帮助(https://godbolt.org/z/5HjcLY)。但是我需要那些空检查。

2 个答案:

答案 0 :(得分:2)

[expr.add]p5

  

当两个指针表达式P和Q相减时,结果的类型为实现定义的有符号整数类型; [...]

     
      
  • 如果P和Q都得出空指针值,则结果为0。
  •   
  • 否则,如果P和Q分别指向同一数组对象x[i]的元素x[j]x,则表达式P-Q的值为i-j
  •   
  • 否则,该行为是不确定的。
  •   

成员初始值设定项列表中的减法落在第三点,因此您拥有UB。

如果您删除了nullptr检查,则它“有效”,因为gcc无法证明第一个条件没有发生。

答案 1 :(得分:1)

如果您使用reinterpret_castuintptr_t而不是uint8_t *,则可以使此工作有效。通过这种方式,您可以将UB换成实现定义的行为。

请参阅:https://godbolt.org/z/rBTqYl