是GCC的选择-O2打破这个小程序还是我有未定义的行为
我在一个非常大的应用程序中发现了这个问题,从中做了一个SSCCE。 我不知道代码是否有未定义的行为或-O2
打破它。
用gcc ac -o a.exe -O2 -Wall -Wextra -Werror
编译它时会输出5 。
但是在没有-O2
(例如-O1
)的情况下编译时会打印25 ,或者在2个注释行中取消注释(防止内联)。
#include #include // __attribute__((noinline)) int f(int* todos, int input) { int* cur = todos-1; // fixes the ++ at the beginning of the loop int result = input; while(1) { cur++; int ch = *cur; // printf("(%i)\n", ch); switch(ch) { case 0:; goto end; case 1:; result = result*result; break; } } end: return result; } int main() { int todos[] = { 1, 0}; // 1:square, 0:end int input = 5; int result = f(todos, input); printf("=%i\n", result); printf("end\n"); return 0; }
是GCC的选择-O2
打破这个小程序还是我在某处有未定义的行为?
int* cur = todos-1;
调用未定义的行为。 todos - 1
是无效的指针地址。
我的重点:
(C99,6.5.6p8)“如果指针操作数和结果都指向同一个数组对象的元素,或者指向数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。 “
作为@ ouah的答案的补充,这解释了编译器正在做什么。
生成的汇编程序供参考:
400450: 48 83 ec 18 sub $0x18,%rsp 400454: be 05 00 00 00 mov $0x5,%esi 400459: 48 8d 44 24 fc lea -0x4(%rsp),%rax 40045e: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp) 400465: 00 400466: 48 83 c0 04 add $0x4,%rax 40046a: 8b 10 mov (%rax),%edx
但是,如果我在main()
添加printf
:
400450: 48 83 ec 18 sub $0x18,%rsp 400454: bf 84 06 40 00 mov $0x400684,%edi 400459: 31 c0 xor %eax,%eax 40045b: 48 89 e6 mov %rsp,%rsi 40045e: c7 04 24 01 00 00 00 movl $0x1,(%rsp) 400465: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp) 40046c: 00 40046d: e8 ae ff ff ff callq 400420 400472: 48 8d 44 24 fc lea -0x4(%rsp),%rax 400477: be 05 00 00 00 mov $0x5,%esi 40047c: 48 83 c0 04 add $0x4,%rax 400480: 8b 10 mov (%rax),%edx
具体来说(在printf
版本中),这两个指令填充了todo
数组
40045e: c7 04 24 01 00 00 00 movl $0x1,(%rsp) 400465: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp)
非printf
版本明显缺少这一点,由于某种原因,它只分配第二个元素:
40045e: c7 44 24 04 00 00 00 movl $0x0,0x4(%rsp)