Reputation: 199
I'm making a data structures and algorithms library in C for learning purposes (so this doesn't necessarily have to be bullet-proof), and I'm wondering how void functions should handle errors on preconditions. If I have a function for destroying a list as follows:
void List_destroy(List* list) {
/*
...
free()'ing pointers in the list. Nothing to return.
...
*/
}
Which has a precondition that list != NULL
, otherwise the function will blow up in the caller's face with a segfault.
So as far as I can tell I have a few options: one, I throw in an assert()
statement to check the precondition, but that means the function would still blow up in the caller's face (which, as far as I have been told, is a big no-no when it comes to libraries), but at least I could provide an error message; or two, I check the precondition, and if it fails I jump to an error block and just return;
, silently chugging along, but then the caller doesn't know the List*
was NULL.
Neither of these options seem particularly appealing. Moreover, implementing a return value for a simple destroy() function seems like it should be unnecessary.
EDIT: Thank you everyone. I settled on implementing (in all my basic list functions, actually) consistent behavior for NULL List*
pointers being passed to the functions. All the functions jump to an error block and exit(1)
as well as report an error message to stderr
along the lines of "Cannot destroy NULL list." (or push, or pop, or whatever). I reasoned that there's really no sensible reason why a caller should be passing NULL List*
pointers anyway, and if they didn't know they were then by all means I should probably let them know.
Upvotes: 1
Views: 4942
Reputation: 5543
Generally, you have several options if a constraint of one of your functions is violated:
assert
or call abort
or exit
or the like)Where (but this is my personal opinion) this is a good rule of thumb:
free
.fopen
.memcpy
. I prefer the use of assert
(one of the fourth options) because it enables both: Crashing reliably if someone is unwilling to read your documentation and introduce undefined behaviour for people who do read it (they will prevent that by obeying your constraints), depending on whether they compile with NDEBUG
defined or not. Dereferencing a pointer argument can serve as an assert
, because it will make your program crash (which is the right thing, people not reading your documentation should crash as early as possible) if these people pass an invalid pointer.So, in your case, I would make it similar to free
and would succeed without doing anything.
HTH
Upvotes: 1
Reputation: 319
I would say that simply returning in case the list is NULL would make sense at this would indicate that list is empty(not an error condition). If list is an invalid pointer, you cant detect that and let kernel handle it for you by giving a seg fault and let programmer fix it.
Upvotes: 0
Reputation: 4738
If you wish not to return any value from function, then it is good idea to have one more argument for errCode.
void List_destroy(List* list, int* ErrCode) {
*ErrCode = ...
}
Edit:
Changed &
to *
as question is tagged for C.
Upvotes: 0
Reputation: 140569
Destructors (in the abstract sense, not the C++ sense) should indeed never fail, no matter what. Consistent with this, free
is specified to return without doing anything if passed a null pointer. Therefore, I would consider it reasonable for your List_destroy
to do the same.
However, a prompt crash would also be reasonable, because in general the expectation is that C library functions crash when handed invalid pointers. If you take this option, you should crash by going ahead and dereferencing the pointer and letting the kernel fire a SIGSEGV, not by assert
, because assert
has a different crash signature.
Absolutely do not change the function signature so that it can potentially return a failure code. That is the mistake made by the authors of close()
for which we are still paying 40 years later.
Upvotes: 1