Dalton
Dalton

Reputation: 199

How to handle error conditions in a void function

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

Answers (4)

mafso
mafso

Reputation: 5543

Generally, you have several options if a constraint of one of your functions is violated:

  1. Do nothing, successfully
  2. Return some value indicating failure (or set something pointed-to by an argument to some error code)
  3. Crash randomly (i.e. introduce undefined behaviour)
  4. Crash reliably (i.e. use assert or call abort or exit or the like)

Where (but this is my personal opinion) this is a good rule of thumb:

  • the first option is the right choice if you think it's OK to not obey the constraints (i.e. they aren't real constraints), a good example for this is free.
  • the second option is the right choice, if the caller can't know in advance if the call will succeed; a good example is fopen.
  • the third and fourth option are a good choice if the former two don't apply. A good example is 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

CodeQ
CodeQ

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

Digital_Reality
Digital_Reality

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

zwol
zwol

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

Related Questions