volatile读/写nullptr的副作用

3
我是一个有用的助手,我可以为您翻译文本。 我正在观看thisC++讲座(使用俄语)。在大约16:10的时候,讲师提出了一个开放性问题: 拥有这段代码:
int* foo()
{
    volatile auto a = nullptr;
    int* b = a;
    return b;
}

int main()
{}

当使用 -Ofast 编译选项时,Clang 会为 foo 生成 以下 汇编代码。

    mov     qword ptr [rsp - 8], 0 # volatile auto a = nullptr;
    xor     eax, eax
    ret

这意味着编译器假定从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

在这里,编译器认为从a读取确实产生了副作用,并保持一切不变。

问题是根据C++20标准,正确的行为是什么?


1
我相信这将属于“好像规则”。如果优化没有可观察的影响,那么可以进行优化。 - NathanOliver
@NathanOliver 访问 volatile 被认为是可观察的,尽管其详细含义是由实现定义的。(当然,标准并未说明应发出哪些特定指令。) - user17732522
C++代码(假设其格式正确)需要符合所需的C++抽象机器行为。编译器供应商有足够的自由度将抽象机器行为优化为本地代码。(格式错误的代码可能会导致各种有趣的行为。其中“有趣”被定义为不得不在星期五晚上八点进入工作并一直工作到周末,以调试代码并推出更改以投入生产环境。) - Eljay
2个回答

8

a的类型将会是volatile std::nullptr_t

std::nullptr_t实际上不需要具有任何内部状态,尽管它规定与void*相同大小。该类型仅指定转换行为。

std::nullptr_t不需要在其占用的内存中存储值。所有的转换行为都只依赖于源有std::nullptr_t类型这一事实。因此不需要在那里写入或读取0值。赋值语句int* b = a;不依赖于a中存储的任何状态或值,只依赖于它的类型。

我认为这两种行为都是正确的。volatile访问的可观察副作用的确切含义是实现定义的,如果std::nullptr_t实现为实际上不使用其内存来存储值,则不应生成初始化和隐式转换中对其进行加载/存储的任何指令。但是,如果std::nullptr_t类似于一个始终具有值0的指针实现,则可以合理地期望进行加载和存储。

注:答案最初声称std::nullptr_t可以在任意数量的存储空间中实现,这是错误的,因为标准要求sizeof(std::nullptr_t)==sizeof(void*)。请参见[basic.fundamental]/14


事实上,在查看了标准之后,我认为GCC的行为更加可疑。至少根据一条注释,对a的左值到右值转换不应访问nullptr_t的glvalue。这意味着可能不会从a的内存位置加载任何内容,因为它会引入规则不应产生的数据竞争可能性。


0

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

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