为什么 g++ 不优化局部数组而是优化全局数组?

Why does g++ not optimize a local array but a global?

本文关键字:数组 优化 全局 为什么 优化局 g++      更新时间:2023-10-16

我有以下两个函数,它们的作用基本相同:

enum Direction{
N = 0,
NW,
W,
SW,
S,
SE,
E,
NE,
TOTAL_DIRS
};
char const * const strings[] = {"N", "NW", "W", "SW", "S", "SE", "E", "NE"};
char const *
getDirString2(unsigned dir) {
if (TOTAL_DIRS > dir)
return strings[dir];
return nullptr;
}
char const *
getDirString3(unsigned dir) {
char const * const strings[] = {"N", "NW", "W", "SW", "S", "SE", "E", "NE"};
if (TOTAL_DIRS > dir)
return strings[dir];
return nullptr;
}

但是,虽然 g++ 优化了像我期望的那样使用全局数组的函数。它为替代方案创建了更多复杂的代码。Clang为两者创建相同的代码,如果我改用switch语句,clang和c ++也会创建与getDirString2相同的代码。

这是编译器资源管理器 https://godbolt.org/z/GxvrTv 的链接

这是我应该为 g++ 提交错误报告的事情,还是有充分的理由?

我想你可以称之为错过的优化,尽管这对 gcc 的人来说有点苛刻。

GCC 在编译getDirString3时,正在做你要求它做的事情 - 在堆栈上构造一个字符串数组,然后只返回它的一个元素。

另一方面,Clang 看到这个数组永远不会改变,而是在静态存储中构造它,请参阅:https://godbolt.org/z/24n-N7

要使 gcc 像 clang 一样生成代码,请将getDirString3中的数组声明为static(这首先是一个好主意),请参阅:https://godbolt.org/z/henD2Z

注意:起初,我说这是一个不符合标准的优化,但在评论中与Chris Dodd讨论后,我意识到这是部分不正确的。

更新的答案:

这是我应该为 g++ 提交错误报告的事情,还是有充分的理由?

clang 和 gcc 都可以做同样的优化,但默认情况下在 gcc 中是禁用的。所以这不是一个错误,这是有原因的。

叮当做了什么?

在您的示例中,编译器看到局部数组是常量,因此无需在每次调用函数时在堆栈上构造它。

如何在 gcc 中启用相同的优化?

要在 gcc 中启用相同的优化,请使用标志 -fmerge-all-constants。生成的代码将与 clang 的代码几乎相同(参见:https://godbolt.org/z/Ldx_qe):

getDirString3(unsigned int):
xor     eax, eax
cmp     edi, 7
ja      .L1
mov     edi, edi
mov     rax, QWORD PTR strings.2080[0+rdi*8]

为什么在 gcc 中默认禁用它?

来自 gcc 文档网站:

-合并所有常量...像 C 或 C++ 这样的语言需要每个变量,包括同一变量的多个实例 递归调用,具有不同的位置,因此使用此选项 导致不符合项的行为。

在上面的示例中,优化是安全的,并且遵循 as-if 规则,因为未使用此本地数组的地址或与来自不同调用的地址进行比较。但是,似乎这种优化在某些情况下会导致非重叠行为。您可以检查这些情况:此处和此处。

那么,你应该使用 -fnmerge-all-constants 标志吗?

两个编译器都不会检查是否使用常量的地址,因此我不建议这样做。在您的示例中很明显,优化是安全的,但是您无法知道编译器每次都会在哪里进行优化(否则您将自己完成),并且您无法确定它不会导致有时不协调行为。