0

I have the following C code:

#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
   int value = 5;
   char buffer_one[8], buffer_two[8];
   strcpy(buffer_one, "one"); /* Put "one" into buffer_one. */
   strcpy(buffer_two, "two"); /* Put "two" into buffer_two. */

   return 0;
}

From my knowledge of the stack, the buffer_one array should start at a higher adress than the buffer_two array, since the stack grows towards lower adresse and buffer_two is at the top of the stack. However, when i compiler the code using gcc and use GDB to step through the code, it tells me that the opposite is true:

eman@eman:~/Documents/CFiles/bufferOverflow/source$ gcc -g -o example overflow_example.c 
eman@eman:~/Documents/CFiles/bufferOverflow/source$ gdb -q example
Reading symbols from example...done.
(gdb) list
1   #include <stdio.h>
2   #include <string.h>
3   int main() {
4   char buffer_one[8];
5   char buffer_two[8];
6   
7   strcpy(buffer_one, "one"); /* Put "one" into buffer_one. */
8   strcpy(buffer_two, "two"); /* Put "two" into buffer_two. */
9   
10  return 0;
(gdb) break 9
Breakpoint 1 at 0x400571: file overflow_example.c, line 9.
(gdb) run
Starting program: /home/eman/Documents/CFiles/bufferOverflow/source/example 

Breakpoint 1, main () at overflow_example.c:10
10  return 0;
(gdb) print &buffer_one
$1 = (char (*)[8]) 0x7fffffffdd50
(gdb) print &buffer_two
$2 = (char (*)[8]) 0x7fffffffdd60
(gdb) 

What is going on here?

extra question: Why does the arrays take up 10 bytes when it is initialized with 8 bytes?

Carefullcars
  • 181
  • 4
  • 5
    Hint: not 10 bytes, but 0x10 bytes. – Will Jun 22 '16 at 12:47
  • @Will oh, 16 bytes? I thought each char takes up 1 byte of memory not 2 – Carefullcars Jun 22 '16 at 12:50
  • It's hard to call it wrong since there's no standard for what's right. – user3528438 Jun 22 '16 at 12:54
  • Compiler can arrange local variables in function as it wishes. So, this test doesn't really tell you anything. Try comparing addresses to variables in other functions. – user694733 Jun 22 '16 at 12:54
  • 2
    @Carefullcars, what makes you think there's a stack? If there is a stack, what makes you think there's a correct direction in which it should grow? If there's a stack, what makes you think a function's local variables receive addresses as if they were pushed onto the stack in the order of their declaration? ***None*** of that is mandated by C. – John Bollinger Jun 22 '16 at 12:55
  • @Carefullcars because of [*alignment*](http://stackoverflow.com/questions/672461/what-is-stack-alignment) in this case 16 bytes. If you change them to `char buffer_one[11];` you'll see they still take 16 bytes. – Weather Vane Jun 22 '16 at 13:10
  • @Olaf, yes, that's part of what I said. – John Bollinger Jun 22 '16 at 13:25
  • @JohnBollinger I read (assembly language for the x86 processor) and it told me as much... – Carefullcars Jun 22 '16 at 13:25
  • @Will: Which means writing the whole program would have to be in Assember. And local variables are typically not "pushed" onto the stack (even iff ther is a stack). But all this completely depends on the target platform. – too honest for this site Jun 22 '16 at 13:25
  • @Carefullcars, what part of your x86-assembly-reading skills inform you about how a C compiler is supposed to use the features of the hardware it is building for? Your question presumes that there is a specific way it should do so, but nothing in the C language standard supports that proposition, neither in general nor in the specific details you are asking about. – John Bollinger Jun 22 '16 at 13:31
  • 1
    Assuming an underlying x64 architecture and, in your case, the gcc compiler and a linux-based operating system, the stack is a `reverse access` data map. So the allocation will be performed towards the highest logical address, and the access will be performed towards the lowest logical address. Anyway, there's no concept of stack in C standards as far as I know. – pah Jun 22 '16 at 13:32

1 Answers1

4

Several things:

  1. The compiler is not required to lay out variables in the order in which they were declared (or any other specific order). Members of a struct instance are laid out in the order declared, although there may be padding bytes between each member (see 2).

  2. The platform may require that objects be aligned on specific addresses (for example, addresses that are evenly divisible by 8 or 16). Thus, it's possible for there to be unused bytes between objects. If the addresses of buffer_one and buffer_two differ by 16, that doesn't mean that the compiler set aside 16 bytes for buffer_one, it means there are 8 bytes of padding between the end of buffer_one and the beginning of buffer_two, and the behavior on attempting to read/write those bytes is undefined. In practical terms, it means your code can tolerate a small buffer overflow in buffer_one with no ill effects, but you don't want to rely on that.

  3. You cannot rely on the ordering between objects to be meaningful. You certainly cannot rely on it being repeatable across implementations.

  4. Although you'll be hard-pressed to find a C implementation that doesn't use one, the language definition doesn't require the use of a runtime stack, nor does it require that the stack grow upwards (towards increasing addresses) or downwards (towards decreasing addresses). That's strictly an implementation detail.

John Bode
  • 119,563
  • 19
  • 122
  • 198
  • Great answer, clears up a lot! I was following books which implied that a C compiler will always add objects to the stack frame in the same way(probably for simplification). I guess i have to disassemble each C program if i really want to understand how it sets up stack frames(if the stack exists <.<). – Carefullcars Jun 22 '16 at 14:15