我在一个非常大的应用程序中发现了这个问题,从中做了一个SSCCE。我不知道代码是否有未定义的行为或-O2
打破它。
使用gcc a.c -o a.exe -O2 -Wall -Wextra -Werror
进行编译时,会打印 5 。
但是在没有-O2
(例如-O1
)的情况下进行编译时会打印 25 ,或者取消注释2条注释行中的一条(防止内联)。
#include <stdio.h>
#include <stdlib.h>
// __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
打破了这个小程序,还是某个地方有未定义的行为?
答案 0 :(得分:13)
int* cur = todos-1;
调用未定义的行为。 todos - 1
是无效的指针地址。
强调我的:
(C99,6.5.6p8)“如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不应产生溢出; 否则,行为未定义。“
答案 1 :(得分:1)
作为@ 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
但是,如果我在printf
中添加main()
:
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 <printf@plt>
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)