这段代码中不应该使用严格别名吗?

Shouldn't strict-aliasing kick in in this code?

拍下这个玩具代码:

void f(const int& a, int* dat) {
    
    for (int i=0; i<a; ++i )
      dat[i] += a;
}

Observe 编译器担心 dat[i] 可能与 a 互为别名,即写入 dat[i] 可能会更改 a 的值 - 并且所以感觉有必要在每次循环迭代时重新加载 a(即 link 处的 'movslq (%rdi), %rax' 行)。

这可以通过更改数据类型来使用严格别名来解决:

void f(const int& a, long* dat) {
...    

generated code seems Indeed longer,但这是由于矢量化。它不会在每次迭代时重新加载 a

我很惊讶这对我自己的 自定义 类型不起作用!

struct myInt {
    int i;
    myInt& operator+=(int rhs) { i += rhs; return *this;}
};

void f(const int& a, myInt* dat) {
    
    for (int i=0; i<a; ++i )
      dat[i] += a;
}

这里the compiler returns to re-loading the loop boundary on every iteration。 clang 和 gcc 都可以。

看起来编译器的别名分析将 myInt 视为常规 int - 这在很多方面都是合理的,但会导致此优化被遗漏。这可能是编译器错误吗?还是关于严格别名我遗漏了什么?

想象一下:

struct myInt {
    int i;
    myInt& operator+=(int rhs) { i += rhs; return *this;}
};

void f(const int& a, myInt* dat) 
{
    for (int i=0; i<a; ++i)
      dat[i] += a;
}

int main()
{
   myInt foo{ 1 };
   f(foo.i, &foo);
}

在这个程序中,adat.i实际上是别名。它们是同一个变量。所以编译器实际上需要在写入另一个之后重新加载一个。

如果您确定不会发生别名,大多数编译器都支持 C99 的限制:

void f(const int& __restrict a, int* dat) {
    for (int i = 0; i < a; ++i) dat[i] += a;
}

Godbolt link

请注意,这是危险代码。更好的选择是按值取 a

void f(const int a, int* dat) {
    for (int i = 0; i < a; ++i) dat[i] += a;
}

Godbolt link