Hello All!

I am a little confused about the memory allocated for an executable. Now, say for example, this is my code:

int foo = 1;

int main()
{
    const char* bar = "hello world";
    int num;

    return 0;   
}

Now, the "hello world" part would be stored in the read-only code segment part, this would need 11 bytes, thus contributing to the size of the executable.

Now, foo would be in the data segment and num would be in the stack segment once the executable is loaded into memory. However, my question is, how is information about these variables stored in the binary, such that memory is allocated on the stack and the data segment for these variables?

Any inputs would be helpful.
Thanks!

This is all platform dependent, that is different platforms may do different things, however, the "hello world" would not be stored in the read-only code segment, it is data not code so most likely it would be stored in the read-only data segment. The initial value for foo, 1, would also be stored in the read-only data segment.

The size of the data segment where foo is stored will be in the executable image somewhere so that the memory can be allocated as the program starts.

Both bar and num are on the stack (on implementations that use a stack) memory will be reserved for them in the stack frame created when main is called, this size is stored in the program code.

Thanks a lot Banfa! Could you please provide names of books or maybe some online articles where i can read more about this?

Thanks!

However, my question is, how is information about these variables stored in the binary, such that memory is allocated on the stack and the data segment for these variables?

Of course, this is highly dependent on the platform, so I can only explain in generic terms. The executable file will have two main parts: code-segment and data-segment. These will be loaded by the operating system into a read-only section of the virtual address space of that executable. Usually, there would be a standard fixed translation of addresses such that code can access read-only data-segments, such that addresses of read-only data can be hard-coded into the executable's binary code (say, for example, that all address starting with 0x40000000 in the executable will be mapped to the read-only data-segment by the OS). This is not necessarily so, but you get the idea.

For the static memory (for global non-const variables), the size of that data-segment can be determined at compile-time. So, this size value will either be stored in the executable such that the OS can retrieve it while loading the executable, and subsequently, allocate a chunk of read-write memory for that in the virtual address space. Again, the same kind of mechanism will map hard-coded addresses of these variables into their proper physical address in RAM (this is what "virtual address space" means).

For the stack, the overall size of the stack (the maximum size it can grow to) is something that you can configure with compiler options, the default is usually somewhere between 2Mb and 8Mb nowadays, but depends on the system, the target platform and the compiler. Again, because this size is fixed at compile-time, it is stored in the executable and the OS allocates a section of read-write memory for it in the virtual address space. When the program runs, with a fluctuating use of the stack memory, the "allocation" of stack memory is nothing more than moving a pointer forward and back in the stack address space. There is no actual allocation of memory, and that's why stack-based memory is much faster to allocate and use then dynamic memory (or "freestore"). And, finally, if you move too far in the stack (using too much of it), then it will "overflow" (i.e., attempts to access memory after the end of the stack-memory segment pre-allocated by the OS), and then it will usually crash hard (most operating systems will detect this and terminate the application with extreme prejudice), but it could also trigger other behaviors on more isoteric operating systems (maybe automatically growing the stack space, or be left undetected, and thus corrupting other programs).

For more information, the wikipedia pages on the ELF format (Linux/Unix executables), the PE format (Windows executables), and the Mach-O format (Mac executables). The ELF is probably the best documented on (and it actually better designed compared to the others). By and large, they are pretty much all the same, a header with the information needed (sizes of various blocks) and pointers to sections in the file where to find the memory to be loaded and things like that. Most of the complications come about with the dynamic linking information (like exported symbols list from a DLL or .so file). The loading process that the OS does will also vary between the platforms, most dramatically between Unix (or POSIX) and Windows because Unix/Linux systems actually do partial merging of virtual address spaces, while Windows only does back-and-forth translations, with limited capabilities (I know this only because doing code that is distributed over several DLLs is very different from distributing code over several .so files (the Unix/Linux equivalent of DLLs)). Here is also a very nice article on ELF and shared objects, highly recommended as it really explains a lot.

