为什么 clang 在每次使用时都取消引用参数?

Ale*_*rty 5 c++ windows clang++

我一直在对工作中的一些代码进行性能优化,并偶然发现了一些奇怪的行为,我将其归结为下面的简单 C++ 代码片段:

#include <stdint.h>

void Foo(uint8_t*& out)
{
    out[0] = 1;
    out[1] = 2;
    out[2] = 3;
    out[3] = 4;
}
Run Code Online (Sandbox Code Playgroud)

然后,我铛(在Windows上)用下面的编译:clang -S -O3 -masm=intel test.cpp。这导致以下程序集:

        mov     rax, qword ptr [rcx]
        mov     byte ptr [rax], 1
        mov     rax, qword ptr [rcx]
        mov     byte ptr [rax + 1], 2
        mov     rax, qword ptr [rcx]
        mov     byte ptr [rax + 2], 3
        mov     rax, qword ptr [rcx]
        mov     byte ptr [rax + 3], 4
        ret
Run Code Online (Sandbox Code Playgroud)

为什么 clang 生成的代码反复将out参数取消引用到rax寄存器中?这似乎是一个非常明显的优化,它故意不进行,所以问题是为什么?

有趣的是,我尝试更改uint8_tuint16_t,结果生成了更好的机器代码:

        mov     rax, qword ptr [rcx]
        movabs  rcx, 1125912791875585
        mov     qword ptr [rax], rcx
        ret
Run Code Online (Sandbox Code Playgroud)

Qui*_*mby 5

编译器不能仅仅因为严格的别名而进行这样的优化,因为uint8_t总是*定义为unsigned char。因此它可以指向任何内存位置,这意味着它也可以指向自身,并且因为您将它作为引用传递,所以写入可能会在函数内部产生副作用。

以下是依赖于非缓存读取的模糊但正确的用法:

#include <cassert>
#include <stdint.h>
void Foo(uint8_t*& out)
{
    uint8_t local;
    // CANNOT be used as a cached value further down in the code.
    uint8_t* tmp = out;
    // Recover the stored pointer.
    uint8_t **orig =reinterpret_cast<uint8_t**>(out);
    // CHANGES `out` itself;
    *orig=&local;

    **orig=5;
    assert(local==5);
    // IS NOT EQUAL even though we did not touch `out` at all;
    assert(tmp!=out);
    assert(out==&local);
    assert(*out==5);
}

int main(){
   // True type of the stored ptr is uint8_t**
   uint8_t* ptr = reinterpret_cast<uint8_t*>(&ptr);

   Foo(ptr);
}
Run Code Online (Sandbox Code Playgroud)

这也解释了为什么会uint16_t生成“优化”代码,因为uin16_t永远不会*,(unsigned) char因此编译器可以自由地假设它不会为其他指针类型(例如它自己)设置别名。

*也许一些不相关的晦涩平台具有不同大小的字节。这不是重点。