Почему GCC оптимизирует назначения здесь? - PullRequest
0 голосов
/ 15 декабря 2018

У меня есть класс 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 используется вообще

Работает для:

  • Clang (си без оптимизации)
  • MSVC (режим отладки и выпуска)
  • GCC (текущие и старые версии) -O0 и -O1 (но НЕ для -O2 и -O3)

GCC и сборки Clang Address и UB sanitizer не указывают на какие-либо проблемы (кромеутечка памяти) при выполнении.

Может ли кто-нибудь указать на раздел в стандартном документе C ++, в котором говорится, что в этом коде есть UB (что может быть причиной для GCC, настойчиво оптимизирующего назначение)?Или это ошибка в GCC?

Редактировать: Удаление nullptr проверок в offset_ptr помогает (https://godbolt.org/z/5HjcLY). Но мне нужны эти нулевые проверки.

Ответы [ 2 ]

0 голосов
/ 15 декабря 2018

Вы можете сделать это, если вы используете reinterpret_cast до uintptr_t вместо uint8_t *.Таким образом, вы торгуете UB с поведением, определяемым реализацией.

См .: https://godbolt.org/z/rBTqYl

0 голосов
/ 15 декабря 2018

[expr.add] p5 :

Когда вычитаются два выражения указателя P и Q, тип результата является определяемым реализацией знаковым целочисленным типом;[...]

  • Если P и Q оба имеют нулевые значения указателя, результат равен 0.
  • В противном случае, если P и Q указывают соответственно на элементы x[i] и x[j] того же объекта массива x, выражение P - Q имеет значение i-j.
  • В противном случае поведение не определено.

Вычитание в списке инициализатора члена возвращается к третьей точке, поэтому у вас есть UB.

Это "работает", если вы удалите проверки nullptr, потому что gccне может доказать, что первое условие не выполняется.

...