I'm a bit confused on how to check if a memory allocation failed in order to prevent any undefined behaviours caused by a dereferenced NULL
pointer.
I know that malloc
(and similiar functions) can fail and return NULL
, and that for this reason the address returned should always be checked before proceeding with the rest of the program. What I don't get is what's the best way to handle these kind of cases. In other words: what is a program supposed to do when a malloc
call returns NULL
?
I was working on this implementation of a doubly linked list when this doubt raised.
struct ListNode {
struct ListNode* previous;
struct ListNode* next;
void* object;
};
struct ListNode* newListNode(void* object) {
struct ListNode* self = malloc(sizeof(*self));
if(self != NULL) {
self->next = NULL;
self->previous = NULL;
self->object = object;
}
return self;
}
The initialization of a node happens only if its pointer was correctly allocated. If this didn't happen, this constructor function returns NULL
.
I've also written a function that creates a new node (calling the newListNode
function) starting from an already existing node and then returns it.
struct ListNode* createNextNode(struct ListNode* self, void* object) {
struct ListNode* newNext = newListNode(object);
if(newNext != NULL) {
newNext->previous = self;
struct ListNode* oldNext = self->next;
self->next = newNext;
if(oldNext != NULL) {
newNext->next = oldNext;
oldNext->previous = self->next;
}
}
return newNext;
}
If newListNode
returns NULL
, createNextNode
as well returns NULL
and the node passed to the function doesn't get touched.
Then the ListNode struct is used to implement the actual linked list.
struct LinkedList {
struct ListNode* first;
struct ListNode* last;
unsigned int length;
};
_Bool addToLinkedList(struct LinkedList* self, void* object) {
struct ListNode* newNode;
if(self->length == 0) {
newNode = newListNode(object);
self->first = newNode;
}
else {
newNode = createNextNode(self->last, object);
}
if(newNode != NULL) {
self->last = newNode;
self->length++;
}
return newNode != NULL;
}
if the creation of a new node fails, the addToLinkedList
function returns 0 and the linked list itself is left untouched.
Finally, let's consider this last function which adds all the elements of a linked list to another linked list.
void addAllToLinkedList(struct LinkedList* self, const struct LinkedList* other) {
struct ListNode* node = other->first;
while(node != NULL) {
addToLinkedList(self, node->object);
node = node->next;
}
}
How should I handle the possibility that addToLinkedList
might return 0? For what I've gathered, malloc
fails when its no longer possible to allocate memory, so I assume that subsequent calls after an allocation failure would fail as well, am I right? So, if 0 is returned, should the loop immediately stop since it won't be possible to add any new elements to the list anyway?
Also, is it correct to stack all of these checks one over another the way I did it? Isn't it redundant? Would it be wrong to just immediately terminate the program as soon as malloc fails? I read that it would be problematic for multi-threaded programs and also that in some istances a program might be able to continue to run without any further allocation of memory, so it would be wrong to treat this as a fatal error in any possible case. Is this right?
Sorry for the really long post and thank you for your help!
It depends on the broader circumstances. For some programs, simply aborting is the right thing to do.
For some applications, the right thing to do is to shrink caches and try the malloc
again. For some multithreaded programs, just waiting (to give other threads a chance to free memory) and retrying will work.
For applications that need to be highly reliable, you need an application level solution. One solution that I've used and battle tested is this:
malloc
fails, free some of the emergency pool.NULL
response, sleep and retry.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With