82

根据这部电影(大约 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
4

5 回答 5

130

是的,是的,这是未定义的行为,因为您使用的是变量 uninitialized 1

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

我冒昧地猜测,由于int a没有内部使用void B(),编译器优化了该代码,并且 5 从未写入堆栈上的该位置。也可以尝试添加一个- 它可能printfB()起作用。

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

编辑:我刚刚用(64 位)编译了你的代码gcc -O0,实际上,程序打印了 5,正如熟悉调用堆栈的人所期望的那样。事实上,即使没有-O0. 32 位版本的行为可能会有所不同。

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

1 -关于这是否是正式的“UB”,或者只是不可预测的,下面正在进行一场辩论。

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变量 at初始化[ebp-0x4]为 5。

然而A,编译器放置int a[ebp-0xc]. 所以在这种情况下,我们的局部变量并没有在同一个地方结束!通过添加printf()调用A也将导致 和 的堆栈帧A相同B,并且 print 55

于 2013-10-24T07:09:12.783 回答
36

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

于 2013-10-24T07:06:49.880 回答
12

要记住的一件重要事情 -永远不要依赖这样的东西,也永远不要在实际代码中使用它!这只是一件有趣的事情(甚至并非总是如此),而不是一个功能或类似的东西。想象一下自己试图找到由那种“功能”产生的错误——噩梦。

顺便提一句。- C 和 C++ 充满了那种“特性”,这里有关于它的很棒的幻灯片: http : //www.slideshare.net/olvemaudal/deep-c所以如果你想看到更多类似的“特性”,了解什么是幕后和它是如何工作的只是看这个幻灯片——你不会后悔的,我相信即使是大多数有经验的 c/c++ 程序员也可以从中学到很多东西。

于 2013-10-24T09:40:30.947 回答
7

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

在某些编译器中,变量ainAainB在同一个地址中,因此它可能会 print 5,但同样,您不能依赖未定义的行为。

于 2013-10-24T07:06:13.033 回答
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.

编辑1

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

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

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


编辑2

Deep C幻灯片解释了为什么结果是 5/garbage。从这些幻灯片中添加此信息并稍作修改,以使此答案更有效。

案例一:没有优化

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

也许这个编译器有一个它重用的命名变量池。例如,变量 a 在 中使用和释放 B(),然后当A()需要一个整数名称a时,它将获得该变量将获得相同的内存位置。如果您将变量重命名为B()b那么我认为您不会得到5.

案例2:优化

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

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

垃圾!

于 2013-10-24T07:10:42.083 回答