我正在看这个C++ 讲座(俄语)。16:10 左右,讲师提出了一个悬而未决的问题:
有这个代码:
int* foo()
{
volatile auto a = nullptr;
int* b = a;
return b;
}
int main()
{}
Run Code Online (Sandbox Code Playgroud)
Clang为(-Ofast)生成以下程序集foo
mov qword ptr [rsp - 8], 0 # volatile auto a = nullptr;
xor eax, eax
ret
Run Code Online (Sandbox Code Playgroud)
这意味着编译器假设读取没有副作用,a并且基本上删除了int* b = a;部分代码。
另一方面,GCC生成了一些不同的代码
mov QWORD PTR [rsp-8], 0 # volatile auto a = nullptr;
mov rax, QWORD PTR [rsp-8] # int* b = a;
xor eax, eax
ret
Run Code Online (Sandbox Code Playgroud)
这里编译器认为读取a确实会产生副作用并保持一切不变。
问题是根据 C++20 标准什么是正确的行为?
的类型a将是volatile std::nullptr_t.
std::nullptr_t并不真正需要具有任何内部状态,尽管它被指定为具有与 相同的大小void*。仅为此类型指定转换行为。
不需要std::nullptr_t在它占用的内存中存储值。所有转换行为仅取决于源具有std::nullptr_t类型这一事实。因此不需要0在那里写入或读取值。该赋值int* b = a;不依赖于 中存储的任何状态或值a,仅依赖于其类型。
我想说这两种行为都是正确的。访问的可观察副作用的确切含义无论如何volatile都是实现定义的,如果std::nullptr_t实现为实际上不使用其任何内存来存储值,那么人们不会期望从它加载/存储到它的任何指令无论如何都会为初始化和隐式转换生成int* b = a;。但是,如果std::nullptr_t实现类似于始终具有 value 的指针0,那么期望存储和加载是合理的。
注意:答案最初声称std::nullptr_t可以在任意数量的存储字节中实现,这是错误的,因为标准要求sizeof(std::nullptr_t) == sizeof(void*)。请参阅[basic.fundamental]/14。
事实上,在看过标准之后,我认为GCC的行为更加可疑。至少根据注释,左值到右值的转换a不应该访问nullptr_t左值。这意味着a的内存位置可能不会有任何负载,因为这会引入该规则不应出现的数据争用的可能性。
| 归档时间: |
|
| 查看次数: |
183 次 |
| 最近记录: |