为什么使用 constexpr 时超出范围变量的地址等于 0?

Bor*_*ris 14 c++ gcc constexpr

关于语义,我有些不明白constexprx当然,在堆栈上分配的地址无法在编译时计算出来。

constexpr auto foo()
{
    int x = 5;    
    return &x;
}

static_assert(foo() == 0);
Run Code Online (Sandbox Code Playgroud)

尽管如此,这段代码仍然可以编译(GCC 13.2 -std=c++20),并且 的地址x等于0,这可以在通过的声明中看到static_assert

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

Jan*_*tke 16

指向对象的指针可以在常量表达式中以有限的方式使用,即使这些对象从未存在于常量表达式中,或者已经死亡:

// In your example, x does exist at compile time, but its lifetime ends after foo() exits.
// You cannot dereference a pointer &x after x is dead, but you can do some other things:
int x = 5;

// The address of a local variable is never null.
// GCC and clang compile this.
// You can even perform this comparison outside foo(), like in your assertion, when
// x has died.
static_assert(&x != nullptr);

// The distance between two addresses can be computed, if those addresses are
// constant expresssions. GCC and clang compile this.
static_assert(&x - &x == 0);
Run Code Online (Sandbox Code Playgroud)

在表达式 中foo() == 00实际上是一个空指针常量,因此您所做的相当于&x == nullptr,尽管是间接的。此比较的结果具有实现定义的效果,因为它x是死的,并foo()产生无效的指针值

当到达存储区域的持续时间结束时,表示该存储区域的任何部分的地址的所有指针的值变成无效指针值。通过无效指针值进行间接寻址以及将无效指针值传递给释放函数具有未定义的行为。 对无效指针值的任何其他使用都具有实现定义的行为。

- [basic.stdc.general] p4

断言:

static_assert(foo() == 0);
Run Code Online (Sandbox Code Playgroud)

clang 失败,自 GCC 14 起,GCC 失败。但是,GCC 13 及更低版本将此无效指针值视为等于 null,因此比较成功。请参阅编译器资源管理器中的实时示例


use*_*522 9

在常量表达式的求值中,如果变量的生命周期仅限于常量表达式的求值,则可以像在任何其他表达式求值中一样正常使用变量。(即编译器将在编译时模拟堆栈来计算常量表达式。)

要求static_cast整个操作数,即foo() == 0是一个常量表达式

在评估时,foo() == 0您正在比较指针值,但指针值不是常量表达式结果的一部分,因此编译时指针转义到运行时上下文没有问题(这在常量表达式中是不允许的) )。所以没有问题。

然而,一旦返回,由于变量的存储期限结束,foo()返回的指针值就变成无效指针值x。除了少数例外,任何无效指针值的使用都是由实现定义的。比较0属于这种实现定义。

因此是否static_assert(foo() == 0, "???");编译是实现定义的。最初我预计编译器通常会视为foo() == 0false,因为如果假设成为无效指针值的指针保留其地址,那么它不能等于空指针值。然而,我现在似乎不清楚一些编译器供应商实际上期望这里的行为是什么。据我所知,他们缺乏这种实现定义行为的文档。