gcc -O0仍然优化了“未使用”的代码。 有一个编译标志来改变?

正如我在这个问题提出 , _mm_div_ss(s1, s2);是删除(是的,用-O0 )一行代码_mm_div_ss(s1, s2); 可能是因为结果没有保存。 但是,这应该会触发一个浮点exception并引发SIGFPE,如果删除该调用,则不会发生这种情况。

问题 :是否有一个标志或多个标志传递给gcc,以便代码按原样编译? 我正在fno-remove-unused但我没有看到这样的事情。 理想情况下,这将是一个编译器标志,而不必更改我的源代码,但如果不支持,是否有一些gcc属性/编译指示使用?

我试过的东西:

 $ gcc --help=optimizers | grep -i remove 

没有结果。

 $ gcc --help=optimizers | grep -i unused 

没有结果。

并明确禁用所有死代码/消除标志 – 请注意,没有关于未使用的代码的警告:

 $ gcc -O0 -msse2 -Wall -Wextra -pedantic -Winline \ -fno-dce -fno-dse -fno-tree-dce \ -fno-tree-dse -fno-tree-fre -fno-compare-elim -fno-gcse \ -fno-gcse-after-reload -fno-gcse-las -fno-rerun-cse-after-loop \ -fno-tree-builtin-call-dce -fno-tree-cselim ac ac: In function 'main': ac:25:5: warning: ISO C90 forbids mixed declarations and code [-Wpedantic] __m128 s1, s2; ^ $ 

源程序

 #include <stdio.h> #include <signal.h> #include <string.h> #include <xmmintrin.h> static void sigaction_sfpe(int signal, siginfo_t *si, void *arg) { printf("%d,%d,%d\n", signal, si!=NULL?1:0, arg!=NULL?1:0); printf("inside SIGFPE handler\nexit now.\n"); exit(1); } int main() { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); sa.sa_sigaction = sigaction_sfpe; sa.sa_flags = SA_SIGINFO; sigaction(SIGFPE, &sa, NULL); _mm_setcsr(0x00001D80); __m128 s1, s2; s1 = _mm_set_ps(1.0, 1.0, 1.0, 1.0); s2 = _mm_set_ps(0.0, 0.0, 0.0, 0.0); _mm_div_ss(s1, s2); printf("done (no error).\n"); return 0; } 

编译上面的程序给出

 $ ./a.out done (no error). 

改变线

 _mm_div_ss(s1, s2); 

 s2 = _mm_div_ss(s1, s2); // add "s2 = " 

产生预期的结果:

 $ ./a.out inside SIGFPE handler 

编辑更多的细节。

这似乎与_mm_div_ss 定义中的__always_inline__属性有关。

 $ cat tc int div(int b) { return 1/b; } int main() { div(0); return 0; } $ gcc -O0 -Wall -Wextra -pedantic -Winline tc -o t.out $ 

(没有警告或错误)

 $ ./t.out Floating point exception $ 

vs下面(function属性除外)

 $ cat tc __inline int __attribute__((__always_inline__)) div(int b) { return 1/b; } int main() { div(0); return 0; } $ gcc -O0 -Wall -Wextra -pedantic -Winline tc -o t.out $ 

(没有警告或错误)

 $ ./t.out $ 

至less添加函数属性__warn_unused_result__会提供一个有用的信息:

 $ gcc -O0 -Wall -Wextra -pedantic -Winline tc -o t.out tc: In function 'main': tc:9:5: warning: ignoring return value of 'div', declared with attribute warn_unused_result [-Wunused-result] div(0); ^ 

编辑:

关于gcc邮件列表的一些讨论。 最终,我认为一切都按预期工作。

海湾合作委员会并没有在这里“优化”任何东西。 它只是不会产生无用的代码。 这似乎是一个非常普遍的错觉,即编译器应该生成一些纯粹的代码forms,并且对其进行任何更改都是“优化”。 哪有这回事。

编译器创build一些数据结构来表示代码的含义,然后对该数据结构应用一些转换,然后生成汇编器,然后汇编到指令中。 如果你编译时没有“优化”,那就意味着编译器只会尽可能less地生成代码。

在这种情况下,整个语句是无用的,因为它什么都不做,立即抛出(在扩展内联之后,内build的意思就是写a/b;不同之处在于写a/b;会发出一个关于statement with no effect的警告statement with no effect而内build的可能不会被相同的警告处理)。 这不是一个优化,编译器实际上不得不花费额外的努力去发明一个无意义的语句,然后伪造一个临时variables来存储这个语句的结果,然后把它扔掉。

你正在寻找的不是标志来禁用优化,但pessimization标志。 我不认为任何编译器开发人员浪费时间来实现这样的标志。 也许不如四月愚人笑话。

为什么gcc不发出指定的指令?

编译器生成的代码必须具有标准指定的可观察行为 。 任何不可观察的东西都可以随意更改(和优化),因为它不会改变程序的行为(如指定的那样)。

你怎么能打败它提交?

诀窍是让编译器相信这段代码的行为实际上是可观察的。

由于这是一个在微基准testing中经常遇到的问题,所以我build议您看一下(例如)Google-Benchmark如何解决这个问题。 从benchmark_api.h我们得到:

 template <class Tp> inline void DoNotOptimize(Tp const& value) { asm volatile("" : : "g"(value) : "memory"); } 

这个语法的细节是无聊的,为了我们的目的,我们只需要知道:

  • "g"(value)表示该value被用作语句的input
  • "memory"是一个编译时间的读/写障碍

所以,我们可以将代码更改为:

 asm volatile("" : : : "memory"); __m128 result = _mm_div_ss(s1, s2); asm volatile("" : : "g"(result) : ); 

哪一个:

  • 强制编译器认为s1s2可能在其初始化和使用之间被修改
  • 强制编译器考虑使用该操作的结果

没有任何标志需要,它应该在任何级别的优化工作(我在-O3testing它https://gcc.godbolt.org/ )。

我不是gcc内部的专家,但似乎你的问题不是通过一些优化过程去除死代码。 编译器很可能不会考虑首先生成此代码。

让我们将您的示例从编译器特定的内在函数简化为一个普通的旧加法:

 int foo(int num) { num + 77; return num + 15; } 

没有生成+ 77代码 :

 foo(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-4], edi mov eax, DWORD PTR [rbp-4] add eax, 15 pop rbp ret 

当其中一个操作数有副作用时, 只有该操作数被评估 。 但是,在组装中没有增加。

但是将这个结果保存到一个(甚至是未使用的)variables中会强制编译器生成加法代码 :

 int foo(int num) { int baz = num + 77; return num + 15; } 

部件:

 foo(int): push rbp mov rbp, rsp mov DWORD PTR [rbp-20], edi mov eax, DWORD PTR [rbp-20] add eax, 77 mov DWORD PTR [rbp-4], eax mov eax, DWORD PTR [rbp-20] add eax, 15 pop rbp ret 

以下只是一个推测,但是从编译器构build的经验来看,不用为未使用的expression式生成代码,而不是稍后消除此代码。

我的build议是明确你的意图,并将expression式的结果放入volatile (因此,优化器不可移除)variables。

@Matthieu M指出,防止预先计算价值是不够的。 因此,除了使用信号播放外,还应该使用文档化的方式来执行所需的精确指令(可能是volatile内联汇编)。