Answer: Generally, every program we write contains instructions, initialized and uninitialized data, variables local to a function and instructions for dynamically allocated storage. Once the program is done with writing we, then, compile it to generate a default output “a.out”, an executable linkable format (ELF) file, stored on secondary storage. This ‘a.out’ file is binary file of our C program and is ready to execute. This is organised into several segments. In order to see which different segments comprise it and size of each segment, run the ‘size’ command on it. For example, say, for segments.c program,
gcc segments.c size ./a.out
this outputs something like this, as given below,
text data bss dec hex filename 1283 496 16 1795 703 ./a.out
Note the head ‘dec’ gives total size requirement of your C program. Now. let’s explore these segments individually to see which segment comprises what. ‘Text segment’ is where all executable instructions are stored; ‘data segment’ contains all initialized data viz., global and static data. And what do you think about where uninitialized data go? BSS (Block started by symbol) segment doesn’t actually contain the uninitialized data except a note for the their size requirement.
Stack segment is created dynamically on demand where local variables of a function, return address and values of registers are stored. Block of memory is allocated from heap when first call to malloc() is made.
You might wonder why default output file ‘a.out’ is organised into segments? Actually, this organisation gives loader convenience to load the images of these segments into the process’s address space. Loader firstly copies text segment and doesn’t worry about it as this segment typically neither grows in size nor in value. Then it copies the image of data segment into process’s address space. In order To allocate bss segment, loader takes the note of size requirement from the executable then obtains a block of that size for uninitialized data storage. At this point, both BSS and data segment refers to jointly as data segment. This is because a segment, in OS memory management terms, is simply a range of virtual addresses so, these segments coalesced. Data segment is typically the largest segment.
We still need some memory space for local variables, temporaries, parameters passing in procedures. A stack segment is allocated for this requirement. this segment is created at run time. Let’s, now, look into how C organises this runtime data structure.
A stack segment contains single stack data structure. As we already know about it through ours’ past programming experience that it’s dynamic area of memory and is implemented as last-in-first-out queue. Thus we have only valid operations with stack are either to push values on the top of stack or retrieve them by popping off from top of the stack. A push operation augments stack while pop reduces it. A function can access variables local to a calling function via parameters or global pointers. The runtime maintains a pointer, often in a register which is usually called sp (stack-pointer), which indicates the current top of the stack. Basically, stack is used for following three purposes:
1. It provides storage space for local variables declared inside a function. These are called ‘automatic variables’ in C programming.
2. Stack stores “housekeeping” information when a function call is made. This housekeeping information is known as “stack frame” which includes address from which function was called in order to return to when called function is finished execution, parameters that don’t fit into registers and saved values of registers.
3. Stack is used as a “Scratch-pad area”- every time when program seeks for temporary storage, perhaps while computing some lengthy arithmetic expression, it can push partial results onto stack and popping them off when needed.
Just as stack segment grows dynamically on demand, so the data segment contains an object that does this is called “heap”. Heap provides storage when needed for dynamically allocated storage through call to ‘malloc()’ function and access to that block of allocated memory through pointer. Everything in heap is anonymous that is we can’t access it directly by name, only indirectly through pointer. malloc() and other friends viz., calloc(), realloc(), library call is just provide the way to obtain allocation from heap. The difference lies in that calloc() clears the block to ‘0’ before returning a pointer to it while realloc() is same as malloc() except that it resizes the block either by growing or shrinking it, or more often by copying the contents somewhere else and returning you a pointer to new location.
Since allocation from heap is done with calls to malloc(), therefore memory allocated from heap doesn’t have to be returned in the order it was acquired or it doesn’t have to be returned at all. Unordered call to malloc()/free() causes heap fragmentation. Heap must keep track of different regions, and whether they are in use or available to malloc(). One scheme is to have a linked list of available blocks (free store), and each block handed to malloc() is preceded by a size count that goes with it.
Heap memory space is limited by a pointer called “brk”. If your program accesses past the ‘brk’, it’ll be aborted. In order to malloc() more memory, simply shift ‘brk’ pointer away.
Notice that lowest part of the virtual address space is left unmapped; that is, this is within the process’s address space but has not been assigned to a physical address. Therefore any reference to this is illegal. This space is only a few Kbytes memory from address ‘0’ up. Though, this catches references from ‘null pointer’, and pointers that have small integer values.
Sanfoundry Global Education & Learning Series – 1000 C Tutorials.