Edited 4 Years Ago by mike_2000_17: edit

Here's a hands on example.

(A good reference to look at first:) http://stackoverflow.com/questions/1395591/what-is-exactly-the-base-pointer-and-stack-pointer-to-what-do-they-point

I modified the C code a little:

int foo=1;
int main(int argumentCount, char *arguments[])
{
    const char* bar = "Hello, Daniweb!";
    char *firstArg;
    int num;
    num=1;
    num=argumentCount;
    num=0;
    firstArg=arguments[1];
    firstArg=(char *)0;

    return num;   
}

I compiled using MinGW: gcc -S blah.c
This is a 32-bit system. YMMV.
Assembly:

    .file   "blah.c"
    .globl  _foo
    .data
    .align 4
_foo:
    .long   1
    .def    ___main;    .scl    2;  .type   32; .endef
    .section .rdata,"dr"
LC0:
    .ascii "Hello, Daniweb!\0"
    .text
    .globl  _main
    .def    _main;  .scl    2;  .type   32; .endef
_main:
LFB0:
    .cfi_startproc
    pushl   %ebp
    .cfi_def_cfa_offset 8
    .cfi_offset 5, -8
    movl    %esp, %ebp
    .cfi_def_cfa_register 5
    andl    $-16, %esp
    subl    $16, %esp
    call    ___main
    movl    $LC0, 12(%esp)
    movl    $1, 8(%esp)
    movl    8(%ebp), %eax
    movl    %eax, 8(%esp)
    movl    $0, 8(%esp)
    movl    12(%ebp), %eax
    movl    4(%eax), %eax
    movl    %eax, 4(%esp)
    movl    $0, 4(%esp)
    movl    8(%esp), %eax
    leave
    .cfi_restore 5
    .cfi_def_cfa 4, 4
    ret
    .cfi_endproc
LFE0:

As you can see, the "Hello" text is in the .data segment and not the .text segment where the code is.

Now let's look at what this actually does:
(The .cfi commands are pre-assembler directives and can be ignored for our purposes.)
EBP is the frame pointer.
It gets saved with pushl %ebp
ESP is the stack pointer.
At first they are set equal to each other by movl %esp, %ebp
It looks like ESP is being byte-aligned to 16 bytes with andl $-16, %esp. Can someone confirm this?
Now ESP is subtracted from to make room for local data by subl $16, %esp

EAX is an all around working register. It's very useful.

The argumentCount (or argc) is at 8 bytes after the frame pointer. It's represented by "8(%ebp)"
The arguments (or argv) is at 12 bytes after the frame pointer. It's represented by "12(%ebp)"

The bar variable is located at 12 bytes after the stack pointer. It's represented by "12(%esp)"
The num variable is located at 8 bytes after the stack pointer. It's represented by "8(%esp)"
The firstArg variable is located at 4 bytes after the stack pointer. It's represented by "4(%esp)"

Let's walk through starting just after call ___main

movl $LC0, 12(%esp) loads the pointer from the "Hello World" (now "Hello, Daniweb") into bar.
movl $1, 8(%esp) puts a "literal" 1 in num.
movl 8(%ebp), %eax moves the argumentCount into the EAX working register.
movl %eax, 8(%esp) moves the new contents of the EAX working register into num.
movl $0, 8(%esp) puts a "literal" 0 into num.
movl 12(%ebp), %eax moves the argument array pointer into EAX.
movl 4(%eax), %eax moves the pointer to arguments[1] into EAX.
movl %eax, 4(%esp) moves the what is now inside EAX (the very first argument in the argument array) into firstArg
movl $0, 4(%esp) puts a literal $0 inside firstArg, effectively firstArg=NULL;
movl 8(%esp), %eax puts the num variable into EAX. This is the first part of return num;
leave restores the EBP and ESP pointers.
ret exits the function with EAX as the return code.

Edited 4 Years Ago by DeanMSands3

This question has already been answered. Start a new discussion instead.