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

问题描述 投票:0回答:2

我不明白

constexpr
语义。当然,
x
在堆栈上分配的地址是无法在编译时计算出来的。

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

static_assert(foo() == 0);

尽管如此,这段代码仍然可以编译(GCC 13.2

-std=c++20
)并且
x
的地址等于
0
,这可以在通过的
static_assert
声明中看到。

参见https://godbolt.org/z/Pjrx3cb68

c++ gcc constexpr
2个回答
7
投票

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

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

在计算

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

但是,一旦

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

因此,

static_assert(foo() ==  0, "???");
是否编译是实现定义的(尽管通常的实现会失败,因为最初指向对象的指针值在这些实现上总是与空指针值比较不相等)。


6
投票

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

// 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);

在表达式

foo() == 0
中,
0
实际上是一个空指针常量,所以你所做的相当于
&x == nullptr
,尽管是间接的。此比较的结果具有实现定义的效果,因为
x
已死亡,并且
foo()
产生 无效的指针值:

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

-
[basic.stdc.general] p4

断言:

static_assert(foo() == 0);

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

© www.soinside.com 2019 - 2024. All rights reserved.