简体   繁体   English

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

[英]Conflict between a Stanford tutorial and GCC

According to this movie (around minute 38), if I have two functions with the same local vars, they will use the same space. 根据这部电影(大约38分钟),如果我有两个具有相同本地变量的函数,它们将使用相同的空间。 So the following program, should print 5 . 所以下面的程序,应该打印5 Compiling it with gcc results -1218960859 . gcc结果编译它-1218960859 why? 为什么?

The program: 该程序:

#include <stdio.h>

void A()
{
    int a;
    printf("%i",a);
}

void B()
{
    int a;
    a = 5;
}

int main()
{
    B();
    A();
    return 0;
}

as requested, here is the output from the disassembler: 根据要求,这是反汇编程序的输出:

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

Yes, yes, this is undefined behavior , because you're using the variable uninitialized 1 . 是的,是的,这是未定义的行为 ,因为您使用的是未初始化的变量1

However, on the x86 architecture 2 , this experiment should work . 但是,在x86架构2上此实验应该可行 The value isn't "erased" from the stack, and since it's not initialized in B() , that same value should still be there, provided the stack frames are identical. 该值不会从堆栈中“擦除”,并且由于它未在B()初始化,因此如果堆栈帧相同,则仍应存在相同的值。

I'd venture to guess that, since int a is not used inside of void B() , the compiler optimized that code out, and a 5 was never written to that location on the stack. 我斗胆猜测,由于int a不内所用void B()编译器优化的代码出来,和一个5永远不会写入到堆栈上的该位置。 Try adding a printf in B() as well - it just may work. 尝试在B()添加printf - 它可能会起作用。

Also, compiler flags - namely optimization level - will likely affect this experiment as well. 此外,编译器标志 - 即优化级别 - 也可能影响该实验。 Try disabling optimizations by passing -O0 to gcc. 尝试通过将-O0传递给gcc来禁用优化。

Edit: I just compiled your code with gcc -O0 (64-bit), and indeed, the program prints 5, as one familiar with the call stack would expect. 编辑:我刚用gcc -O0 (64位)编译你的代码,实际上,程序打印5,就像熟悉调用堆栈所期望的那样。 In fact, it worked even without -O0 . 实际上,即使没有-O0也能工作。 A 32-bit build may behave differently. 32位版本可能表现不同。

Disclaimer: Don't ever, ever use something like this in "real" code! 免责声明:不要永远, 永远使用这样的“真正的”代码!

1 - There's a debate going on below about whether or not this is officially "UB", or just unpredictable. 1 - 关于这是否是正式的“UB”,或者只是不可预测, 下面正在进行辩论。

2 - Also x64, and probably every other architecture that uses a call stack (at least ones with an MMU) 2 - 也是x64,可能是使用调用堆栈的每个其他架构(至少有一个MMU)


Let's take a look at a reason why it didn't work. 让我们来看看一个原因, 没有奏效。 This is best seen in 32 bit, so I will compile with -m32 . 这在32位中最好看,所以我将使用-m32编译。

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

I compiled with $ gcc -m32 -O0 test.c (Optimizations disabled). 我用$ gcc -m32 -O0 test.c编译(禁用了优化)。 When I run this, it prints garbage. 当我运行它时,它打印垃圾。

Looking at $ objdump -Mintel -d ./a.out : 看看$ 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    

We see that in B , the compiler reserved 0x10 bytes of stack space, and initialized our int a variable at [ebp-0x4] to 5. 我们看到在B ,编译器保留了0x10字节的堆栈空间,并将我们的int a初始化为[ebp-0x4] int a变量为5。

In A however, the compiler placed int a at [ebp-0xc] . 但是,在A中,编译器在[ebp-0xc]处放置int a So in this case our local variables did not end up at the same place! 所以在这种情况下,我们的局部变量并没有在同一个地方结束! By adding a printf() call in A as well will cause the stack frames for A and B to be identical, and print 55 . 通过在A添加printf()调用也会导致AB的堆栈帧相同,并打印55

It's undefined behavior . 这是未定义的行为 An uninitialized local variable has an indeterminate value, and using it will lead to undefined behavior. 未初始化的局部变量具有不确定的值,使用它将导致未定义的行为。

