2025-10-18

c memory management

part of c programming

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