C Stack vs Heap
C programs use memory in different ways depending on how long an object must live and who controls its storage. The stack is the usual home for local variables with automatic lifetime, while the heap is used for dynamically allocated objects requested with functions such as malloc. Understanding the difference helps you avoid dangling pointers, memory leaks, stack overflows, and confusing pointer bugs.
Overview: How Stack and Heap Memory Work
The terms stack and heap are common implementation names. The C standard describes storage duration more formally: automatic, allocated, static, and thread storage. In everyday C programming, local variables inside a function usually have automatic storage and are commonly placed on the call stack. Objects returned by malloc, calloc, or realloc have allocated storage and are commonly placed in the heap.
The stack is tied to function calls. When a function is called, the program creates space for that call’s local variables, parameters, return address, and bookkeeping data. When the function returns, that space is no longer valid for those local variables. This is fast because allocation and release follow a strict last-in, first-out pattern: the most recent function call is the first one to finish.
The heap is not tied to a single function call. A heap object remains alive until your program passes its pointer to free. That means a function may allocate memory and return a pointer to it, but it also means the program must have a clear owner responsible for releasing it. Heap allocation is more flexible than stack allocation, but usually slower and easier to misuse.
| Feature | Stack | Heap |
|---|---|---|
| Typical C source | int x; inside a block |
malloc(sizeof *p) |
| Lifetime | Until the block or function ends | Until free is called |
| Size decision | Usually compile time or block entry | Runtime |
| Failure mode | Too much may overflow the stack | Allocation may return NULL |
| Responsibility | Compiler and call flow | Programmer ownership |
Syntax
Stack variables are ordinary declarations. Heap variables require a pointer, an allocation call, a failure check, and a matching free.
int local = 10;
size_t count = 4;
int *items = malloc(count * sizeof *items);
if (items == NULL) {
return 1;
}
free(items);
items = NULL;
localis an automatic object. It is valid only while its block is active.itemsis a stack variable that stores an address. The array it points to is on the heap.malloc(count * sizeof *items)requests enough bytes forcountintegers without repeating the type name.items == NULLchecks whether allocation failed.free(items)releases the heap block. Setting the pointer toNULLprevents accidental reuse through that variable.
Examples
Stack memory inside a function
#include <stdio.h>
void print_stack_demo(void) {
int count = 3;
int scores[3] = {10, 20, 30};
printf("count = %d\n", count);
printf("scores[0] = %d\n", scores[0]);
printf("scores has %zu bytes\n", sizeof scores);
}
int main(void) {
print_stack_demo();
printf("function returned\n");
return 0;
}
Output:
count = 3
scores[0] = 10
scores has 12 bytes
function returned
count and scores are automatic local variables. They are created when print_stack_demo begins and stop being valid when it returns. The array does not need free because the stack frame is removed automatically.
Heap memory for a runtime-sized array
#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 5;
int *values = malloc(count * sizeof *values);
if (values == NULL) {
printf("allocation failed\n");
return 1;
}
for (size_t i = 0; i < count; i++) {
values[i] = (int)((i + 1) * (i + 1));
}
int sum = 0;
printf("values:");
for (size_t i = 0; i < count; i++) {
printf(" %d", values[i]);
sum += values[i];
}
printf("\nsum = %d\n", sum);
free(values);
return 0;
}
Output:
values: 1 4 9 16 25
sum = 55
The pointer variable values itself is local to main, but the five integers are in allocated storage. This distinction is important: leaving main would destroy the pointer variable, but it would not automatically call free for the heap block. In longer-running programs, forgetting that release becomes a memory leak.
A stack struct that owns heap memory
#include <stdio.h>
#include <stdlib.h>
typedef struct {
char *data;
size_t length;
} Buffer;
Buffer make_buffer(size_t length) {
Buffer b;
b.data = malloc(length + 1);
b.length = length;
if (b.data == NULL) {
b.length = 0;
return b;
}
for (size_t i = 0; i < length; i++) {
b.data[i] = (char)('A' + i);
}
b.data[length] = '\0';
return b;
}
int main(void) {
Buffer buffer = make_buffer(6);
if (buffer.data == NULL) {
printf("could not create buffer\n");
return 1;
}
printf("text = %s\n", buffer.data);
printf("length = %zu\n", buffer.length);
free(buffer.data);
return 0;
}
Output:
text = ABCDEF
length = 6
This example shows a common real pattern. The Buffer variable in main is a stack object, but one of its members points to heap memory. Returning the structure is safe because the pointer value is copied, and the pointed-to heap block remains alive. The caller must still release buffer.data.
Returning heap memory safely
#include <stdio.h>
#include <stdlib.h>
int *make_total(int a, int b) {
int *result = malloc(sizeof *result);
if (result != NULL) {
*result = a + b;
}
return result;
}
int main(void) {
int *total = make_total(40, 2);
if (total == NULL) {
printf("allocation failed\n");
return 1;
}
printf("total = %d\n", *total);
free(total);
total = NULL;
printf("pointer released\n");
return 0;
}
Output:
total = 42
pointer released
A function must not return the address of one of its local variables, because that variable stops existing when the function returns. This version allocates an int on the heap, stores the result there, and returns the pointer. The caller becomes the owner and releases it.
How It Works Step by Step
- When
mainstarts, the runtime enters a function call frame. Local variables such as pointers, counters, and small arrays are usually placed in that frame. - When another function is called, a new frame is placed above the current one. Its local variables are separate from the caller’s locals.
- When that function returns, its frame is removed. Any pointer to an object inside that frame becomes invalid, even if the address still appears to contain the old bytes.
- When
mallocis called, the allocator searches its managed heap area for a block large enough for the requested byte count. - If allocation succeeds,
mallocreturns a pointer to usable storage. If it fails, it returnsNULL. freegives the block back to the allocator. It does not erase every pointer copy, so using an old pointer afterfreeis still a bug.
Do not rely on exact addresses or on stack direction. Some systems grow the stack downward, some details vary by compiler and operating system, and optimization can keep values in registers. The dependable C-level rule is lifetime: automatic objects end with their block, and allocated objects end with free.
Common Mistakes
Returning the address of a stack local
A tempting but broken idea is to create a local variable and return &local. The pointer would refer to a dead object after the function returns. Allocate on the heap when the result must outlive the function, or let the caller pass storage into the function.
#include <stdio.h>
#include <stdlib.h>
int *make_answer(void) {
/* Wrong idea: int answer = 42; return &answer; */
int *answer = malloc(sizeof *answer);
if (answer != NULL) {
*answer = 42;
}
return answer;
}
int main(void) {
int *answer = make_answer();
if (answer == NULL) {
printf("allocation failed\n");
return 1;
}
printf("answer = %d\n", *answer);
free(answer);
return 0;
}
Output:
answer = 42
Using a pointer after free
After free(p), the heap block no longer belongs to you. Reading or writing through p is undefined behavior. If the pointer variable will remain in scope, assign NULL after freeing and design the program so only one owner frees the block.
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *number = malloc(sizeof *number);
if (number == NULL) {
printf("allocation failed\n");
return 1;
}
*number = 7;
printf("before free = %d\n", *number);
free(number);
number = NULL;
if (number == NULL) {
printf("number is no longer available\n");
}
return 0;
}
Output:
before free = 7
number is no longer available
Best Practices
- Use stack storage for small, fixed-size values that do not need to outlive the current function.
- Use heap storage when the size is known only at runtime, the object is large, or the object must outlive the function that creates it.
- Check every allocation result before dereferencing it.
- Write allocation sizes as
count * sizeof *pointerto avoid mismatches when the pointed-to type changes. - Give every heap allocation a clear owner and exactly one matching
free. - Do not return addresses of local variables, and do not store such addresses for later use.
- Avoid very large local arrays; prefer heap allocation when a buffer could be large or user-controlled.
- After freeing a long-lived pointer variable, set it to
NULLif it might be checked or reused later.
Practice Exercises
- Write a function that receives an array length, allocates a heap array of
double, fills it with halves such as0.5,1.0, and1.5, prints it, and frees it. - Create a small
Studentstructure on the stack with a heap-allocatednamemember. Print the name, then release the member before the program ends. - Rewrite a function that currently returns a pointer to a local array. Make the caller provide the array instead, or make the function allocate heap storage and document who must call
free.
Summary
- The stack is best for automatic local objects with simple, short lifetimes.
- The heap is best for dynamic size, large objects, or data that must live beyond one function call.
- A pointer variable may be on the stack while the object it points to is on the heap.
- Stack objects are released automatically; heap objects require
free. - Most stack versus heap bugs are lifetime bugs: dangling pointers, leaks, double frees, and use-after-free errors.
