Memory Management in Constrained Systems
Stack vs. heap, memory-mapped I/O, and the bug classes interviewers actually probe for.
Questions
Easy / Med / Hard
Your accuracy
An embedded or systems program manages memory far more explicitly than application code, and the space it has to work with is often measured in kilobytes rather than gigabytes -- which is exactly why the mistakes here matter more than they would in a web service with virtual memory and a garbage collector cleaning up after you.
The stack and the heap are both RAM, used two completely different ways. The stack grows and shrinks automatically as functions are called and return -- it holds local variables and stack frames, allocation and deallocation are essentially free (just moving a pointer), and its lifetime is scoped: the moment a function returns, its stack memory is gone, whether or not you still have a pointer to it. The heap is memory you request explicitly (malloc in C) and must release explicitly (free); its lifetime is whatever you make it, which is powerful and is also the entire source of two categories of bug: a memory leak (allocated, never freed, so usable memory shrinks over the program's life) and a use-after-free or double free (freed, then still touched, or freed twice). On a device with no virtual memory and no OS to reclaim leaked pages when the process exits, a slow leak in code that's supposed to run for months is not a curiosity, it eventually crashes the device.
A pointer to a stack variable that's returned or stored past the function's return is a dangling pointer the instant the function returns -- the memory is still physically there, so it often appears to work, right up until something else reuses that stack space and the old data is gone. This is one of the most common junior-level embedded bugs, precisely because it doesn't fail immediately.
Memory-mapped I/O is how software talks to hardware on most embedded platforms: a hardware register -- a UART's status flag, a GPIO pin's output state -- is given a fixed memory address, and reading or writing that address reads or writes the hardware directly, through the exact same load/store instructions used for ordinary memory. This is precisely why those addresses must be accessed through a volatile-qualified pointer: the compiler has no way to know the value can change on its own (a peripheral setting a status bit) or that a write has a side effect beyond storing a value (an interrupt-clear register, say), and without volatile it's free to optimise the access away entirely.
Alignment and padding matter more here than in application code. A struct's members are typically padded so each one starts at an address that's a multiple of its own size, because many processors either can't read a misaligned value at all or do so much slower via multiple bus accesses. That padding means sizeof(struct) is often larger than the sum of its members' sizes -- a fact that trips people up the first time they memcpy a struct expecting the packed size, and it's why reordering a struct's fields (largest first) can shrink it.