One important thing to remember - don't ever rely on something like that and never use this in real code! 要记住一件重要的事情 - 不要依赖类似的东西, 永远不要在真正的代码中使用它! It's just an interesting thing(which even isn't always true), not a feature or something like that. 这只是一个有趣的事情(甚至并非总是如此),而不是一个特征或类似的东西。 Imagine yourself trying to find bug produced by that kind of "feature" - nightmare. 想象一下,你自己试图找到那种“特征”所产生的错误 - 噩梦。

Btw. 顺便说一句。 - C and C++ are full of that kind of "features", here is GREAT slideshow about it: http://www.slideshare.net/olvemaudal/deep-c So if you want to see more similar "features", understand what's under the hood and how it's working just watch this slideshow - you won't regret and i'm sure that even most of experienced c/c++ programmers can learn a lot from this. - C和C ++充满了那种“功能”,这里有关于它的幻灯片: http//www.slideshare.net/olvemaudal/deep-c因此,如果你想看到更多类似的“功能”,了解什么是在引擎盖下以及它如何工作只是观看这个幻灯片 - 你不会后悔,我相信大多数经验丰富的c / c ++程序员都可以从中学到很多东西。

In the function A , the variable a is not initialized, printing its value leads to undefined behavior. 在函数A ,变量a未初始化,打印其值会导致未定义的行为。

In some compiler, the variable a in A and a in B are in the same address, so it may print 5 , but again, you can't rely on undefined behavior. 在一些编译器中,变量aAaB在同一个地址,这样就可以打印5 ,但同样,你不能依赖未定义行为。

Compile your code with gcc -Wall filename.c You will see these warnings. 使用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]  

In c Printing uninitialized variable Leads to Undefined behavior. 在c中打印未初始化的变量导致未定义的行为。

Section 6.7.8 Initialization of C99 standard says 第6.7.8节C99标准的初始化说

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. 如果未显式初始化具有自动存储持续时间的对象,则其值不确定。 If an object that has static storage duration is not initialized explicitly, then: 如果未显式初始化具有静态存储持续时间的对象,则:

— 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 EDIT1

As @Jonathon Reinhart If you disable optimization by Using -O flag gcc-O0 then you might get output 5. 如@Jonathon Reinhart如果通过使用-O标志gcc-O0禁用优化,则可能会得到输出5。

But this is not at all good idea , never ever use this in production code. 但这并不是一个好主意,永远不要在生产代码中使用它。

-Wuninitialized this is one of the valuable warning You should consider this one You should not either disable or skip this warning that leads huge damage in production like causing crashes in while running daemons. -Wuninitialized这是一个有价值的警告你应该考虑这一个你不应该禁用或跳过这个警告,导致生产中的巨大损害,如在运行守护进程时导致崩溃。


Edit2 EDIT2

Deep C slides explained Why result is 5/garbage.Adding this information from those slides with minor modifications to make this answer little more effective. Deep C幻灯片解释了为什么结果是5 / garbage.从这些幻灯片中添加这些信息并进行微小修改,以使这个答案更有效。

Case 1: without optimization 案例1:没有优化

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

Perhaps this compiler has a pool of named variables that it reuses. 也许这个编译器有一个它重用的命名变量池。 Eg variable a was used and released in B() , then when A() needs an integer names a it will get the variable will get the same memory location. 例如变量,使用和释放B()则当A()需要一个整数名称a它将得到变量会得到同样的存储器位置。 If you rename the variable in B() to, say b , then I don't think you will get 5 . 如果你将B()的变量重命名为b ,那么我认为你不会得到5

Case 2: with optimization 案例2:优化

A lot of things might happen when the optimizer kicks in. In this case I would guess that the call to B() can be skipped as it does not have any side effects. 当优化器启动时可能会发生很多事情。在这种情况下,我猜测可以跳过对B()的调用,因为它没有任何副作用。 Also, I would not be surprised if the A() is inlined in main() , ie no function call. 另外,如果A()main()内联,即没有函数调用,我也不会感到惊讶。 (But since A () has linker visibility the object code for the function must still be created just in case another object file wants to link with the function). (但是由于A ()具有链接器可见性,因此必须仍然创建该函数的目标代码,以防另一个目标文件想要与该函数链接)。 Anyway, I suspect the value printed will be something else if you optimize the code. 无论如何,我怀疑如果您优化代码,打印的值将是其他内容。

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

Garbage! 垃圾!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM