gcc-O0仍然优化了“输出”;“未使用”;代码。是否有一个编译标志来改变这一点?
正如我在中提到的,gcc正在删除(是的,带有gcc-O0仍然优化了“输出”;“未使用”;代码。是否有一个编译标志来改变这一点?,c,gcc,C,Gcc,正如我在中提到的,gcc正在删除(是的,带有-O0)一行代码\u mm\u div\u ss(s1,s2)可能是因为结果未保存。但是,这会触发浮点异常并引发SIGFPE,如果删除调用,则不会发生这种情况 问题:是否有一个或多个标志传递给gcc,以便按原样编译代码?我在想类似于fno-remove unused的东西,但我没有看到类似的东西。理想情况下,这将是一个编译器标志,而不必更改我的源代码,但如果不支持,是否有一些gcc属性/pragma可供使用 我尝试过的事情: $ gcc --help=
-O0
)一行代码\u mm\u div\u ss(s1,s2)代码>可能是因为结果未保存。但是,这会触发浮点异常并引发SIGFPE,如果删除调用,则不会发生这种情况
问题:是否有一个或多个标志传递给gcc,以便按原样编译代码?我在想类似于fno-remove unused
的东西,但我没有看到类似的东西。理想情况下,这将是一个编译器标志,而不必更改我的源代码,但如果不支持,是否有一些gcc属性/pragma可供使用
我尝试过的事情:
$ gcc --help=optimizers | grep -i remove
没有结果
$ gcc --help=optimizers | grep -i unused
没有结果
并显式禁用所有死代码/消除标志——请注意,没有关于未使用代码的警告:
$gcc-O0-msse2-Wall-Wextra-pedantic-Winline\
-fno dce-fno dse-fno树dce\
-fno树dse-fno树fre-fno比较elim-fno gcse\
-重新加载后fno gcse-fno gcse las-fno循环后重新运行cse\
-fno树内置呼叫dce-fno树cselim a.c
a、 c:在函数“main”中:
a、 c:25:5:警告:ISO C90禁止混合声明和代码[-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;
}
换线
_mm_div_ss(s1, s2);
到
产生预期的结果:
$ ./a.out
inside SIGFPE handler
编辑更多细节
这似乎与\u mm\u div\u ss
上的\u始终\u内联\u
属性有关
$cat t.c
int
分区(内部b)
{
返回1/b;
}
int main()
{
分区(0);
返回0;
}
$gcc-O0-Wall-Wextra-pedantic-Winline t.c-o.out
$
(无警告或错误)
$./t.out
浮点异常
$
vs如下(除功能属性外,相同)
$cat t.c
__内联int属性(((总是内联)
分区(内部b)
{
返回1/b;
}
int main()
{
分区(0);
返回0;
}
$gcc-O0-Wall-Wextra-pedantic-Winline t.c-o.out
$
(无警告或错误)
$./t.out
$
添加函数属性\uuuuu warn\u unused\u result\uuuu
至少会给出一条有用的消息:
$gcc-O0-Wall-Wextra-pedantic-Winline t.c-o t.out
t、 c:在函数“main”中:
t、 c:9:5:警告:忽略'div'的返回值,用属性warn\u unused\u result[-Wunused result]声明
分区(0);
^
编辑:
关于这个问题的一些讨论。最终,我认为一切都在按预期进行。GCC在这里没有“优化”任何内容。它不会生成无用的代码。这似乎是一种非常常见的错觉,即编译器应该生成某种纯粹形式的代码,对其进行任何更改都是一种“优化”。没有这样的事
编译器创建一些表示代码含义的数据结构,然后在该数据结构上应用一些转换,并由此生成汇编程序,然后编译成指令。如果编译时没有进行“优化”,这只意味着编译器将尽可能少地生成代码
在这种情况下,整个语句是无用的,因为它什么也不做,会立即被丢弃(在扩展内联线和内置的含义后,它相当于写入a/b;
,区别在于写入a/b;
将发出一个关于语句的警告,但没有任何效果
,而内置的可能不会被相同的警告处理)。这不是一个优化,编译器实际上必须花费额外的努力来为一个无意义的语句创造意义,然后伪造一个临时变量来存储该语句的结果,然后将其丢弃
你要找的不是禁用优化的标志,而是悲观化标志。我认为任何编译器开发人员都不会浪费时间来实现这些标志。也许只是愚人节的玩笑。我不是gcc
内部的专家,但你的问题似乎不是通过一些优化来删除死代码s、 最有可能的是,编译器甚至没有考虑在第一时间生成此代码
让我们将您的示例从特定于编译器的内部函数简化为普通的旧添加:
int foo(int num) {
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
下面只是一个推测,但根据我在编译器构造方面的经验,不为未使用的表达式生成代码,而不是稍后删除此代码,这是更自然的
我的建议是明确说明您的意图,并将表达式的结果放入(因此,优化器不可删除)变量中
@Matthieu M指出,仅仅防止预计算值是不够的。因此,对于不仅仅是处理信号的事情,您应该使用有文档记录的方法来执行您想要的确切指令(可能是,volatile
inline assembly)
为什么gcc不发出指定的指令
编译器生成的代码必须具有标准指定的可观察行为。任何不可观察的行为都可以随意更改(和优化),因为它不会更改程序的行为(如指定的)
你怎么能使它屈服呢
诀窍是让编译器相信特定代码段的行为实际上是可以观察到的
由于这是micro benchmark中经常遇到的问题,我建议您看看(例如)Google benchmark是如何解决这一问题的。从我们得到:
其中:
强制编译器考虑<代码> S1 < /Cord>和<代码> S2< /代码>可能已经被修改了
int foo(int num) {
num + 77;
return num + 15;
}
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
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
template <class Tp>
inline void DoNotOptimize(Tp const& value) {
asm volatile("" : : "g"(value) : "memory");
}
asm volatile("" : : : "memory");
__m128 result = _mm_div_ss(s1, s2);
asm volatile("" : : "g"(result) : );