82

According to this movie (around minute 38), if I have two functions with the same local vars, they will use the same space. So the following program, should print 5. Compiling it with gcc results -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
Ismail Badawi
  • 36,054
  • 7
  • 85
  • 97
elyashiv
  • 3,623
  • 2
  • 29
  • 52
  • 41
    "they well use the same space" - that's incorrect. They might. Or they might not. And you can't rely on this either way. – Mat Oct 24 '13 at 07:06
  • 17
    i wonder what use of this has as an exercise, if one would use this in production code one would get shot. – AndersK Oct 24 '13 at 07:35
  • 12
    @claptrap Maybe to learn how the call stack works and understand what the computer is doing under the hood? People are taking this way too seriously. – Jonathon Reinhart Oct 24 '13 at 07:38
  • @JonathonReinhart still questionable use considering all the hoops you have to jump through – AndersK Oct 24 '13 at 07:39
  • 9
    @claptrap Again, it's a *learning exercise*. The "hoops you have to jump through" all make sense if you understand what is going on at the assembly level. I *seriously* doubt the OP has any intent of using something like this in a "real" program (if he does, he should be kicked!) – Jonathon Reinhart Oct 24 '13 at 07:43
  • @elyashiv, I think you should also run `objdump -Mintel -d ./a.out` and edit the question to show the disassembly of `A` and `B`, like I have in my answer. – Jonathon Reinhart Oct 24 '13 at 07:45
  • 1
    The output from the disassembler does not represent the `c` code you posted. – deed02392 Oct 24 '13 at 11:40
  • 12
    The example is misleading to the unsuspecting, because the two local variables have the same name; but this is irrelevant to what's going on: Only the number and type of variables matters. Different names should work exactly the same. – alexis Oct 24 '13 at 13:50
  • 1
    It's probably best to think of that tutorial as using a fictional naive compiler for C, rather than `gcc` specifically. The idea was to illustrate the concepts of mainstream procedural programming, using a suitable language already familiar to students, not to say "this is how a C compiler actually works". I don't think it's a bad instance of *lies-to-children*, since truly understanding the answer to this involves some bits from compiler construction, which is an advanced elective. In fact, thanks to this "misdirection" the OP now knows a bit more about C compilers than he otherwise would've. – millimoose Oct 24 '13 at 17:29
  • 3
    Closing a C question based on a Stanford lecture about incidental reuse of stack storage as a duplicate of a C++ question about purposely reusing that storage seems like a stretch. What if I know C but not C++? Then all the `cout` mumbo-jumbo in the other question wouldn't even mean anything to me. Not all questions that can be correctly answered with "It's undefined behavior" are duplicates. – blahdiblah Oct 24 '13 at 19:38
  • @blahdiblah Agreed. Voting to re-open. Does the OP need to put up a giant banner to fend off the "*undefined behavior*" nuts? (Not disagreeing with their answers, just pointing out that the OP probably doesn't care). – Jonathon Reinhart Oct 24 '13 at 22:29
  • @elyashiv Provided you realize this code is only an example of how a call stack works on x86, and a big no-no in "real code", I highly suggest you add a disclaimer to the top of your question that states this. – Jonathon Reinhart Oct 24 '13 at 22:48
  • I haven't watched the movie, but if it says that the variables *will* use the same space, it's wrong. They *may* use the same space. – Keith Thompson Oct 28 '13 at 14:50

5 Answers5

130

Yes, yes, this is undefined behavior, because you're using the variable uninitialized1.

However, on the x86 architecture2, this experiment should work. 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.

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. Try adding a printf in B() as well - it just may work.

Also, compiler flags - namely optimization level - will likely affect this experiment as well. Try disabling optimizations by passing -O0 to 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. In fact, it worked even without -O0. A 32-bit build may behave differently.

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.

2 - Also x64, and probably every other architecture that uses a call stack (at least ones with an 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.

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

I compiled with $ gcc -m32 -O0 test.c (Optimizations disabled). When I run this, it prints garbage.

Looking at $ 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.

In A however, the compiler placed int a at [ebp-0xc]. 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.

Community
  • 1
  • 1
Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
  • 5
    Even if it works once, it will not be reliable on some architectures - an interrupt preamble my blow away everything below the stack pointer at any time. – Martin James Oct 24 '13 at 07:36
  • 3
    @MartinJames Can you elaborate on what kind of architectures an interrupt would modify the user-mode stack? – Jonathon Reinhart Oct 24 '13 at 07:39
  • 1
    Doing a `printf` could further clobber the stack. Defining `volatile int a = 5;` should do the trick. – idoby Oct 24 '13 at 09:44
  • 4
    @JonathonReinhart - one example is any where there is no memory-management unit, no user/lernel mode distictions and so no hardware-switch to a different stack on interrupt. There are probably others where some data has to be pushed onto the interrupted task stack before there is a switch to kernel interrupt stack. – Martin James Oct 24 '13 at 11:09
  • 6
    So many up votes for an answer which doesn't even mention "undefined behavior". On top of it, it is also accepted. – BЈовић Oct 24 '13 at 11:22
  • Why is so much space reserved for an `int`? – deed02392 Oct 24 '13 at 12:05
  • 2
    @BЈовић: It doesn't contain the phrase "undefined behavior" but it mentions it by saying "using the variable uninitialized" and "quite possibly still be there". It's clear by the language that this behavior is coincidental and may or may not work depending on how your compiler works etc. In the spec, the layman concept of "may or may not work by coincidence" is called "undefined behavior" – slebetman Oct 24 '13 at 16:32
  • 25
    Also, it's accepted because it ***actually answers the question***. – slebetman Oct 24 '13 at 16:33
  • 3
    @slebetman No, it does not. The fact that he "analyzed" what gcc is doing in this particular case of undefined behavior does not prove the point. Since the standard does not specify what is the behavior, the binary is free to [release nasal demons](http://www.catb.org/jargon/html/N/nasal-demons.html) if it likes. Someone bellow even quoted the standard for this case, and got only few votes. – BЈовић Oct 24 '13 at 19:21
  • 8
    @BЈовић Did you watch any of the video? Look, everybody and their brother knows that you shouldn't do this in real code, and it invokes *undefined behavior*. That's not the point. The point is that a computer is a well-defined, predictable machine. On an x86 box (and probably most other architectures), with a sane compiler and potentially some code/flag massaging, this *will work* as expected. This code, along with the video is merely a *demonstration* of how the call stack works. If it bothers you that badly, I suggest you go elsewhere. Some of us curious types like to understand things. – Jonathon Reinhart Oct 24 '13 at 22:25
  • "*I'll add that in theory, it makes sense that this should work.*" -- I'm not sure that "in theory" is the right phrase here. In theory (i.e., as defined by the C standard), the behavior is undefined. *In practice*, they may happen to use the same space (and I can easily imagine an interrupt clobbering that space between the calls). – Keith Thompson Oct 28 '13 at 14:51
  • @KeithThompson Perhaps *in theory* isn't the right phrase. But according to the way an x86 binary executes, yes, this should work. I'll update my answer. You mentioned interrupts, but again, constraining my answer to x86 (and any other MMU system really), interrupts are of no concern to the user-mode stack. – Jonathon Reinhart Oct 29 '13 at 01:01
  • @JonathonReinhart: Would a compiler that happens to put local variables of similar functions at different addresses really violate "the way an x86 binary executes"? I'm think of something like this: http://en.wikipedia.org/wiki/Address_space_layout_randomization (though I'm not certain it applies). – Keith Thompson Oct 29 '13 at 01:05
  • @KeithThompson ASLR is really a loader technology - not a compiler one - and doesn't really apply to stack frames. Clearly from the example disassembly, the compiler is free to put local vars wherever it wants to. My only real point from this answer, was to show the OP that if the stack frames are truly identical (which as we've learned, goes deeper than just if they have the same # of locals), then he *should* see the expected value being printed. It was a learning exercise in how a call stack works. – Jonathon Reinhart Oct 29 '13 at 01:16
  • @JonathonReinhart: That most programs are run on machines which use a separate stack for interrupts does not imply that systems which do otherwise are particularly weird. If a machine can load DOS, then within DOS applications interrupts will very likely use the same stack as applications. – supercat Jul 31 '15 at 21:31
  • @supercat I understand that. I never implied that DOS was weird. But let's be real here. The *vast* majority of systems today are running an operating system that leverages the privilege separation and MMU of the processor, and hardware interrupts are delivered to a separate stack. I'm all for being pedantic, but you guys are putting words in my mouth. – Jonathon Reinhart Jul 31 '15 at 21:57
  • @JonathonReinhart: Computers have changed a lot since first I cut my teeth on them in the late 1970s. Back then, it was pretty likely that except when interrupts where disabled, anything beyond the stack was liable to get arbitrarily stomped. Nowadays most systems have no particular reason to stomp anything beyond the end of stack, but I don't know that they guarantee stuff won't get stomped. – supercat Jul 31 '15 at 22:02
  • @supercat On Linux, interrupts will absolutely be delivered to a kernel stack. The x86-64 ABI actually mandates a [red zone](https://en.wikipedia.org/wiki/Red_zone_(computing)) beyond the stack pointer, into which functions are allowed to write, without adjusting SP. If an interrupt were allowed to write here, this would never work. (This is why the kernel is compiled with `-fno-redzone`). – Jonathon Reinhart Jul 31 '15 at 22:58
  • @JonathonReinhart: Interesting. Is the basic idea to optimize leaf functions (since most function calls to be to leaf functions, that could obviously be helpful from a performance standpoint). If a function which uses the Red Zone will sometimes want to call other functions, it would have to modify the stack pointer in such case, right? – supercat Jul 31 '15 at 23:10
  • @supercat Yes, that is exactly my understanding; the red zone is a leaf function stack optimization. (Also http://stackoverflow.com/questions/25787408 -- and the flag is `-mno-red-zone`, my apologies.) – Jonathon Reinhart Jul 31 '15 at 23:21
  • 1
    *That's not the point. The point is that a computer is a well-defined, predictable machine. On an x86 box (and probably most other architectures), with a sane compiler and potentially some code/flag massaging, this will work as expected.* ... with modern optimizers that is not really the case anymore see my answer to [Is uninitialized local variable the fastest random number generator?](http://stackoverflow.com/a/31746063/1708801) – Shafik Yaghmour Sep 18 '15 at 23:58
36

It's undefined behavior. An uninitialized local variable has an indeterminate value, and using it will lead to undefined behavior.

Jens
  • 69,818
  • 15
  • 125
  • 179
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • 6
    To be more precise, using an unitialized variable of which the address is never taken is undefined behavior. – Jens Gustedt Oct 24 '13 at 08:27
  • @JensGustedt Nice comment. Do you have anything to say about the “The next example” section of http://blog.frama-c.com/index.php?post/2013/03/13/indeterminate-undefined ? – Pascal Cuoq Oct 24 '13 at 09:26
  • @PascalCuoq,this even seems to be an ongoing discussion in the standards committee. There are situations where inspecting memory that you get through a pointer makes sense, even if you can't know whether it is initialized or not. Simply making it undefined in all cases is too restrictive. – Jens Gustedt Oct 24 '13 at 09:36
  • @JensGustedt: How does taking its address cause using it to have defined behavior: `{ int uninit; &uninit; printf("%d\n", uninit); }` still has undefined behavior. On the other hand, you can treat any object as an array of `unsigned char`; is that what you had in mind? – Keith Thompson Oct 28 '13 at 14:49
  • @KeithThompson, no it is the other way round. Having a variable such that its address is never taken *and* it is not initialized leads to UB. Reading an indeterminate value by itself is not undefined behavior, the contents is just unpredictable. From 6.3.2.1 p2: *If the lvalue designates an object of automatic storage duration that could have been declared with the register storage class (never had its address taken), and that object is uninitialized (not declared with an initializer and no assignment to it has been performed prior to use), the behavior is undefined.* – Jens Gustedt Oct 28 '13 at 20:01
  • @JensGustedt: Interesting. That sentence is in C11, but not in C99. It seems like an odd restriction, but it's probably meant to make compile-time optimizations easier. – Keith Thompson Oct 28 '13 at 20:14
  • @KeithThompson, yes, it seems that there are processors that have some sort of "uninitialized state" for hardware registers that isn't matched by any in memory value. – Jens Gustedt Oct 28 '13 at 22:33
12

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.

cyriel
  • 3,522
  • 17
  • 34
7

In the function A, the variable a is not initialized, printing its value leads to undefined behavior.

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.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
  • 1
    The tutorial is 100% right, but whether the results on the original poster`s machine will be the same depends on the assembly generated by the compiler. As @JonathonReinhart pointed out the call to `B()` may be have been optimized away. – Lloyd Crawley Oct 24 '13 at 07:19
  • 1
    I have a problem with the "that tutorial is wrong" verbiage. Did you actually go watch the tutorial? It's not trying to teach you how to do crazy thing like this, but to demonstrate how the call stack works. In that case, the tutorial is completely correct. – Jonathon Reinhart Oct 24 '13 at 07:19
  • @JonathonReinhart I didn't watch the tutorial, thought this example was from the tutorial, I'll remove this part. – Yu Hao Oct 24 '13 at 07:23
  • @LloydCrawley I've removed the part about the tutorial. I know it's about stack architecture, that's what I meant by they are in the same address when it did print `5`, but apparently Jonathon Reinhart has a much much better explanation. – Yu Hao Oct 24 '13 at 07:53
7

Compile your code with gcc -Wall filename.c You will see these warnings.

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.

Section 6.7.8 Initialization of C99 standard says

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

As @Jonathon Reinhart If you disable optimization by Using -O flag gcc-O0 then you might get output 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.


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.

Case 1: without optimization

$ 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. If you rename the variable in B() to, say b, then I don’t think you will get 5.

Case 2: with optimization

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. Also, I would not be surprised if the A() is inlined in main(), ie no function call. (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). Anyway, I suspect the value printed will be something else if you optimize the code.

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

Garbage!

Gangadhar
  • 10,248
  • 3
  • 31
  • 50
  • 1
    Your logic in Edit 2, Case 1 is completely incorrect. That is not *at all* how it works. The name of the local variable means absolutely nothing. – Jonathon Reinhart Oct 30 '13 at 06:21
  • @JonathonReinhart As mentioned in answer I added this from deepc slides, please explain on which basis it is incorrect. – Gangadhar Oct 30 '13 at 08:15
  • 3
    There isn't any association between stack space and variable names. The example relies on the fact that conceptually the stack frame in the second function call will simply overlay the stack frame of the second function call. It doesn't matter what the names are, as long as both method signatures are the same, the same thing might happen. As others have pointed out, if it were in an embedded system and a hardware interrupt was serviced between the calls to A() and B(), the stack would contain random values. Old tools like Code Guard for Borland allowed writing zeros to stack before each call. – Dan Haynes Oct 30 '13 at 13:56
  • @DanHaynes Your comment convinces me.stack frame in the second function call may overlay the stack frame of the First function call as far as the variable type and function prototype same.Yes i too agree for there is nothing to do with variable names. – Gangadhar Oct 30 '13 at 18:55