根据this电影(大约38分钟),如果我有两个具有相同本地变量的函数,它们将使用相同的空间。所以下面的程序应该打印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
答案 0 :(得分:130)
是的,是的,这是未定义的行为,因为您使用的是未初始化的 1 变量。
但是,在x86架构 2 上, 此实验应该可以正常工作 。该值不会从堆栈中“擦除”,并且因为它未在B()
中初始化,所以如果堆栈帧相同,则仍应存在相同的值。
我冒昧地猜测,因为int a
在void B()
内没有使用,编译器优化了该代码,并且从未写入5堆栈上的位置。尝试在printf
中添加B()
- 它可能会起作用。
此外,编译器标志 - 即优化级别 - 也可能会影响此实验。尝试通过将-O0
传递给gcc。
编辑:我刚刚使用gcc -O0
(64位)编译了代码,实际上,程序打印5,就像熟悉调用堆栈所期望的那样。实际上,即使没有-O0
,它也能正常工作。 32位版本可能表现不同。
免责声明:不要,永远在“真实”代码中使用这样的内容!
1 - 关于这是否正式为“UB”,或者只是不可预测,below正在进行辩论。
2 - 也是x64,可能是使用调用堆栈的所有其他架构(至少是带有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字节的堆栈空间,并将int a
的{{1}}变量初始化为5。
但是在[ebp-0x4]
中,编译器将A
放在int a
。所以在这种情况下,我们的局部变量没有最终在同一个地方!在[ebp-0xc]
中添加printf()
调用也会导致A
和A
的堆栈帧相同,并打印B
。
答案 1 :(得分:36)
未定义的行为。未初始化的局部变量具有不确定的值,使用它将导致未定义的行为。
答案 2 :(得分:12)
要记住一件重要的事情 - 不要依赖于类似的东西,而从不在实际代码中使用它!这只是一个有趣的事情(甚至并非总是如此),而不是一个特征或类似的东西。想象一下,你自己试图找到那种“特征”所产生的错误 - 噩梦。
顺便说一下。 - C和C ++充满了这种“功能”,这里有 GREAT 幻灯片: http://www.slideshare.net/olvemaudal/deep-c因此,如果你想看到更多类似的“功能”,了解它的内幕以及它是如何工作只是观看这个幻灯片 - 你不会后悔,我相信大多数经验丰富的c / c ++程序员可以从中学到很多东西。
答案 3 :(得分:7)
在函数A
中,变量a
未初始化,打印其值会导致未定义的行为。
在某些编译器中,a
中的变量A
和a
中的B
位于同一地址,因此可能会打印5
,但是,你不能依赖未定义的行为。
答案 4 :(得分:7)
使用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中打印未初始化的变量导致未定义的行为。
第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
As @Jonathon Reinhart如果您使用-O
标记gcc-O0
禁用优化,那么您可能会获得输出5.
但这并不是一个好主意,永远不要在生产代码中使用它。
-Wuninitialized
这是一个有价值的警告您应该考虑这个警告您不应该禁用或跳过此警告,这会导致生产中的巨大损失,例如在运行守护程序时导致崩溃。
<强> EDIT2 强>
Deep C幻灯片解释了为什么结果是5 /垃圾。从这些幻灯片中添加这些信息并进行微小修改,以使这个答案更有效。
案例1:没有优化
$ gcc -O0 file.c && ./a.out
5
也许这个编译器有一个池
它重用的命名变量。例如
变量a在
B()
中使用和发布,然后A()
需要时
它将获得整数名a
变量将获得相同的内存
地点。如果重命名变量
在B()
中,比如b
,我不认为
你会得到5
。
案例2:优化
当优化器启动时,可能会发生很多事情
这种情况我猜想可以跳过对B()
的调用
它没有任何副作用。另外,我不会感到惊讶
如果在A()
中内联main()
,即没有函数调用。 (但由于A
()
具有链接器可见性,因此该函数的目标代码仍然必须
创建以防其他目标文件想要链接
功能)。无论如何,我怀疑打印的价值会是
如果你优化代码,别的东西。
gcc -O file.c && ./a.out
1606415608
垃圾!