Before writing code, you must understand memory layout.
-
Stack:
- Stores local variables (inside functions), function parameters, and return addresses.
- Fast allocation/deallocation (via adjusting the stack pointer).
- Memory is automatically managed—when a function returns, its local variables are gone.
- Size is limited (e.g., a few MBs on most systems).
-
Heap:
- Stores dynamically allocated memory (via
mallocin C orBox::newin Rust). - Manual allocation/deallocation (
freein C,dropin Rust or RAII for automatic). - Slower than stack because it needs bookkeeping and possibly more complex algorithms.
- Can grow much larger than stack (limited by system memory).
- Stores dynamically allocated memory (via
Kernel engineer insight:
- Stack is contiguous, easy to index, and extremely fast. Overflowing it is catastrophic (stack smashing).
- Heap is fragmented, managed by a memory allocator, and often comes with metadata overhead (think of
mallocheaders). - When debugging, seeing addresses can reveal whether memory is on stack or heap. Stack addresses are usually high in memory (on x86_64 Linux) and heap addresses are lower, but this depends on OS.
Here’s a minimal C program that prints stack vs heap addresses:
#include <stdio.h>
#include <stdlib.h>
int global_var = 42; // global/static memory
int main() {
int stack_var = 10; // stack variable
int *heap_var = malloc(sizeof(int)); // heap allocation
*heap_var = 20;
printf("Address of stack_var: %p\n", (void*)&stack_var);
printf("Address of heap_var: %p\n", (void*)heap_var);
printf("Address of global_var: %p\n", (void*)&global_var);
free(heap_var); // always free heap memory
return 0;
}Explanation for a kernel-level perspective:
| Variable type | Memory region | Lifetime | Allocation/deallocation |
|---|---|---|---|
stack_var |
Stack | Scope of function | Automatic (on function exit) |
heap_var |
Heap | Until freed | Manual (malloc + free) |
global_var |
Data segment | Entire program lifetime | Static allocation |
Tips kernel engineers would highlight:
- Always cast addresses to
(void*)when printing pointers. - Remember stack overflows are dangerous.
- Memory alignment may affect addresses.
- Comparing addresses can give insights about memory layout of the process.
Rust has similar memory concepts, but with safety guarantees and ownership rules.
fn main() {
let stack_var = 10; // stack variable
let heap_var = Box::new(20); // heap allocation using Box
static GLOBAL_VAR: i32 = 42; // global/static memory
println!("Address of stack_var: {:p}", &stack_var);
println!("Address of heap_var: {:p}", &*heap_var); // deref Box to get heap address
println!("Address of GLOBAL_VAR: {:p}", &GLOBAL_VAR);
}Rust fundamentals:
Box::newallocates memory on the heap.stack_varis automatically deallocated whenmainends.- Rust automatically frees heap memory when
Boxgoes out of scope (RAII), unlike manualfreein C. - Ownership system ensures no double-free or dangling pointers, which are common in C kernel development mistakes.
Kernel-level wisdom in Rust:
- Rust prevents use-after-free bugs at compile time.
- Stack vs heap debugging in Rust is almost identical to C (you still look at addresses), but memory safety is guaranteed.
- You can still observe memory layout, but you cannot arbitrarily manipulate memory like in C (unless using
unsafe).
-
Memory safety vs speed trade-offs
- Stack: fast but limited.
- Heap: flexible but slower and fragmented.
-
Memory alignment
- Stack and heap addresses are aligned; misaligned access can crash CPUs.
-
Lifetime management
- C: Manual (
malloc/free). - Rust: Automatic via ownership, lifetimes, and RAII.
- C: Manual (
-
Debugging and security
- Stack addresses can reveal stack layout for buffer overflow attacks.
- Heap fragmentation can lead to subtle bugs.
-
Print addresses for learning, not production
- Kernel engineers often examine addresses during debugging or kernel crash dumps.
-
Observing memory segments
- Kernel memory layout: text/code, data (globals), heap, stack, mmap regions.