所有的临时分配都有唯一的地址吗?
Do all transient allocations have unique address?
在阅读 C++ Weekly video 关于 C++20 中 constexpr 新支持的评论时,我发现评论声称 C++20 允许在 constexpr 上下文中使用 UB。
起初我确信评论是正确的,但我越来越多地思考它,我开始怀疑 C++20 的措辞包含一些聪明的语言来实现这种定义的行为。
要么所有瞬态分配return唯一地址,要么可能是 C++ 中一些更通用的概念,使 2 个不同的分配指针总是(即使在非 constexpr 上下文中)比较 false,即使在运行时实际上也是如此分配器会给你相同的地址(因为你删除了第一个分配)。
作为额外的怪异:你只能使用 ==
进行比较,<
,>
失败...
这是在 constexpr 中涉嫌 UB 的程序:
#include <iostream>
static constexpr bool f()
{
auto p = new int(1);
delete p;
auto q = new int(2);
delete q;
return p == q;
}
int main()
{
constexpr bool res1 = f();
std::cout << res1 << std::endl; // May output 0 or 1
}
这里的结果是实现定义的。 res1
可能为假、真或格式不正确,具体取决于实现想要定义它的方式。这对于相等比较和关系比较都是一样的。
两者 [expr.eq] (for equality) and [expr.rel] (for relational) start by doing an lvalue-to-rvalue conversion on the pointers (because we have to actually read what the value is to do a comparison). [conv.lval]/3 都表示该转换的结果是:
Otherwise, if the object to which the glvalue refers contains an invalid pointer value ([basic.stc.dynamic.deallocation], [basic.stc.dynamic.safety]), the behavior is implementation-defined.
这里就是这种情况:两个指针都包含无效的指针值,根据 [basic.stc.general]/4:
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.
脚注阅读:
Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault.
所以我们从左值到右值转换中得到的值是...实现定义的。它可以通过使这两个指针比较相等的方式实现定义。它可能是实现定义的,导致这两个指针比较 not 相等(显然所有实现都这样做)。或者它甚至可以是实现定义的,导致这两个指针之间的比较是未指定或未定义的行为。
值得注意的是,[expr.const]/5 (the main rule governing constant expressions), despite rejecting undefined behavior and explicitly rejecting any comparison whose result is unspecified ([expr.const]/5.23),未提及结果由实现定义的比较。
这里没有未定义的行为。什么都可以。诚然,在不断评估期间这非常奇怪,我们希望看到一套更严格的规则。
值得注意的是,对于 p < q
,gcc 和 clang 似乎拒绝比较,因为它不是常量表达式(这是......允许的结果),而 msvc 认为 p < q
和 p > q
是值为 false
的常量表达式(这是...也是允许的结果)。
在阅读 C++ Weekly video 关于 C++20 中 constexpr 新支持的评论时,我发现评论声称 C++20 允许在 constexpr 上下文中使用 UB。
起初我确信评论是正确的,但我越来越多地思考它,我开始怀疑 C++20 的措辞包含一些聪明的语言来实现这种定义的行为。
要么所有瞬态分配return唯一地址,要么可能是 C++ 中一些更通用的概念,使 2 个不同的分配指针总是(即使在非 constexpr 上下文中)比较 false,即使在运行时实际上也是如此分配器会给你相同的地址(因为你删除了第一个分配)。
作为额外的怪异:你只能使用 ==
进行比较,<
,>
失败...
这是在 constexpr 中涉嫌 UB 的程序:
#include <iostream>
static constexpr bool f()
{
auto p = new int(1);
delete p;
auto q = new int(2);
delete q;
return p == q;
}
int main()
{
constexpr bool res1 = f();
std::cout << res1 << std::endl; // May output 0 or 1
}
这里的结果是实现定义的。 res1
可能为假、真或格式不正确,具体取决于实现想要定义它的方式。这对于相等比较和关系比较都是一样的。
两者 [expr.eq] (for equality) and [expr.rel] (for relational) start by doing an lvalue-to-rvalue conversion on the pointers (because we have to actually read what the value is to do a comparison). [conv.lval]/3 都表示该转换的结果是:
Otherwise, if the object to which the glvalue refers contains an invalid pointer value ([basic.stc.dynamic.deallocation], [basic.stc.dynamic.safety]), the behavior is implementation-defined.
这里就是这种情况:两个指针都包含无效的指针值,根据 [basic.stc.general]/4:
When the end of the duration of a region of storage is reached, the values of all pointers representing the address of any part of that region of storage become invalid pointer values. Indirection through an invalid pointer value and passing an invalid pointer value to a deallocation function have undefined behavior. Any other use of an invalid pointer value has implementation-defined behavior.
脚注阅读:
Some implementations might define that copying an invalid pointer value causes a system-generated runtime fault.
所以我们从左值到右值转换中得到的值是...实现定义的。它可以通过使这两个指针比较相等的方式实现定义。它可能是实现定义的,导致这两个指针比较 not 相等(显然所有实现都这样做)。或者它甚至可以是实现定义的,导致这两个指针之间的比较是未指定或未定义的行为。
值得注意的是,[expr.const]/5 (the main rule governing constant expressions), despite rejecting undefined behavior and explicitly rejecting any comparison whose result is unspecified ([expr.const]/5.23),未提及结果由实现定义的比较。
这里没有未定义的行为。什么都可以。诚然,在不断评估期间这非常奇怪,我们希望看到一套更严格的规则。
值得注意的是,对于 p < q
,gcc 和 clang 似乎拒绝比较,因为它不是常量表达式(这是......允许的结果),而 msvc 认为 p < q
和 p > q
是值为 false
的常量表达式(这是...也是允许的结果)。