斯坦福大学教程和GCC之间的冲突

根据这部电影(大约38分钟),如果我有两个具有相同局部variables的函数,他们将使用相同的空间。 所以下面的程序,应该打印5 。 用gcc结果-1218960859 。 为什么?

该程序:

 #include <stdio.h> void A() { int a; printf("%i",a); } void B() { int a; a = 5; } int main() { B(); A(); return 0; } 

按照要求,这里是反汇编器的输出:

 0804840c <A>: 804840c: 55 push ebp 804840d: 89 e5 mov ebp,esp 804840f: 83 ec 28 sub esp,0x28 8048412: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 8048415: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 8048419: c7 04 24 e8 84 04 08 mov DWORD PTR [esp],0x80484e8 8048420: e8 cb fe ff ff call 80482f0 <printf@plt> 8048425: c9 leave 8048426: c3 ret 08048427 <B>: 8048427: 55 push ebp 8048428: 89 e5 mov ebp,esp 804842a: 83 ec 10 sub esp,0x10 804842d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5 8048434: c9 leave 8048435: c3 ret 08048436 <main>: 8048436: 55 push ebp 8048437: 89 e5 mov ebp,esp 8048439: 83 e4 f0 and esp,0xfffffff0 804843c: e8 e6 ff ff ff call 8048427 <B> 8048441: e8 c6 ff ff ff call 804840c <A> 8048446: b8 00 00 00 00 mov eax,0x0 804844b: c9 leave 804844c: c3 ret 804844d: 66 90 xchg ax,ax 804844f: 90 nop 

是的,是的,这是未定义的行为 ,因为您正在使用未初始化的variables1

但是,在x86架构2上这个实验应该可以工作 。 该值不会从堆栈中“擦除”,并且由于它没有在B()初始化,所以如果堆栈帧是相同的,那么相同的值应该仍然存在。

我冒昧猜测,因为int a不在void B()内部使用 ,所以编译器优化了代码,并且从未写入堆栈中的那个位置。 尝试在B()添加一个printf – 它可能会工作。

另外,编译器标志(即优化级别)也可能会影响这个实验。 尝试通过将-O0传递给gcc来禁用优化。

编辑:我刚刚用gcc -O0 (64位)编译你的代码,实际上,程序打印5,就像熟悉调用堆栈所期望的那样。 事实上,它甚至没有-O0 。 一个32位版本可能会有不同的performance。

免责声明:永远不要在“真实”的代码中使用这样的东西!

1 – 下面有关于这是官方的“UB”,还是仅仅是不可预测的争论。

2 – 也是x64,也可能是其他所有使用调用堆栈的架构(至less有一个使用MMU)


让我们来看看它不起作用的原因。 这是最好的32位,所以我会用-m32编译。

 $ gcc --version gcc (GCC) 4.7.2 20120921 (Red Hat 4.7.2-2) 

我使用$ gcc -m32 -O0 test.c编译(禁用了优化)。 当我运行这个,它打印垃圾。

$ objdump -Mintel -d ./a.out

 080483ec <A>: 80483ec: 55 push ebp 80483ed: 89 e5 mov ebp,esp 80483ef: 83 ec 28 sub esp,0x28 80483f2: 8b 45 f4 mov eax,DWORD PTR [ebp-0xc] 80483f5: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80483f9: c7 04 24 c4 84 04 08 mov DWORD PTR [esp],0x80484c4 8048400: e8 cb fe ff ff call 80482d0 <printf@plt> 8048405: c9 leave 8048406: c3 ret 08048407 <B>: 8048407: 55 push ebp 8048408: 89 e5 mov ebp,esp 804840a: 83 ec 10 sub esp,0x10 804840d: c7 45 fc 05 00 00 00 mov DWORD PTR [ebp-0x4],0x5 8048414: c9 leave 8048415: c3 ret 

我们看到在B ,编译器保留了0x10字节的堆栈空间,并且在[ebp-0x4]处初始化int avariables为5。

A中,编译器把int a放在[ebp-0xc] 。 所以在这种情况下,我们的局部variables不会在同一个地方结束! 通过在A添加一个printf()也会导致AB的堆栈框架相同,并打印55

这是未定义的行为 。 未初始化的局部variables具有不确定的值,使用它将导致未定义的行为。

一个重要的事情要记住 – 不要依赖这样的事情, 不要在真正的代码中使用它! 这只是一个有趣的事情(甚至不总是这样),而不是一个function或类似的东西。 想象一下,你自己试图找出由这种“特征”产生的错误 – 恶梦。

顺便说一句。 – C和C ++充满了这种“function”,这里是伟大的幻灯片: http : //www.slideshare.net/olvemaudal/deep-c所以,如果你想看到更多类似的“function”,了解什么在引擎盖下,以及如何工作,只是看这个幻灯片 – 你不会后悔,我敢肯定,即使是大多数有经验的C / C ++程序员可以从这方面学到很多东西。

在函数A ,variablesa没有被初始化,打印它的值会导致未定义的行为。

在某些编译器中, A的variablesaB中的variablesa位于相同的地址,所以它可能会打印5 ,但是不能依赖未定义的行为。

gcc -Wall filename.c编译你的代码你会看到这些警告。

 In function 'B': 11:9: warning: variable 'a' set but not used [-Wunused-but-set-variable] In function 'A': 6:11: warning: 'a' is used uninitialized in this function [-Wuninitialized] 

在c打印未初始化的variables导致未定义的行为。

6.7.8节初始化C99标准说

如果具有自动存储持续时间的对象未被显式初始化,则其值是不确定的。 如果具有静态存储持续时间的对象未被明确初始化,则:

 — if it has pointer type, it is initialized to a null pointer; — if it has arithmetic type, it is initialized to (positive or unsigned) zero; — if it is an aggregate, every member is initialized (recursively) according to these rules; — if it is a union, the first named member is initialized (recursively) according to these rules. 

EDIT1

作为@Jonathon Reinhart如果您通过使用-O标志gcc-O0禁用优化,那么您可能会得到输出5。

但这并不是一个好主意,永远不要在生产代码中使用它。

-Wuninitialized这是一个有价值的警告你应该考虑这一个你不应该禁用或跳过这个警告,导致生产中的巨大损害像在运行守护进程中导致崩溃。


EDIT2

深C幻灯片解释为什么结果是5 /垃圾。从这些幻灯片添加这些信息,稍作修改,使这个答案更有效。

案例1:没有优化

 $ gcc -O0 file.c && ./a.out 5 

也许这个编译器有一个重用的命名variables池。 例如variablesa被使用并且在B()被释放,那么当A()需要一个整数a时候就会得到一个variables会得到相同的内存位置。 如果你把B()的variables重命名为b ,那么我认为你不会得到5

案例2:优化

当优化器启动时,可能会发生很多事情。在这种情况下,我猜测对B()的调用可以被忽略,因为它没有任何副作用。 另外,如果A()main()被内联,我不会感到惊讶,即没有函数调用。 (但是由于A ()具有链接器可见性,所以为了防止另一个目标文件想要与函数链接,还必须创build函数的目标代码)。 无论如何,如果您优化代码,我怀疑打印的值是别的。

 gcc -O file.c && ./a.out 1606415608 

垃圾!