reinterpret_cast 的 bug 或 UB?

15

考虑以下代码:

#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;
}

针对minPointer,使用GCC8的-O3生成的汇编代码如下:

https://godbolt.org/z/qWJuV_

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

这段代码明显没有实现代码创建者预期的功能。这段代码是否会导致未定义行为或者是GCC(8)的一个错误?


据我所知,如果指针不在同一数组或对象中,则简单地不能使用“<”来比较它们;其他任何情况都是未定义的行为。 - underscore_d
1
(参见下面的评论,但仍然有些相关)[比较空指针的小于运算符] - underscore_d
@underscore_d,让我们不要鼓励C++和C遵循相同的规则这一误解。 - Brian Bi
1
@underscore_d 我同意Justin的观点。不过你提到的内容仍然很有趣,我之前并不知道。 - bartop
4
参考链接包括:使用'auto'和std::minmax观察到奇怪的行为 / 如果std::max()返回引用(必须如此),是否会导致悬空引用? - 以及minmax的cppreference说明 - 稍微往下拉一点 - “对于重载版本(1,2),如果其中一个参数是右值,则所返回的引用在包含该调用的完整表达式结束后变成悬空引用”。 - underscore_d
2
另一个:使用std::minmax和rvalues的结构化绑定(提示:您无法通过结构化绑定避免此问题,因为如果您将类型声明为值,则适用于不可见的“pair”,而不是其成员) - underscore_d
2个回答

22

这是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>。或者完全绕过pairreturn std::min(...);


就语言细节而言,由于[expr.reinterpret.cast]/4,您可以将指针转换为足够大的整数类型,并且std::uintptr_t保证足够大。因此,一旦您解决了悬空引用问题,就没问题了。


1
最小修复。我们真的需要C++中的生命周期依赖标记。 - Yakk - Adam Nevraumont
@Yakk-AdamNevraumont 自定义衰减机制也很有趣,这样我们就可以指定 pair 是一个 pair<T,T> 而不是 pair<T const&, T const&> - Barry
这是其中一件事情,在回顾时看起来非常明显,但只要忘记一次就会带来致命的结果。 :/ - underscore_d
1
@Barry std::pair< T const&, T const& > minmax( T const& [[lifetime]], T const& [[lifetime]] )(属性可能不适用于此处),它说明返回值的有效生命周期取决于两个输入参数的生命周期。编译器可以将绑定到输入参数的临时对象的生命周期延长,或者拒绝使用悬空引用作为不安全的操作。返回类型的更改取决于调用上下文,而这似乎比这种标记更难。 - Yakk - Adam Nevraumont
我希望编译器能够警告这个问题。 - M.M
@M.M 依赖编译器偶尔警告悬空引用的人听起来很糟糕。std::minmax 应该被彻底禁止。 - Passer By

13
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链接


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接