Ошибка reinterpret_cast или UB?

Рассмотрим следующий код:

#include <cstdint>
#include <algorithm>

std::uintptr_t minPointer(void *first, void *second) {
    const auto pair = std::minmax(
        reinterpret_cast<std::uintptr_t>(first),
        reinterpret_cast<std::uintptr_t>(second)
    );
    return pair.first;
}

и сборка, созданная GCC8 с -O3 на https://godbolt.org/z/qWJuV_ для minPointer :

minPointer(void*, void*):
  mov rax, QWORD PTR [rsp-8]
  ret

что явно не делает того, что задумал создатель кода. Этот код вызывает некоторые UB или это ошибка GCC (8)?


person bartop    schedule 17.09.2018    source источник
comment
Насколько я знаю, вы просто не можете использовать < для сравнения указателей, если они не находятся в одном массиве или объекте; все остальное - УБ.   -  person underscore_d    schedule 17.09.2018
comment
(см. комментарии ниже, но все же несколько связанные) меньше, чем сравнение для пустых указателей   -  person underscore_d    schedule 17.09.2018
comment
@underscore_d Давайте не будем поощрять неправильное представление о том, что C++ и C следуют одним и тем же правилам.   -  person Brian Bi    schedule 17.09.2018
comment
@underscore_d Я согласен с Джастином. Но то, что Вы сбросили, все равно довольно интересно, а я об этом не знал.   -  person bartop    schedule 17.09.2018
comment
связанные: Наблюдение за странным поведением с 'auto' и std::minmax / Если std::max() возвращает по ссылке (как и должно быть), может ли это привести к оборванной ссылке? - & cppreference для minmax заметок - немного далеко внизу - Для перегрузок (1,2), если один из параметров является значением r, возвращаемая ссылка становится висячей ссылкой в ​​конце полного выражения, содержащего вызов   -  person underscore_d    schedule 17.09.2018
comment
другое: структурированные привязки с std::minmax и rvalues (спойлер: вы можете не избегайте этого со структурированными привязками, потому что если вы объявляете тип как значение, это относится к невидимому pair, а не к его членам)   -  person underscore_d    schedule 17.09.2018


Ответы (2)


Это UB, но не по той причине, о которой вы могли подумать.

Соответствующая подпись std::minmax():

template< class T > 
std::pair<const T&,const T&> minmax( const T& a, const T& b );

В этом случае ваш pair является парой ссылок на uintptr_t const. Где настоящие объекты, на которые мы ссылаемся? Правильно, это были временные файлы, созданные в последней строке, которые уже вышли за рамки! У нас есть висящие ссылки.

Если вы написали:

return std::minmax(
    reinterpret_cast<std::uintptr_t>(first),
    reinterpret_cast<std::uintptr_t>(second)
).first;

тогда у нас нет висящих ссылок, и вы можете видеть, что gcc генерирует соответствующий код:

minPointer(void*, void*):
  cmp rsi, rdi
  mov rax, rdi
  cmovbe rax, rsi
  ret

В качестве альтернативы вы можете явно указать тип pair как std::pair<std::uintptr_t, std::uintptr_t>. Или просто полностью обойти пару и return std::min(...);.


Что касается особенностей языка, вам разрешено преобразовывать указатель в достаточно большой целочисленный тип из-за [expr.reinterpret.cast]/4, а std::uintptr_t гарантированно будет достаточно большим. Так что, как только вы исправите проблему с оборванной ссылкой, все будет в порядке.

person Barry    schedule 17.09.2018
comment
Минимальное исправление. Нам действительно нужна разметка пожизненной зависимости в C++. - person Yakk - Adam Nevraumont; 17.09.2018
comment
@Yakk-AdamNevraumont Также был бы интересен собственный механизм распада, чтобы мы могли каким-то образом указать, что pair является pair<T,T> вместо pair<T const&, T const&> здесь? - person Barry; 17.09.2018
comment
Это одна из тех вещей, которые в ретроспективе кажутся такими очевидными, но их так легко забыть всего один раз, что приводит к фатальным последствиям. :/ - person underscore_d; 17.09.2018
comment
@Barry std::pair< T const&, T const& > minmax( T const& [[lifetime]], T const& [[lifetime]] ) (атрибуты, вероятно, здесь неуместны), в котором говорится, что допустимое время жизни возвращаемого значения зависит от времени жизни обоих входных аргументов. Затем компилятор может либо (а) продлить время жизни временных объектов, привязанных к входным аргументам, либо (б) отклонить использование висячей ссылки как небезопасное. Изменение типа возвращаемого значения зависит от контекста вызова, и это кажется более сложным, чем такая разметка. - person Yakk - Adam Nevraumont; 17.09.2018
comment
Я бы хотел, чтобы компиляторы предупреждали об этом - person M.M; 18.09.2018
comment
@M.M Люди, которые полагаются на компиляторы, чтобы время от времени предупреждать о оборванных ссылках, звучат плохо. std::minmax нужно просто забанить. - person Passer By; 18.09.2018

reinterpret_cast хорошо определен. Проблема в том, что тип const auto pair равен const std::pair<const std::uintptr_t&, const std::uintptr_t&>, так как std::minmax возвращает, поэтому у вас есть висячие ссылки.

Вам просто нужно избавиться от оборванных ссылок, чтобы он работал:

std::uintptr_t minPointer(void *first, void *second) {
    const std::pair<std::uintptr_t, std::uintptr_t> pair = std::minmax(
        reinterpret_cast<std::uintptr_t>(first),
        reinterpret_cast<std::uintptr_t>(second)
    );
    return pair.first;
}

ссылка на Godbolt

person Justin    schedule 17.09.2018