In a systems programming interview, one of the fastest ways to separate an “Application Developer” from a “Systems Architect” is to ask where a specific variable lives. In the embedded and semiconductor world, memory is a finite resource. Understanding the ELF (Executable and Linkable Format) layout is critical for debugging stack overflows, memory leaks, and boot-time initialization failures.
1. The Anatomy of a Process
When your C/C++ code is compiled and linked, it is organized into distinct logical segments.
A. The .text Segment (Code)
This is where your compiled machine instructions reside.
-
Properties: Read-only. In many embedded systems, this stays in NOR Flash and is executed in place (XIP).
B. The .data Segment (Initialized Globals)
Global and static variables that have a pre-defined value (e.g., int x = 10;).
-
Storage: Copied from Flash to RAM during the C-startup code (
crt0).
C. The .bss Segment (Uninitialized Globals)
Global and static variables that are not initialized (e.g., int y;).
-
The “BSS” Trick: To save space in the binary, the ELF file doesn’t store zeros for these. It only stores the total size of the BSS. The startup code zeroes out this RAM region before
main()is called.
2. The Dynamic Duo: Stack vs. Heap
The “Top” and “Bottom” of the RAM are usually reserved for the Stack and the Heap, which grow toward each other.
-
The Stack: * Handles local variables, function return addresses, and parameters.
-
Managed automatically by the CPU (Stack Pointer).
-
Architect’s Warning: In kernel mode or RTOS threads, the stack is often fixed and small (e.g., 4KB or 8KB). A deep recursion or a large local array (
char buf[1024]) can trigger a Stack Overflow, corrupting the heap or other thread data.
-
-
The Heap:
-
Used for dynamic allocations (
malloc,new). -
Managed by the programmer (and the C-library allocator).
-
Architect’s Warning: High-speed embedded systems often forbid heap usage after initialization to avoid non-deterministic latency and fragmentation.
-
3. The “Gotcha” Interview Questions
Q: Where does a static variable inside a function live?
A: It lives in the .data or .bss segment, NOT on the stack. Even though its scope is limited to the function, its lifetime is the duration of the program.
Q: What happens if you return a pointer to a local variable?
A: This is “Undefined Behavior.” The pointer points to a stack address that will be overwritten as soon as the next function is called.
Q: How can you tell if a pointer is pointing to the Stack or the Heap at runtime?
A: You compare the pointer’s address to the current Stack Pointer ($SP$). If the address is higher (on most architectures), it’s likely on the stack. Architects often use “Linker Symbols” (like _stack_top and _heap_start) to perform these boundary checks in safety-critical code.
4. Summary Table: Memory Segment Characteristics
| Segment | Variable Type | Initialized? | Lifetime |
| .text | Functions / Constants | Yes | Permanent |
| .data | int x = 5; (Global/Static) |
Yes | Permanent |
| .bss | int x; (Global/Static) |
No (Zeroed) | Permanent |
| Stack | int x; (Inside Function) |
No | Scope-based |
| Heap | malloc / new |
Depends | Manual |
Architect’s Interview Tip
When discussing memory layout, always mention the C-Startup code (crt0). Mentioning that you know the hardware/firmware must manually copy .data from Flash to RAM and zero out .bss before main() starts shows that you understand the “bare-metal” reality of the system.
In the next article, we dive into the most powerful and dangerous tool in the C-language: Pointer Arithmetic, Type Punning, and Alignment.
Ready for Article 3?
