Memory Layout in C: Stack, Heap, Data & Text Segments

Learn C memory layout, text, data, BSS, heap and stack segments, stack frames, dynamic memory allocation, process memory and memory management. Embedded Tech Development Academy (ETDA).

Memory Layout in C Programming: Stack, Heap, Data and Text Segments

Introduction to Memory Layout in C Programming

Memory management is one of the most important concepts in C programming, system programming, embedded C, operating systems, and firmware development. Unlike many high-level programming languages, C provides direct access to memory through pointers, arrays, structures, dynamic allocation, and address manipulation. Because of this low-level control, understanding how a C program is arranged in memory is essential for writing efficient and reliable software.

The memory layout of a C program describes how different types of program data and executable instructions are organized inside a process address space during execution. A typical process contains areas such as the text segment, read-only data, initialized data segment, BSS segment, heap, and stack. Each region has a different purpose, lifetime, protection attribute, and allocation mechanism.

Understanding process memory layout, virtual memory, memory addresses, static storage duration, automatic storage duration, dynamic memory allocation, stack frames, heap management, pointers, memory alignment, segmentation faults, buffer overflow, memory leaks, and stack overflow helps developers reason about what happens at the machine level when C code executes.

These concepts become especially important in embedded systems, where RAM and flash memory are limited and firmware must carefully control memory usage. Embedded Tech Development Academy (ETDA) provides practical technical training in C programming, embedded C, microcontrollers, and firmware development. Learners searching for a Top Embedded Training Institute in Bangalore can strengthen their programming and embedded-system fundamentals with assured placement support.

Understanding Process Memory in Linux

User Space and Kernel Space

In a Linux-based system, a process normally operates within user space, while the operating system kernel operates in a protected kernel address space.

User-space programs cannot directly access arbitrary kernel memory. Attempting an invalid memory access can result in a segmentation fault or another memory-protection failure.

User-Space Memory Layout

A simplified C process memory layout can be represented as:

Higher Addresses
+---------------------------+
|           Stack           |
|     Local variables      |
|     Function frames      |
+---------------------------+
|            ↓             |
|                           |
|            ↑             |
|           Heap            |
|     Dynamic allocation    |
+---------------------------+
|       BSS Segment         |
|   Uninitialized globals   |
+---------------------------+
|      Data Segment         |
|   Initialized globals     |
+---------------------------+
|      Read-Only Data       |
|    Constants / literals   |
+---------------------------+
|      Text / Code          |
|    Executable machine     |
|          code             |
+---------------------------+
Lower Addresses
Important Note About Memory Growth

The traditional diagram often shows the heap growing upward and the stack growing downward. This is a useful conceptual model, but the exact virtual-memory layout and growth behavior depend on the operating system, architecture, compiler, linker, and process configuration.

Text Segment or Code Segment

Purpose of the Text Segment

The text segment, also called the code segment, contains the executable machine instructions generated by the compiler and linker.

For example:

int add(int a, int b)
{
    return a + b;
}

The compiled machine instructions for add() are stored in the executable’s code region when the program is loaded.

Read-Only Protection

The text segment is normally mapped as readable and executable but not writable, helping prevent accidental modification of program instructions.

Code Sharing

On systems that support appropriate executable mappings, identical read-only code pages can potentially be shared between processes, reducing physical memory usage.

Read-Only Data and Constant Storage

String Literals and Constants

Read-only data is often placed in a region separate from writable program data.

Consider:

const int MAX_VALUE = 100;

char *message = "Hello";

The exact placement of objects depends on compiler and linker behavior, but string literals are typically stored in read-only memory.

Why Read-Only Storage Matters

Attempting to modify a string literal through an inappropriate pointer can result in undefined behavior.

A safer declaration is:

const char *message = "Hello";
Embedded Firmware Application

In embedded systems, constant tables and lookup data may be placed in Flash or ROM rather than consuming limited RAM. This is particularly important for microcontrollers with small SRAM capacity.

Data Segment

Initialized Global and Static Variables

The data segment stores writable global and static variables that have explicit non-zero initial values.

Example:

int counter = 10;

static int status = 1;

Both variables have static storage duration.

Lifetime of Data Segment Variables

These variables normally exist for the entire execution of the program.

Their lifetime differs from automatic local variables because they are created before normal function execution begins and remain available until program termination.

Embedded Memory Consideration

In embedded firmware, initialized global and static variables usually require both non-volatile storage for initial values and RAM for their runtime copies, depending on the linker and startup implementation.

BSS Segment

What Is BSS?

The BSS (Block Started by Symbol) segment contains uninitialized or zero-initialized global and static objects.

For example:

int total;

static int flag;

These variables are automatically initialized to zero before normal program execution.

BSS Versus Data Segment

Consider:

int count = 10;
int value;

count requires an explicit initial value and is normally associated with the initialized data area.

value is zero-initialized and is normally placed in BSS.

Why BSS Is Efficient

The executable does not need to store large sequences of zero bytes explicitly. Instead, the program image can record the required BSS size, and startup code can initialize that region to zero.

Heap Memory in C

Dynamic Memory Allocation

The heap is used for memory that is dynamically allocated during program execution.

C provides functions such as:

malloc()
calloc()
realloc()
free()

Example:

int *ptr = malloc(sizeof(int));

if (ptr != NULL)
{
    *ptr = 25;
    free(ptr);
}

Heap Allocation Lifecycle

The general lifecycle is:

malloc()
   ↓
Memory allocated
   ↓
Program uses memory
   ↓
free()
   ↓
Memory returned to allocator
Common Heap Errors

Incorrect heap management can cause:

  • Memory leaks
  • Double free
  • Use-after-free
  • Invalid memory access
  • Heap fragmentation
  • Allocation failure

