【发布时间】:2015-04-22 06:33:56
【问题描述】:
上下文
我的一位朋友问我以下谜题:
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;放在函数体中(注释write something before this comment之前),并在调用fn之前或之后放置i = 10;(这是在评论write something after this comment之后)。这可能是预期的解决方案,但我更喜欢你的策略。 -
在
void fn()printf("%d\n", 10); exit(0);没有UB。 -
@chux 我比我更喜欢
#define fn() i=10。 -
我对替代解决方案不感兴趣。我想知道为什么用这个解决方案无限循环。
-
这里描述和解释了一个非常相似的事情blogs.msdn.com/b/oldnewthing/archive/2014/06/27/10537746.aspx
标签: c gcc optimization undefined-behavior