Why it is bad idea to check result of malloc call with assert
Anastasiia Vorobeva
Posted on February 20, 2024
The pointer returned by malloc needs to be checked before use. Using the assert macro to check the pointer would be wrong. In this article, we'll investigate why this is a bad programming tip.
When I say malloc, I mean not only this function, but also calloc, realloc, _aligned_malloc, _recalloc, strdup, and so on.
The malloc function returns a null pointer if it's unable to allocate a memory buffer of the specified size. So, it's better to check if a pointer is equal to NULL before dereferencing it.
Why this check is mandatory, I have covered in detail in the article: "Four reasons to check what the malloc function returned". If you have the slightest doubt that such a check is necessary, please read the article before continuing this one.
The standard check looks like this:
int *ptr = malloc(sizeof(int) * N);
if (!p)
{
// Handling memory allocation error
}
I prefer explicit comparison, though. It protects the code from typos and makes it a bit more readable (and it's obvious that a pointer is being checked):
if (p == NULL)
The most important thing to do is to handle the memory allocation error. The error may appear as follows:
- a program terminates with a return code (if such behavior is allowed);
- an operation is rejected, but the program continues working to save the data;
- if the program controls a device, it shuts down the device (if such behavior is allowed);
- the library should return a failure status, and the program will decide what to do next;
- other options depending on the type of the program.
Let's not assume that we can skip the check, because the program will crash anyway if a null pointer is dereferenced. It may not crash. Null pointer dereference is undefined behavior, and its signs can be very surprising to a programmer. I've covered all of this in the article I mentioned earlier.
Now, let's discuss the following anti-pattern:
int *ptr = malloc(sizeof(int) * N);
assert(ptr);
memcpy(ptr, foo, sizeof(int) * N);
Some programmers use the assert macro (or its analogs, such as the ASSERT macro from the MFC library) to check the pointer.
Please note that if the NDEBUG macro is declared, the assert macro turns off (does nothing). An example of an implementation from assert.h:
#ifdef NDEBUG
#define assert(condition) ((void)0)
#else
#define assert(condition) /*implementation defined*/
#endif
In reality, this means that in the release version of the program, which should run as fast as possible, the NDEBUG macro is declared and assert is turned into "nothing". As a result, there's no protection against null pointers, and the consequences can be quite unpleasant.
In the debug version of the program, assert works as it should, but it makes no sense in real cases.
Usually, when developers test the debug version of a program, they use simple operation scenarios that don't require large memory allocations. Most likely, severe memory fragmentation won't occur. It's very unlikely that there will be a situation where memory allocation is impossible.
If such a thing really happens, assert indeed detects a null pointer. However, this is almost useless for the debug version of the program. The null pointer dereference shows up immediately anyway. There won't be any tricky undefined behavior issues as there are in optimized code.
So, this is what we have:
- the assert macro doesn't work where it's supposed to (in release builds);
- but it works where it's of little relevance (in debug builds).
What happens if you don't declare the NDEBUG macro for release versions?
This is a bad idea. In fact, a memory allocation error always causes an application to crash. Such behavior is unacceptable for many types of applications (especially for libraries). Many developers also use the same macro to implement additional checks throughout the code where it's necessary. So, missing NDEBUG can be considered a bug and then be fixed. Just don't do that.
Never be lazy to write proper crash checks, and users will be grateful. Thank you for reading.
Additional links:
- There are tons of other ways to create erroneous code: 60 terrible tips for a C++ developer
- Null Pointer Dereferencing Causes Undefined Behavior
- Shall we check pointer for NULL before calling free function?
Posted on February 20, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.