In long-running applications, these problems can gradually consume available memory or corrupt allocator metadata.

Stack Memory in C

Purpose of the Stack

The stack is primarily used for automatic storage associated with function calls.

Example:

void calculate(void)
{
    int a = 10;
    int b = 20;

    int result = a + b;
}

The local variables generally have automatic storage duration and are associated with the function’s stack frame.

Stack Growth

A traditional architecture diagram often represents the stack as growing toward lower addresses.

However, the exact implementation depends on the processor architecture and ABI.

Stack Overflow

If a program consumes more stack space than available, a stack overflow can occur.

Common causes include:

  • Excessive recursion
  • Very large local arrays
  • Deep function-call chains
  • Large automatic structures
  • Incorrect embedded task stack sizing

Understanding a Stack Frame

What Is a Stack Frame?

When a function executes, the compiler and processor follow the platform’s calling convention to manage a function-call context, commonly represented as a stack frame.

A simplified conceptual frame may contain:

  • Local variables
  • Function parameters
  • Saved registers
  • Return address
  • Temporary compiler-generated data

Not every item must physically reside on the stack on every architecture. Registers and compiler optimizations can change the exact implementation.

Function Call Example

int add(int a, int b)
{
    int result = a + b;
    return result;
}

When add() is called, the calling convention determines how arguments, registers, the return address, and local storage are handled.

Function Return

After the function completes, its activation state is removed or becomes invalid according to the calling convention and generated machine code. Automatic local objects cease to exist when their scope and lifetime end.

Memory Layout and Embedded C

Flash and RAM Organization

Embedded systems commonly divide physical memory into Flash/ROM and SRAM.

A simplified microcontroller memory model may look like:

Flash / ROM
├── Program Code
├── Read-Only Constants
└── Initial Values

SRAM
├── Initialized Data
├── BSS
├── Heap
└── Stack

Linker Script and Memory Placement

The linker script determines how compiled sections are mapped into target memory regions.

Typical sections include:

.text
.rodata
.data
.bss
Startup Code

During system startup, initialization code commonly copies initialized data from its non-volatile load location to RAM and clears the BSS region before main() executes.

This makes understanding memory layout essential for microcontroller firmware, bootloaders, RTOS applications, device drivers, and bare-metal development.

Memory Leak

A memory leak occurs when dynamically allocated memory is no longer accessible but has not been released.

int *ptr = malloc(100 * sizeof(int));

If the program loses ptr without calling free(), the allocated memory may remain unavailable to the application.

Dangling Pointer

A pointer becomes dangling when it refers to an object whose lifetime has ended.

Buffer Overflow

Writing beyond the bounds of an allocated array can corrupt adjacent memory.

int data[5];

data[5] = 10;   // Invalid access

The valid indices are 0 through 4.

These errors can cause crashes, corrupted data, unpredictable execution, and security vulnerabilities.

Frequently Asked Questions

What is memory layout in C?

Memory layout describes how a running C program’s code, global data, dynamically allocated memory, and automatic function-call storage are organized in its process address space.

A simplified process layout contains the text segment, read-only data, initialized data, BSS, heap, and stack. The exact organization depends on the operating system, architecture, compiler, and linker.

Stack memory is primarily associated with automatic function-call storage and generally has a structured lifetime tied to scopes and calls. Heap memory is dynamically allocated at runtime and remains allocated until explicitly released or otherwise managed by the program.

BSS contains global and static objects that are zero-initialized or have no explicit initializer. The loader or startup code establishes the required zero-filled memory before normal program execution.

Embedded systems have limited RAM and Flash. Understanding stack usage, heap allocation, global data, BSS, code size, linker sections, memory addresses, and startup initialization helps developers design firmware that uses available memory efficiently and avoids runtime memory failures.

Conclusion

Understanding memory layout in C programming provides a direct connection between source code and the underlying execution environment. The text segment, read-only data, initialized data, BSS, heap, and stack each serve different purposes, and understanding their storage characteristics helps developers reason about object lifetime, memory consumption, pointer behavior, and program execution.

The stack is closely associated with function calls and automatic storage, while the heap supports dynamic memory allocation through functions such as malloc(), calloc(), realloc(), and free(). Global and static objects are generally placed in initialized data or BSS depending on their initialization state, while executable instructions are stored in the text segment. Concepts such as stack frames, virtual memory, memory alignment, linker sections, startup code, memory protection, buffer overflow, memory leaks, dangling pointers, and segmentation faults are therefore important parts of system-level C programming.

These concepts become even more important in embedded development because microcontrollers often operate with tightly constrained RAM, Flash, stack space, and heap resources. Developers working with Embedded C, ARM Cortex-M, 8051, STM32, bare-metal firmware, RTOS applications, device drivers, and bootloaders need to understand exactly how variables and program sections are mapped into physical memory. Embedded Tech Development Academy (ETDA) provides practical training that connects C programming concepts with embedded-system and firmware development. Learners looking for a Top Embedded Training Institute in Bangalore can develop these technical skills with assured placement support.

A strong understanding of memory organization also creates a foundation for advanced topics such as pointers, dynamic memory management, linker scripts, memory-mapped I/O, RTOS task stacks, heap implementations, interrupt handling, cache behavior, and embedded system optimization. Embedded Tech Development Academy (ETDA) focuses on practical embedded technologies and programming fundamentals so learners can understand how software interacts with hardware memory. For students seeking a Top Embedded Training Institute in Bangalore, learning memory management alongside hands-on C and embedded programming provides a technical foundation supported by assured placement support.

Author: ETDA Trainers
Experience: 10+ Years of Industry Experience in Embedded Systems, IoT, and Embedded C Programming