Question
In C, programmers are often taught to call free() for every block of memory allocated with malloc(). I want to understand the real consequences of not freeing memory in practice.
For example, consider this program:
#include <stdlib.h>
int main(void)
{
char *a = malloc(1024);
/* Do some arbitrary work with 'a' here. */
return 0;
}
If free(a); is omitted, what actually happens when the program exits? My assumption is that once the process terminates, the operating system reclaims the process memory anyway, so there is no lasting harm. Is that understanding correct?
I also have a second scenario. Suppose a program behaves somewhat like a shell, where users can define variables such as aaa = 123, and those variables are stored in dynamically allocated data structures for later use. In that kind of program, the memory may need to stay allocated for the entire lifetime of the process.
In such a design, is it acceptable for memory to be allocated and kept until the process exits, without being explicitly freed earlier? If that is considered poor design, what is the usual alternative?
Short Answer
By the end of this page, you will understand what happens to malloc()-allocated memory when a C program ends, why free() still matters even if the OS reclaims memory at process termination, and when keeping allocations for the whole program lifetime is a reasonable design choice.
Concept
In C, malloc() asks the runtime for a block of memory on the heap, and free() returns that block so it can be reused while the program is still running.
The key idea is this:
- During program execution, memory you do not
free()remains unavailable for reuse by your program. - When the process terminates, the operating system usually reclaims the process's memory resources.
So for a short-lived program like this:
#include <stdlib.h>
int main(void)
{
char *a = malloc(1024);
return 0;
}
on a normal operating system, the 1024 bytes do not remain permanently lost after the process exits. The OS cleans them up.
However, that does not mean skipping free() is always harmless.
Why free() still matters
1. Leaks matter while the program is alive
If a program runs for a long time, repeated allocations without corresponding frees will steadily increase memory usage.
2. free() expresses ownership and intent
Mental Model
Think of heap memory like renting storage boxes.
malloc()= rent a box- using the pointer = put things in the box
free()= return the box so you stop paying for it- program exit = the whole warehouse closes and all your boxes are cleared out
If you rent one box and the warehouse closes a second later, not returning the box yourself may not cause visible damage.
But if your program is like a long-running business and keeps renting new boxes without returning old ones, you eventually run out of space or waste resources.
For the shell-variable example, imagine a cabinet of records that must stay available the entire day. You do not throw each record away right after writing it. You keep it as long as it is useful, then discard it when it is no longer needed—or when the office closes.
Syntax and Examples
The core C functions are:
#include <stdlib.h>
void *malloc(size_t size);
void free(void *ptr);
Basic allocation and cleanup
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
char *buffer = malloc(1024);
if (buffer == NULL) {
return 1;
}
buffer[0] = 'A';
buffer[1] = '\0';
printf("%s\n", buffer);
free(buffer);
return 0;
}
This is the normal pattern:
- allocate memory
- check for
NULL
Step by Step Execution
Consider this example:
#include <stdlib.h>
int main(void)
{
char *a = malloc(1024);
if (a == NULL) {
return 1;
}
a[0] = 'X';
a[1] = '\0';
free(a);
return 0;
}
Here is what happens step by step:
-
char *a = malloc(1024);- The program asks for 1024 bytes from the heap.
- If successful,
apoints to the start of that memory block. - If allocation fails,
abecomesNULL.
-
if (a == NULL)- The program checks whether allocation succeeded.
-
a[0] = 'X'; a[1] = '\0';
Real World Use Cases
This concept shows up in many common situations.
Short-lived command-line programs
A utility that runs for 50 milliseconds and exits may technically leak memory at exit without causing system-wide damage. But it is still better style to clean up.
Long-running servers
A web server, database process, or background worker must free memory that is no longer needed. Otherwise memory usage grows over time.
Interactive shells and REPLs
A shell stores command history, variables, aliases, or parsed state. Some data legitimately lives for the whole session.
Compilers and parsers
Some compilers allocate many small objects while processing one file, then free everything together after the compilation unit is done.
Caches and registries
Programs sometimes intentionally retain memory for quick reuse. That is acceptable if the cache has a limit or matches the application's lifetime.
Embedded systems
In embedded software, memory is often much more constrained. Even small leaks can be serious, especially in software that must run continuously.
Real Codebase Usage
In real projects, developers focus less on "free immediately after malloc" and more on ownership and lifetime.
Common patterns
1. Create/destroy pairs
Functions are often designed in pairs:
Widget *widget_create(void);
void widget_destroy(Widget *w);
This makes memory ownership explicit.
2. Shutdown cleanup
Programs with global state often allocate during startup and release during shutdown.
int app_init(void);
void app_shutdown(void);
3. Container cleanup
If you store data in linked lists, hash tables, or trees, the container usually has a function to free every node.
void map_destroy(Map *map);
4. Early returns with cleanup paths
Real code often uses guard clauses and a shared cleanup section.
Common Mistakes
1. Assuming "the OS cleans it up" means leaks do not matter
That is only partly true.
- It is mostly true after process exit.
- It is false while the program keeps running.
Broken thinking:
while (1) {
malloc(1024); /* leak forever */
}
This will eventually exhaust memory.
2. Losing the pointer
If you overwrite the only pointer to an allocation, you cannot free it anymore.
char *p = malloc(100);
p = malloc(200); /* first allocation is now leaked */
free(p);
Avoid this by freeing before overwriting, or by using a separate pointer.
3. Freeing too early
Do not free memory while it is still needed.
char *p = malloc(10);
free(p);
p[0] = 'A'; /* undefined behavior */
After free(p), the pointer must not be used as valid storage.
Comparisons
| Situation | Is explicit free() needed? | Why |
|---|---|---|
| Short-lived program allocates once and exits | Usually not required for OS cleanup | The OS typically reclaims memory at process termination |
| Long-running loop keeps allocating | Yes | Memory usage grows during execution |
| Data needed for entire program lifetime | Not necessarily before shutdown | Lifetime matches the process lifetime |
| Data removed dynamically during runtime | Yes | Memory should be reclaimed when no longer needed |
| Library code | Yes | Libraries should not assume when the host process exits |
free() vs process exit cleanup
| Aspect |
|---|
Cheat Sheet
void *malloc(size_t size);
void free(void *ptr);
malloc()allocates heap memory.free()releases heap memory for reuse.free(NULL)is safe.- After
free(ptr), do not read or write throughptr. - Setting a pointer to
NULLafterfree()can help avoid accidental reuse. - If a process exits, the OS usually reclaims its memory.
- That does not make leaks harmless in long-running programs.
- Memory that is intentionally kept for the whole program lifetime is not automatically bad design.
- The important rule: each allocation should have a clear owner and lifetime.
Good pattern
char *p = malloc(100);
if (p == NULL) {
return 1;
}
/* use p */
free(p);
p = NULL;
FAQ
Is it a memory leak if the OS frees memory when the program exits?
Technically, memory that was never explicitly freed is still considered leaked by many tools, even if the OS reclaims it at process termination.
Is it okay not to free memory in main() right before returning?
In a tiny short-lived program, it often has no practical system impact. But explicit cleanup is still clearer and scales better as the code grows.
Should long-lived data structures be freed before exit?
If they truly live for the whole program lifetime, freeing them only at shutdown is reasonable. It is still helpful to have cleanup functions.
Why do leak-checking tools care if the OS reclaims memory anyway?
Because the tool is checking whether your program manages its own resources correctly, not whether the operating system can clean up after it.
What is the alternative for many related allocations with the same lifetime?
A common alternative is to use a memory pool or arena and free the whole group at once.
Do I need to free memory in a library even if the application will exit soon?
Yes. Library code should manage memory properly and should not assume anything about the host program's lifetime.
Can keeping memory until process exit be good design?
Yes, if the data is intentionally process-lifetime data, such as configuration, symbol tables, or shell session state.
Mini Project
Description
Build a small shell-like variable store in C. The program will let you create variables, update them, print them, and clean them up at the end. This demonstrates the key idea from the question: some data may intentionally live for the entire runtime, but you should still design a clear cleanup path.
Goal
Create a linked-list-based variable store where each variable remains available until explicitly cleared or until program shutdown.
Requirements
- Store variables as name/value pairs using dynamic memory.
- Support adding a new variable and updating an existing one.
- Print all stored variables.
- Free all allocated memory before the program exits.
Keep learning
Related questions
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C printf Format Specifier for bool: How to Print Boolean Values
Learn how to print bool values in C with printf, why no %b/%B specifier exists, and the common patterns to print true/false or 0/1.
Calling C or C++ from Python: Building Python Bindings
Learn the quickest ways to call C or C++ from Python, including ctypes, C extensions, Cython, and binding tools with practical examples.