【发布时间】:2013-06-13 18:32:55
【问题描述】:
当我教 C 时,有时我指望 GCC 来完成某些规则中“令人信服”的部分。例如,不应考虑函数上的局部变量在调用之间保留值。
GCC 总是帮助我向学生教授这些课程,将垃圾放在局部变量上,以便他们了解正在发生的事情。
现在,这段代码肯定让我很难受。
#include <stdio.h>
int test(int x)
{
int y;
if(!x)
y=0;
y++;
printf("(y=%d, ", y);
return y;
}
int main(void)
{
int a, i;
for(i=0; i<5; i++)
{
a=test(i);
printf("a=%d), ", a);
}
printf("\n");
return 0;
}
输出是:
(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),
但如果我评论这一行:
/* printf("(y=%d, ", y); */
那么输出变成:
a=1), a=32720), a=32721), a=32722), a=32723),
我使用-Wall开关编译代码,但没有任何警告与使用局部变量相关但未初始化它们。
是否有任何 GCC 开关会导致警告,或者至少显式显示一些垃圾?我尝试了优化开关,这有助于代码输出变成这样:
$ gcc test.c -o test -Wall -Os
$ ./test
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -Ofast
$ ./test
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O0
$ ./test
(y=1, a=1), (y=2, a=2), (y=3, a=3), (y=4, a=4), (y=5, a=5),
$ gcc test.c -o test -Wall -O1
$ ./test
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O2
$ ./test
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
$ gcc test.c -o test -Wall -O3
$ ./test
(y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1), (y=1, a=1),
但在所有情况下 y=1 都是一种诡计。标准是否改变了,所以局部变量现在用零初始化?
【问题讨论】:
-
我不认为这是巧合,因为编译器会将 y 链接到某个相对的内存区域。它不会改变 y 与第一个堆栈的关系,所以如果前一个堆栈的大小没有增长,y 将始终在同一个位置!
-
@Magn3s1um:您假设
y在堆栈上。如果它在寄存器中怎么办?未定义的行为是未定义的行为。你根本不能指望结果,即使你认为你知道为什么它可能会做你想做的事。 -
依靠编译器为具有未定义行为的程序显示明显的错误行为是一个高风险的提议。尤其是在涉及未初始化的变量和释放后使用错误时。
-
@Devolus 实际上我不同意。我见过很多人在面对一个行为未定义的程序时会说“但是当我之前尝试过它时它起作用了”。我认为让 C 和 C++ 程序员养成依靠尝试来查看它们是否有效的习惯是一个坏主意——这些语言就是不这样工作的。
-
@SebastianRedl,问题是当人们依靠“它对我有用”来了解它会产生什么影响时。许多人认为“如果它以前有效”就可以了,这不一定是正确的。
标签: c static initialization local