Hello! Welcome to your first lesson in our course on C pointers.
Given your extensive background in automotive and embedded systems, you've undoubtedly worked with memory at a low level, whether through debuggers like Trace32 or by managing communication buffers. Our goal in this course is to formalize and deepen that practical knowledge, starting from the ground up to build a truly expert-level understanding of pointers.
Today's lesson addresses the very first learning outcome: explaining how memory is organized in C programs. Before we can talk about pointers, which hold memory addresses, we first need a clear mental model of the memory they point into. We'll explore the different segments of a program's memory—the code, data, heap, and stack—and understand the role each one plays. This foundation is critical for writing safe, efficient, and bug-free C code, especially in resource-constrained embedded environments.
The Virtual Address Space: A Program's View of Memory
When your C program runs, the operating system gives it a private virtual address space. This is a clean, contiguous block of memory that your program sees, which the OS then maps to actual physical RAM. This abstraction is fundamental, and it's what allows mechanisms like shared memory for IPC to work—the OS simply maps the same physical memory region into the virtual address space of multiple processes.
This virtual address space is not a monolithic block; it's organized into several distinct segments.

As you can see, the layout is generally divided into:
- Static Memory: Segments whose size is fixed when the program is compiled and loaded (Text, Data, BSS).
- Dynamic Memory: Segments that can grow and shrink at runtime (Heap, Stack).
Let's examine each of these segments in detail.
Static Memory Segments
These segments are allocated when your program is loaded into memory and persist for the entire duration of the program's execution.
1. Text Segment (or Code Segment)
This is the simplest segment. It contains the compiled machine code of your program—the executable instructions themselves.
- Characteristics: It is typically marked as read-only. Attempting to write to the text segment will cause a segmentation fault. This prevents a program from accidentally or maliciously modifying its own instructions.
- In an Embedded Context: In many microcontroller systems, the text segment, along with other read-only data, is stored in non-volatile memory like Flash or ROM.
2. Data Segment (Initialized Data)
This segment stores global variables, static global variables, and static local variables that are explicitly initialized with a non-zero value.
- Characteristics: This segment is read-write, as the values of these variables can be changed at runtime.
- Example:
int global_var = 100;
A part of this segment can also be read-only (.rodata) to store const-qualified global variables.
- Example:
const char* greeting = "Hello";The string literal"Hello"itself would be in a read-only part of memory.
3. BSS Segment (Uninitialized Data)
BSS stands for "Block Started by Symbol," an old assembler directive. This segment holds all global variables and static variables that are either uninitialized or initialized to zero.
- Characteristics: Before the
main()function is called, the C runtime environment guarantees that every variable in this segment is initialized to zero (orNULLfor pointers). This segment is also read-write. - Example:
int global_counter;orstatic int local_static_val = 0;
The key reason for having a separate BSS segment is an optimization for the executable file size. The file doesn't need to store a large block of zeros; it only needs to record the size of the BSS segment. The loader then allocates and zero-initializes that amount of memory when the program starts.
The following reading provides a great overview of these segments with clear code examples.
The following article, 'Memory Layout in C' from Tutorialspoint, provides a detailed breakdown of each memory segment. It includes helpful diagrams and code snippets to illustrate where different types of variables are stored.
Please read the sections 'Text Segment', 'Initialized Data Segment', and 'Uninitialized Data Segment'. Pay close attention to the code examples showing how global and static variables are placed in either the initialized data or BSS segment depending on their initialization.
Dynamic Memory Segments
These segments change size during the program's execution to handle runtime memory needs.
4. The Heap
The heap is used for manual, dynamic memory allocation. When your program needs a block of memory whose size might not be known at compile time or whose lifetime needs to extend beyond a single function call, you allocate it from the heap.
- Allocation: You use functions like
malloc(),calloc(), andrealloc()to request memory from the heap. - Deallocation: You are responsible for freeing this memory using
free()when you are done with it. Failure to do so results in a memory leak. - Growth: The heap typically begins at the end of the BSS segment and grows "upwards" towards higher memory addresses.
5. The Stack
The stack is used to manage function calls. It stores local variables, function parameters, and the return address (where execution should resume after the function completes).
- Structure: The stack operates on a Last-In, First-Out (LIFO) basis. Each time a function is called, a new stack frame is "pushed" onto the stack. When the function returns, its frame is "popped" off.
- Management: Allocation and deallocation on the stack are automatic and managed by the compiler. You don't have to do anything manually.
- Growth: The stack is typically located at the higher end of the address space and grows "downwards" towards lower memory addresses, towards the heap.
- Limitation: The stack has a fixed size. If you have too many nested function calls or declare very large local variables, you can exhaust the stack space, leading to a stack overflow.
This next resource is tailored for an embedded context and provides an excellent comparison of the heap and stack, which is crucial for performance and stability in your line of work.
Memory Management in C/C++: The Heap and the Stack
The article 'Memory Management in C/C++: The Heap and the Stack' discusses these concepts from an embedded systems perspective. It highlights the practical trade-offs between using the stack and the heap.
Please read the sections 'The Heap Segment', 'The Stack Segment', and 'The Heap vs. Stack'. Focus on the pros and cons of each, their relative speed, and how they behave in a multi-threaded environment—a key concept for complex systems like those using AUTOSAR OS.
Test your understanding!
Consider the following C code snippet. For each variable (g_system_state, g_uninit_buffer, DEFAULT_TIMEOUT, p_data, local_counter, and the memory p_data points to), identify which memory segment it will be stored in.
#include <stdlib.h>
int g_system_state = 1; // RUNNING
char g_uninit_buffer[1024];
const int DEFAULT_TIMEOUT = 100;
void process_data() {
int local_counter = 0;
int* p_data = (int*)malloc(sizeof(int) * 256);
// ... use p_data and local_counter
free(p_data);
}
int main() {
process_data();
return 0;
}
Show answer
g_system_state: Initialized Data Segment. It's a global variable with an explicit non-zero initial value.g_uninit_buffer: BSS Segment. It's an uninitialized global variable. The runtime will zero it out beforemainstarts.DEFAULT_TIMEOUT: Read-Only Data Segment (often part of the initialized data segment, or sometimes even the text segment depending on compiler optimizations). It's aconstglobal, so it's placed in read-only memory.local_counter: Stack. It's a local variable inside theprocess_datafunction. It's created whenprocess_datais called and destroyed when it returns.p_data: Stack. The pointer variablep_dataitself is a local variable, so it resides on the stack frame ofprocess_data.- The memory
p_datapoints to: Heap. Themallocfunction allocates a block of memory (enough for 256 integers) from the heap and returns its starting address, which is then stored inp_data.
Visualizing the Complete Picture
Let's tie this all together. The image below shows a piece of C code and maps its variables directly to the memory segments we've just discussed. It also includes the output of the size command, a common tool in Linux/Unix environments that reports the size of the text, data, and bss segments of a compiled program. This provides a tangible link between your source code, the compiled executable, and the memory layout at runtime.

Your experience with MISRA-C and functional safety (ISO 26262) standards directly relates to this memory model. For instance, MISRA guidelines often restrict or forbid dynamic memory allocation (malloc/free) in safety-critical code precisely because of the risks associated with heap management (e.g., memory leaks, fragmentation, non-deterministic allocation times). Understanding that these functions operate on the heap clarifies the "why" behind such rules.
Conclusion
In this lesson, we've established a solid mental model of how a C program organizes memory. This is the landscape where pointers operate.
Here are the key takeaways:
- Text Segment: Stores your read-only executable code.
- Data Segment: Stores initialized global and
staticvariables. - BSS Segment: Stores uninitialized global and
staticvariables, which are automatically zeroed out. - Heap: A region for manual, dynamic memory allocation (
malloc,free). It's flexible but requires careful management. - Stack: A LIFO structure for local variables and function call management. It's fast and automatically managed but has a limited size.
A variable's storage duration (static, automatic, dynamic) and scope are the primary factors that determine which segment it resides in.
In our next lesson, we will build directly on this foundation. Now that we know where variables live, we'll learn how to work with their addresses. We will introduce pointer variables, the address-of operator (&), and the dereference operator (*), which are the core tools for pointer manipulation.