使用GCC -O2选项编译生成不同的程序
我听说C编译器有/无优化选项可能会生成不同的程序(编译优化程序会导致它的行为不同),但我从未遇到过这种情况。 任何人都可以举个简单的例子来证明这一点吗?使用GCC -O2选项编译生成不同的程序
对于GCC 4.4.4,这不同于与-O0
和-O2
void foo(int i) {
foo(i+1);
}
main() {
foo(0);
}
有了这个优化循环,直到永远。如果没有优化,它崩溃(堆栈溢出!)
其它和更逼真的变体通常将依赖于定时,易受浮精确性的变化,或者取决于未定义行为(未初始化的变量,堆/堆栈布局)
酷!我从不期望编译器优化可以做到这一点。 – user607722 2011-03-25 03:05:37
一个这个问题的答案可能是:
每ANSI C编译器必须至少支持:
- 31个参数的函数定义
- 个31参数在函数调用中的源极线
- 509字符
- 在表达式32个水平嵌套的括号的
- 长整型的最大值不能被任何小于2147483647,(即,长整型 至少为32位)。
来源:C专家编程 - 彼得·菩提树
这可能是因为编译器支持的功能定义,也许31参数-O0和35 -O3,这是因为没有规范为了这。我个人认为这应该是一个缺陷设计,非常可以改进。但简而言之:编译器中的某些内容不受标准约束,并且可以在包含优化级别的实现中进行更改。
希望这可以帮助马克Loeser说,你应该在你的问题更具体。
错误的猜想 – sehe 2011-03-30 21:55:10
我已经看到它在浮点精度极限附近做了很多数学计算。 在极限情况下,算术不是关联的,所以如果以稍微不同的顺序执行操作,则可以得到稍微不同的答案。另外,如果使用80位双精度浮点芯片,但结果存储在64位双精度变量中,则信息可能会丢失,因此操作顺序会影响结果。
你能找到一个例子吗? – user607722 2011-03-25 11:54:57
如果你看一下这个代码生成的汇编:
int main()
{
int i = 1;
while (i) ;
return 0;
}
Whitout的-02标志:
.file "test.c"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $1, -4(%ebp)
.L2:
cmpl $0, -4(%ebp)
jne .L2
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
随着-02标志:
.file "test.c"
.text
.p2align 4,,15
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
.L2:
jmp .L2
.size main, .-main
.ident "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
.section .note.GNU-stack,"",@progbits
随着-O2标志,i
和返回值的声明是ommited,你只有一个跳转标签o这个相同的标签构成无限循环。
没有-02标志,你可以清楚地看到堆栈(subl $16, %esp
)和初始化(movl $1, -4(%ebp)
)以及的,而条件(cmpl $0, -4(%ebp)
)评估和主要的返回值的i
空间的分配功能(movl $0, %eax
)。
well..尽管汇编代码不同,但他们仍然在做同样的事情:无限循环。 – user607722 2011-03-25 02:59:58
优化级别之间的差异通常源于未初始化的变量。例如:
#include <stdio.h>
int main()
{
int x;
printf("%d\n", x);
return 0;
}
当与-O0
编译,输出5895648
。用-O2
编译时,每次运行它时输出一个不同的数字;例如,-1077877612
。
区别可以更微妙;想象你有下面的代码:
int x; // uninitialized
if (x % 10 == 8)
printf("Go east\n");
else
printf("Go west\n");
随着-O0
,这将输出Go east
,并与-O2
,(通常)Go west
。
well..uninitialized变量具有未定义的行为...如果标准中定义了每行代码的行为,那么优化会不会影响输出? – user607722 2011-03-25 03:01:46
很少发现-O2不会产生与不使用优化不同的结果的情况。
unsigned int fun (unsigned int a)
{
return(a+73);
}
没有优化:
fun:
str fp, [sp, #-4]!
.save {fp}
.setfp fp, sp, #0
add fp, sp, #0
.pad #12
sub sp, sp, #12
str r0, [fp, #-8]
ldr r3, [fp, #-8]
add r3, r3, #73
mov r0, r3
add sp, fp, #0
ldmfd sp!, {fp}
bx lr
与优化:
fun:
add r0, r0, #73
bx lr
即使这样的功能:
void fun (void)
{
}
没有优化:
fun:
str fp, [sp, #-4]!
.save {fp}
.setfp fp, sp, #0
add fp, sp, #0
add sp, fp, #0
ldmfd sp!, {fp}
bx lr
随着优化:
fun:
bx lr
如果宣布一切挥发,产生了对帧指针,其中未优化和优化是一样的,你可能会接近一些。同样,如果您编译了一个可调试版本(不确定该开关是什么),那么它的行为就好像一切都是不稳定的,这样您就可以使用调试器来监视内存中的变量并单步执行。这也可能接近来自相同输入的相同输出。
另请注意,无论是否进行优化,预计都会看到来自不同编译器的相同源代码的不同输出,甚至不同主要版本的gcc也会产生不同的结果。像上面这些平凡的功能通常会产生相同的结果,许多编译器进行优化。但是更复杂的函数会带来更多的变量,从编译器到编译器会产生不同的结果。
在错误提交报告中可以找到在不同优化级别上具有不同输出的正确程序的示例,它们只能在特定版本的GCC上“工作”。
但是通过调用UB很容易实现。但是,它不再是一个正确的程序,并且可能会产生不同版本的GCC的不同输出(其中,请参阅mythology)。
以下代码输出Here i am
,当编译时没有进行优化但没有进行优化编译时。
的想法是,该函数x()
被指定为“纯”(具有无副作用),所以编译器可以优化出来(我的编译器是GCC 4.1.2)。
#include <stdio.h>
int x() __attribute__ ((pure));
int x()
{
return printf("Here i am!\n");
}
int main()
{
int y = x();
return 0;
}
优化使用约
- 在某些情况下不存在指针别名的假设(这意味着它可以保持东西在寄存器无需经过另一参考担心修改)
- 中的存储器位置的非挥发性一般
正是因为这一点,你可以像
也警告Type-punned pointers may break strict aliasing rules... (paraphrased)
像这样的警告是为了让您免于头痛,当您的代码在编译机智&优化时发展微妙的错误。
一般来说,在C和C++
- 非常肯定你知道自己在做
- 什么从来没有发挥它宽松(不投炭**直接为char *等)
- 使用常量,易挥发,掷(),尽职尽责
- 信任你的编译器供应商(或开发者),或建立-O0
我敢肯定,我错过了史诗,但你的漂移。
键入我的HTC。对不起打字错误
哦,不要忘记:最具风险的优化(即那些使得每天程序员说法更容易错过/最令人惊讶的细微假设)只能使用-O3或特定的-f .......选项 – sehe 2011-03-30 21:59:22
这里有助于更具体的了解。你的意思是输入是代码,输出是实际的二进制对象吗?或者通过优化编译程序使其行为有所不同?后者通常是编译器中的一个错误,或者您滥用未定义的行为。 – 2011-03-24 17:14:31