背景
我的一个朋友问了我下面这个谜题:
void fn(void)
{
/* write something after this comment so that the program output is 10 */
/* write something before this comment */
}
int main()
{
int i = 5;
fn();
printf("%d\n", i);
return 0;
}
我知道可能会有多种解决方案,其中一些涉及宏,一些则假设了关于实现的某些内容并违反了C语言规范。
我特别感兴趣的一种解决方案是对堆栈做出一定的假设,并编写以下代码:(我知道这是未定义行为,但在许多实现中可能按预期工作)
void fn(void)
{
/* write something after this comment so that the program output is 10 */
int a[1] = {0};
int j = 0;
while(a[j] != 5) ++j; /* Search stack until you find 5 */
a[j] = 10; /* Overwrite it with 10 */
/* write something before this comment */
}
问题
这个程序在没有优化的情况下在MSVC和gcc中工作正常。但是当我用gcc -O2
标志编译它或在ideone上尝试时,在函数fn
中出现无限循环。
我的观察
当我使用gcc -S
与gcc -S -O2
编译文件并进行比较时,明显显示gcc
在函数fn
中保留了一个无限循环。
问题
我知道因为代码调用未定义行为,所以不能称其为错误。但是编译器为什么会在O2
时分析这种行为并留下一个无限循环?
许多人在评论中要求了解如果将某些变量更改为volatile
时的行为。如预期结果:
- 如果将
i
或j
更改为volatile
,程序行为保持不变。 - 如果将数组
a
设置为volatile
,程序不会出现无限循环。 - 此外,如果我应用以下补丁:
- int a[1] = {0};
+ int aa[1] = {0};
+ int *a = aa;
程序行为保持不变(无限循环)
如果我使用gcc -O2 -fdump-tree-optimized
编译代码,我将得到以下中间文件:
;; Function fn (fn) (executed once)
Removing basic block 3
fn ()
{
<bb 2>:
<bb 3>:
goto <bb 3>;
}
;; Function main (main) (executed once)
main ()
{
<bb 2>:
fn ();
}
Invalid sum of incoming frequencies 0, should be 10000
这证实了下面答案给出的论断。
return;
,并在调用fn
之前或之后(在注释“在此注释之后编写一些内容”之后)放置i = 10;
。这可能是预期的解决方案,但我更喜欢您的方法。 - 2012rcampionvoid fn()
内部,printf("%d\n", 10); exit(0);
不会产生未定义行为。 - chux - Reinstate Monica#define fn() i=10
更喜欢你的那个。 - Mark B