2025-12-28

c memory management

part of c programming

  • [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

    • 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

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:
Segmentation fault (core dumped)
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:
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:
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):
double free or corruption (!prev)
Aborted (core dumped)
free(): invalid pointer
Aborted (core dumped)
munmap_chunk(): invalid pointer
Aborted (core dumped)
malloc(): corrupted top size
Aborted (core dumped)
free(): invalid next size (fast)
Aborted (core dumped)
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:
*** 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:
*** buffer overflow detected ***: terminated
Aborted (core dumped)
*** 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:
ERROR: AddressSanitizer: heap-use-after-free on address 0x...
ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
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.