Memory Layout of C Programs

Memory Layout of C Programs

A typical memory representation of a C program consists of the following sections.

  1. Text segment  (i.e. instructions)
  2. Initialized data segment 
  3. Uninitialized data segment  (bss)
  4. Heap 
  5. Stack 

Text Segment: The text segment in the memory layout of a running process is accurate. The text segment, also known as the code segment, is a section of a program in memory that contains the executable instructions of the program. It is where the machine code generated by the compiler is stored, and these instructions are executed by the CPU.

Additionally, your mention of placing the text segment below the heap and stack is generally correct. The text segment is typically positioned in memory before the heap and stack to prevent them from overwriting it. Placing it in this manner helps ensure the integrity of the executable code and prevents unintended memory corruption.

The text segment in a running process is often shareable among multiple instances of the same program to save memory. It’s also marked as read-only to prevent accidental modification of program instructions.

2 . Initialized Data Segment: The Initialized Data Segment, also known as the Data Segment, contains global and static variables that are initialized by the programmer. It’s not read-only, as variable values can be changed during runtime. This segment is further divided into initialized read-only and initialized read-write areas. For example, a global string-like char s[] = "hello world" and a statement-like int debug = 1 would be in the initialized read-write area. A statement const char* string = "hello world" places the string literal in the initialized read-only area and the character pointer variable in the initialized read-write area. Variables like static int i = 10 and global int i = 10 are stored in the data segment.

3. Uninitialized Data Segment: The Uninitialized Data Segment, often referred to as the “bss” segment, gets its name from an old assembler operator, “block started by symbol.” In this segment, the kernel initializes data to zero before the program starts running. It includes global and static variables that are either explicitly set to zero or lack explicit initialization in the source code. Examples of variables placed in the BSS segment include static int i; and int j;.

4. Stack: The Stack is a region in memory that traditionally grows in the opposite direction of the heap. It contains the program stack, which is a Last-In-First-Out (LIFO) structure, usually located in the higher parts of memory. On the x86 PC architecture, it grows towards address zero; on some other architectures, it grows in the opposite direction. A “stack pointer” register keeps track of the top of the stack and is adjusted each time a value is “pushed” onto the stack. Each function call corresponds to a “stack frame” that includes, at a minimum, a return address.

In the stack, automatic variables are stored, and it also holds information saved each time a function is called. When a function is called, the address to return to and information about the caller’s environment, such as machine registers, are saved on the stack. The newly called function allocates space on the stack for its automatic variables. This mechanism is how recursive functions in C can work, as each recursive call uses a new stack frame, preventing interference between variables from different instances of the function.

5. Heap: The Heap is the memory segment where dynamic memory allocation typically occurs. It starts at the end of the BSS segment and grows toward larger memory addresses. Memory allocation functions like malloc, realloc, and free manage the heap. These functions may use system calls like brk and sbrk to adjust the heap’s size. It’s important to note that the use of brk/sbrk and a single contiguous “heap area” is not mandatory for memory allocation; implementations may also be used mmap to reserve potentially non-contiguous regions of virtual memory. The Heap area is shared by all shared libraries and dynamically loaded modules within a process.

Examples:

The size(1) command reports the sizes (in bytes) of the text, data, and bss segments. ( for more details please refer man page of size(1) )

1. Check the following simple C program 


#include <stdio.h>
 
int main(void)
{
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960        248          8       1216        4c0    memory-layout

2. We can add one global variable in the program, and then check the size of bss (highlighted in red color).


#include <stdio.h>
 
int global; /* Uninitialized variable stored in bss*/
 
int main(void)
{
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
 960        248         12       1220        4c4    memory-layout

3. We can add one static variable which is also stored in bss.

#include <stdio.h>
 
int global; /* Uninitialized variable stored in bss*/
 
int main(void)
{
    static int i; /* Uninitialized static variable stored in bss */
    return 0;
}

4. We can initialize the static variable which can be stored in the Data Segment (DS)


#include <stdio.h>
 
int global; /* Uninitialized variable stored in bss*/
 
int main(void)
{
    static int i = 100; /* Initialized static variable stored in DS*/
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960         252         12       1224        4c8    memory-layout

5. We can initialize the global variable which can be stored in the Data Segment (DS)


#include <stdio.h>
 
int global = 10; /* initialized global variable stored in DS*/
 
int main(void)
{
    static int i = 100; /* Initialized static variable stored in DS*/
    return 0;
}
[narendra@CentOS]$ gcc memory-layout.c -o memory-layout
[narendra@CentOS]$ size memory-layout
text       data        bss        dec        hex    filename
960         256          8       1224        4c8    memory-layout

FAQ- Memory Layout of C Programs

Q1. What is memory layout in the C program?

Ans. The memory layout of a C program after being loaded into RAM typically includes six components: text segment, initialized data segment, uninitialized data segment, command-line arguments, stack, and heap.

Q2. What is the heap memory layout in C?

Ans. Heap memory is used for dynamic memory allocation in C and other programming languages. It typically begins immediately after the uninitialized data segment and grows upwards to higher memory addresses. The malloc() and calloc() functions in C are commonly used to allocate memory in the heap. These functions allow the program to request and manage memory dynamically at runtime, which is particularly useful when you need to work with data structures of varying sizes or when the memory requirements are not known at compile time.

Q3. What is the memory layout of arrays?

Ans. In multi-dimensional arrays, elements can be stored in two ways: column-major layout, where columns are contiguous in memory, or row-major layout, where rows are contiguous. This arrangement is also known as column-major order and row-major order, respectively. The choice depends on the programming language and can impact memory access efficiency.

Hridhya Manoj

Hello, I’m Hridhya Manoj. I’m passionate about technology and its ever-evolving landscape. With a deep love for writing and a curious mind, I enjoy translating complex concepts into understandable, engaging content. Let’s explore the world of tech together

Leave a Comment