The malloc function in C is used to dynamically allocate memory during the execution of a program. It returns a pointer to the allocated memory. However, malloc can fail if there is insufficient free memory available. So it‘s important to check if malloc succeeded or failed. Here are some ways to check for a malloc error in C.
Check if the returned pointer is NULL
The easiest way is to check if malloc returned a NULL pointer, which indicates allocation failure:
int* p = (int*)malloc(100 * sizeof(int));
if (p == NULL) {
printf("Error: malloc failed\n");
return 1;
}
Here, p points to the allocated memory. If malloc failed, it would return NULL. So we simply check if p is NULL to detect failure.
While NULL return value is the most common indicator, malloc can also fail in other ways. For instance, on Linux malloc could return a valid address but set errno to ENOMEM if allocation fails due to insufficient memory.
As per ISO C11 standards, if malloc fails and system does not support any other error signalling mechanism, then NULL shall be returned. So checking NULL is always necessary as minimum malloc error handling.
Check return value of malloc
An alternative is to directly check malloc‘s return value against NULL:
if (malloc(1024*1024) == NULL) {
printf("Malloc failed\n");
exit(1);
}
This checks if malloc returned NULL without needing an intermediate pointer variable.
In some cases, direct return value check could be faster than assigning to a pointer variable if allocation failure is unlikely. However, if the check fails, the pointer variable itself is useful in freeing any partially allocated memory.
Set error handling using perror
We can also use perror() to print the cause of the error:
int* ptr = malloc(sizeof(int) * N);
if(!ptr) {
perror("Malloc failed");
exit(1);
}
perror will print "Malloc failed: " along with the reason for failure from the system. This gives more information on why malloc failed compared to just checking against NULL.
Some reasons that perror could print:
- "Malloc failed: Cannot allocate memory" – Indicates insufficient free memory
- "Malloc failed: Invalid size" – Trying to allocate 0 or excessively large amount
- "Malloc failed: Invalid pointer" – Passed invalid pointer to realloc/free
Capturing these error reasons can help during debugging by providing additional context.
Catch out of memory errors
When malloc fails, it typically sets errno to ENOMEM indicating that the failure was caused by insufficient memory. We can catch this specifically:
int* x = malloc(1000000000000);
if(!x) {
if(errno == ENOMEM) {
printf("Out of memory\n");
}
else {
printf("Other malloc error\n");
}
}
Here we differentiate between a memory allocation failure and other errors. This helps log/notify the specific error to developers compared to a generic malloc failure.
Statistics show over 63% of malloc errors are caused by out of memory or heap exhaustion issues. So checking errno for ENOMEM occurrence is useful.
Set memory limit before allocating
Some systems allow setting a limit on maximum memory usage. We can explicitly set this to a low value before malloc to intentionally induce allocation failure:
#include <sys/resource.h>
// Set max memory usage to 1K
struct rlimit limit;
limit.rlim_cur = 1024;
limit.rlim_max = 1024;
if (setrlimit(RLIMIT_AS, &limit) == 0) {
int* p = malloc(2048); // Will fail
if(!p) {
printf("Allocation failed due to set memory limit\n");
}
}
Here we use setrlimit() to force malloc to fail and demonstrate how to handle the failure.
This technique is useful while testing to validate behavior during low memory or allocation failure. Such intentional failures also help evaluate effectiveness of logging and monitoring systems in capturing issues.
Common pitfalls when checking malloc errors
There are some common pitfalls to watch out for when handling malloc errors:
- Not checking malloc‘s return value at all – This would lead to crashes or leaks during failure
- Testing against NULL but not freeing any partial memory allocated already
- Calling functions like printf before out of memory is handled – This itself could allocate memory and crash
- Logging errors incorrectly in signal handlers for segmentation faults
- Relying only on return code without checking errno for failure reason
So code should check malloc return value correctly, free any allocated memory, avoid other allocations, and use errno if available.
Debugging malloc errors
When debuging malloc issues, useful techniques include:
- Enabling debug symbols (-g option) to get line numbers in stack traces
- Attaching debugger after crash to get stack, registers and memory state
- Getting core dump of crashed process and analyzing with gdb
- Tracing memory errors with tools like Valgrind memcheck
- Using logging APIs that don‘t allocate memory internally
For server processes, enabling core dumps with ulimit -c unlimited generates core file on crash that contains program‘s memory content. This core file helps greatly in determining root cause of malloc errors.
Failure injection via fork
To properly test malloc failure handling, we can intentionally inject failures. One way is to use fork() before allocating memory:
#include <unistd.h>
int main() {
if(fork() == 0) {
// Child process
int* x = malloc(1000000000000); // Will fail
} else {
// Parent
wait(NULL); // Wait for child
printf("Child malloc failed successfully!\n");
}
}
Here, only child process tries large allocation that would fail, while parent waits and detects failure. This validates handling crashes due to failed malloc.
C standards on detecting allocation failures
The ISO C11 standard (ISO/IEC 9899:201x) says:
"The malloc function returns a pointer to the allocated space, or NULL if the allocation failed."
So all C11 compliant malloc implementations must return NULL on failure. Programs targeting this standard should handle NULL check properly.
POSIX 2008 standard (IEEE 1003.1) additionally specifies:
"If memory allocation fails, malloc() and calloc() shall return a null pointer and set errno to [ENOMEM]"
So on POSIX systems, checking errno for ENOMEM provides most portability after NULL return value check.
Importance in long running processes
Careful malloc failure handling gets critical in long running server processes rather than short command line apps. If such app crashes due to failed malloc, detecting and restarting cleanly becomes vital to avoid downtime.
Hence most server frameworks provide hooks to handle out of memory events. For example, Node.js has uncaughtException handler to catch unhandled errors. Backend languages like Java and C# also ensure any exceptions propagate up cleanly without direct crash.
So for long running C/C++ programs, combination of NULL checks, errno inspection, logging/reporting helpers, OS level core handling etc helps recover and prevent crashes due to failed mallocs.
Comparison with Rust and Go
Unlike C, some of the newer systems programming languages have built-in mechanisms to handle memory allocation failures.
For instance, Rust‘s memory model uses strict compile time borrow checking that prevents bugs due to invalid memory access in case allocation fails. The Option enum also enforces handling potential NULL cases.
Go lang‘s inbuilt ‘panic/recover‘ mechanism propagates allocation failures up the call stack, forcing the app code to correctly deal with out of memory errors.
So additional language level safety checks in Rust/Go obviates manual checking for allocation errors like C does.
Alternative allocators
There are several alternative memory allocators used in place of malloc to optimize different metrics like speed, fragmentation etc. Some popular ones include:
- Jemalloc – Used in FreeBSD and Firefox focuses on concurrency and fragmentation
- TCMalloc – Developed by Google provides fast multi-threaded performance
- Hoard – Optimized for real time and embedded systems
These allocators also tend report failures in most cases by returning NULL like malloc does. However, advanced ones like jemalloc can be configured to abort() directly on allocation failure by enabling options like abortconf.
So when using specialized allocators, their documentation should be checked if they allow configuring behavior for handling allocation failure scenarios.
Conclusion
Checking the return value or pointer from malloc against NULL is the simplest and most portable way to detect allocation errors across different systems and compilers. Inspecting errno for ENOMEM provides additional context into exact failure reason. Several other alternatives exist for reporting and handling errors during memory allocation stage itself in order to build robust software applications in C language.


