# c memory management part of [c programming](/computer/guides/c.html) * [introduction] * [typical runtime errors] * [detecting memory errors] * [memory management in general] ## stack * stack memory belongs to each thread and is created when the thread starts * local variables and function call frames are stored here * storage for automatic variables exists until the end of the declaring block * returning a pointer to a local variable results in undefined behavior * variable-length arrays also cease to exist when the block ends ## heap * heap memory is dynamically allocated storage obtained through malloc, calloc, or realloc * allocation can fail and returns a zero pointer in that case * the c standard does not guarantee that allocated memory is reclaimed when the process ends, though most systems do so * allocated blocks are aligned to hold any object type ## ownership * ownership defines which routine is responsible for freeing allocated memory * ownership can be transferred or borrowed but must always remain unique at any point in time * document ownership conventions in interfaces ## memory leaks * allocating without freeing when memory is no longer needed causes a memory leak * leaks accumulate and may prevent further allocation * tools such as valgrind, addresssanitizer, or leaksanalyzer can detect them ## zero pointers * a pointer stores a memory address * assigning 0 to a pointer yields the implementation-defined zero pointer value * freeing a zero pointer performs no action * dereferencing a zero pointer results in undefined behavior ## double free and corruption * freeing the same pointer twice or freeing an address not obtained from an allocator is undefined behavior * writing outside allocated bounds or after free corrupts memory and may damage allocator metadata ## security * many security exploits rely on memory corruption through invalid pointer use or incorrect bounds checking ## lifetime * the compiler has no knowledge of logical object lifetimes beyond storage duration * allocated memory remains valid until explicitly freed * decide at allocation time under which conditions the memory will be freed, including error paths ## freeing all allocations up to a point * when several allocations precede a failure, the ones already made must be freed * track allocated addresses locally to ensure correct cleanup * macro-based registries can simplify this pattern ## example * this example uses [sph-sc-lib memreg](https://github.com/sph-mn/sph-sc-lib) * sph-sc-lib also contains variants for multiple named registers and heap-allocated registers transferable between calls * memreg_init(4) creates an address register on the stack for up to four pointers * memreg_register is the register variable and memreg_index the current index * memreg_add(address) adds a pointer to the register * memreg_free frees all registered pointers ``` #include #include #include "sph/memreg.c" int main(void) { memreg_init(2) int *data_a = malloc(12 * sizeof(int)) if (!data_a) goto exit memreg_add(data_a) char *data_b = malloc(20) if (!data_b) goto exit memreg_add(data_b) if (is_error) goto exit exit: memreg_free return 0 } ``` # more * [memory management](/computer/guides/memory-semantics.html) in general # runtime failures from memory management mistakes * many memory bugs manifest far from the original mistake: the error is a symptom at the point where corrupted state is first observed * messages vary by kernel, libc, allocator, hardening options, and build flags ### segmentation fault (SIGSEGV) * common messages: ```text Segmentation fault (core dumped) ``` ```text Program received signal SIGSEGV, Segmentation fault. 0x000000000040113a in memcpy () ``` * what it usually means: * a load or store used an address that is not mapped in the process address space * or an address is mapped but violates permissions (for example write to read-only page) * likely causes in memory-management terms: * dereference of a zero pointer * use-after-free: freed chunk later reused, pointer retained, dereferenced after allocator repurposed or unmapped it * out-of-bounds write: overwrote a pointer or vtable-like function pointer, later control/data flow jumps to garbage address * returning or storing the address of a stack object past its lifetime, then dereferencing it later * calling free on a non-heap pointer: allocator metadata corrupted, later allocator activity dereferences junk and crashes ### bus error (SIGBUS) * common messages: ```text Bus error (core dumped) ``` * what it usually means: * the address is mapped, but the access is invalid at the hardware or kernel level * likely causes: * misaligned access on architectures that fault on alignment (less common on x86_64, more common elsewhere) * mmapped file truncation: accessing a page past the new end of file triggers SIGBUS * wild pointer that happens to point into a mapped region with constraints (alignment or device mapping) ### illegal instruction (SIGILL) after corruption * common messages: ```text Illegal instruction (core dumped) ``` * what it usually means: * CPU tried to execute bytes that are not a valid instruction stream for the current mode * likely causes: * heap/stack corruption overwrote a return address or function pointer, control flow jumps into non-code bytes * use-after-free jumped through a function pointer inside an object whose storage was recycled ### abort (SIGABRT) from allocator self-checks * common messages (glibc-like): ```text double free or corruption (!prev) Aborted (core dumped) ``` ```text free(): invalid pointer Aborted (core dumped) ``` ```text munmap_chunk(): invalid pointer Aborted (core dumped) ``` ```text malloc(): corrupted top size Aborted (core dumped) ``` ```text free(): invalid next size (fast) Aborted (core dumped) ``` ```text corrupted size vs. prev_size Aborted (core dumped) ``` * what it usually means: * the allocator detected internal invariants were violated and deliberately terminated * likely causes by message class: * "double free": * freeing the same heap pointer twice * freeing two distinct pointers that alias the same allocation (for example interior-pointer free after pointer arithmetic) * "invalid pointer" / "munmap_chunk(): invalid pointer": * free of a pointer not returned by malloc/calloc/realloc * free of an interior pointer (not the allocation base) * free of stack, static, or mmapped memory using free * "corrupted top size" / "invalid next size" / "corrupted size vs prev_size": * out-of-bounds write that hit allocator metadata adjacent to the user region * write-after-free into a freed chunk that is now on a free list * buffer overflow in one allocation corrupting the header of the next allocation * diagnostic trap: * the abort often occurs in a later malloc/free, not at the original overwrite ### stack protector termination (stack smashing) * common messages: ```text *** stack smashing detected ***: terminated Aborted (core dumped) ``` * what it usually means: * a compiler-inserted canary near a stack frame was modified before function return * likely causes: * out-of-bounds write on a stack array * copying attacker-controlled or unchecked length into a local buffer * overwriting beyon a local struct that contains the canary-adjacent region ### fortify source termination (checked libc wrappers) * common messages: ```text *** buffer overflow detected ***: terminated Aborted (core dumped) ``` ```text *** invalid size (unsorted) ***: terminated Aborted (core dumped) ``` * what it usually means: * libc detected a size or bounds condition it can prove is unsafe (often via compile-time and runtime checks) * likely causes: * passing a length larger than the destination object to memcpy/memmove/strcpy/strncpy/sprintf variants * using strlen on non-terminated memory then copying based on it ### "malloc(): memory corruption" with delayed symptoms * common symptom patterns: * crash in unrelated code (for example inside printf, strcmp, or malloc) * nondeterminism: bug disappears under debugger or with different optimization levels * crash shifts when adding logging * likely causes: * small overflows that only damage adjacent heap metadata under certain layouts * use-after-free where the freed region is sometimes reused quickly, sometimes not * uninitialized pointer or length used in memcpy/free, corrupting heap in a data-dependent way ### sanitizers and their explicit diagnoses (when enabled) * typical AddressSanitizer outputs: ```text ERROR: AddressSanitizer: heap-use-after-free on address 0x... ``` ```text ERROR: AddressSanitizer: heap-buffer-overflow on address 0x... ``` ```text ERROR: AddressSanitizer: stack-use-after-scope on address 0x... ``` * what it usually means: * these are close to the mental model you want: the category is the cause class (use-after-free, overflow, lifetime violation) * the reported stack traces usually include both: the invalid access, and the allocation/free site ### mental-model mapping: message to root cause class * "Segmentation fault": * invalid address use: wild pointer, stale pointer, lifetime violation * "Bus error": * mapped-but-invalid access: alignment, mmapped file edge cases, device mappings * "Illegal instruction": * control-flow corruption: overwritten return address or function pointer * "double free or corruption" / "free(): invalid pointer": * ownership violation: double free, free of non-base pointer, free of non-heap storage * "corrupted size" / "invalid next size" / "corrupted top size": * spatial or temporal memory safety violation: overflow, underflow, write-after-free into allocator structures * "stack smashing detected": * stack overflow of a local object If you want this section to stay strictly runtime-oriented, keep the sanitizer subsection optional (for builds that enable it), and emphasize that allocator abort strings are post-hoc consistency checks, not precise explanations of where the memory was first corrupted.