60

As a good programmer one should write robust codes that will handle every single outcome of his program. However, almost all functions from the C library will return 0 or -1 or NULL when there's an error.

It's sometimes obvious that error checking is needed, for example when you try to open a file. But I often ignore error checking in functions such as printf or even malloc because I don't feel necessary.

if(fprintf(stderr, "%s", errMsg) < 0){
    perror("An error occurred while displaying the previous error.");
    exit(1);
}

Is it a good practice to just ignore certain errors, or is there a better way to handle all the errors?

  • 13
    Depends on the robustness level that is required for the project you're working on. Systems that has a chance of receiving inputs from untrusted parties (e.g. public-facing servers), or operating in not fully trusted environments, need to be coded very cautiously, to avoid the code becoming a ticking time bomb (or the weakest link being hacked). Obviously, hobby and learning projects do not need such robustness. – rwong Nov 17 '15 at 0:32
  • 1
    Some languages provide exceptions. If you don't catch exceptions, your thread will terminate, which is better than letting it continue with bad state. If you choose to catch exceptions, you can cover many errors in numerous lines of code including invoked functions & methods with one try statement, so you don't have to check every single call or operation. (Also note that some languages are better than others at detecting simple errors like null dereference or array index out of bounds.) – Erik Eidt Nov 17 '15 at 0:40
  • 1
    The problem is mostly a methodological one: you don't write error checking typically when you are still figuring out what you are supposed to implement and you don't want to add error checking right now because you want to get the "happy path" right first. But you are still supposed to check for malloc and co. Instead of skipping the error checking step, you have to prioritize your coding activities and let error checking be an implicit permanent refactoring step in your TODO list, applied whenever you are satisfied with your code. Adding greppable "/*CHECK*/" comments might help. – coredump Nov 17 '15 at 3:04
  • 7
    I can't believe no one's mentioned errno! In case you're not familiar, while it's true that "almost all functions from the C library will return 0 or −1 or NULL when there's an error," they also set the global errno variable, which you can access by using #include <errno.h> and then simply reading the value of errno. So, for example, if open(2) returns -1, you might want to check whether errno == EACCES, which would indicate a permissions error, or ENOENT, which would indicate that the requested file does not exist. – wchargin Nov 17 '15 at 4:22
  • 1
    @ErikEidt C does not support try/catch, although you could simulate it with jumps. – Mast Nov 17 '15 at 9:50
29

In general, code should deal with exceptional conditions wherever it is appropriate. Yes, this is a vague statement.

In higher level languages with software exception handling this is often stated as "catch the exception in the method where you can actually do something about it." If a file error occurred, maybe you let it bubble up the stack to the UI code that can actually tell the user "your file failed to save to disk." The exception mechanism effectively swallows up "every little error" and implicitly handles it at the appropriate place.

In C, you do not have that luxury. There are a few ways to handle errors, some of which are language/library features, some of which are coding practices.

Is it a good practice to just ignore certain errors, or is there a better way to handle all the errors?

Ignore certain errors? Maybe. For example, it is reasonable to assume that writing to standard output will not fail. If it does fail, how would you tell the user, anyway? Yes, it is a good idea to ignore certain errors, or code defensively to prevent them. For example, check for zero before dividing.

There are ways to handle all, or at least most, errors:

  1. You can use jumps, similar to gotos, for error handling. While a contentious issue among software professionals, there are valid uses for them especially in embedded and performance-critical code (e.g. Linux kernel).
  1. Cascading ifs:

    if (!<something>) {
      printf("oh no 1!");
      return;
    }
    if (!<something else>) {
      printf("oh no 2!");
      return;
    }
    
  2. Test the first condition, e.g. opening or creating a file, then assume subsequent operations succeed.

Robust code is good, and one should check for and handle errors. Which method is best for your code depends on what the code does, how critical a failure is, etc. and only you can truly answer that. However, these methods are battle-tested and used in various open source projects where you can take a look to see how real code checks for errors.

  • 21
    Sorry, minor nit to pick here - "writing to standard output will not fail. If it does fail, how would you tell the user, anyway?" - by writing to standard error? There's no guarantee that a failure to write to one implies that it's impossible to write to the other. – Damien_The_Unbeliever Nov 17 '15 at 8:23
  • 4
    @Damien_The_Unbeliever - especially since stdout can be redirected to a file, or not even exist depending on the system. stdout is not a "write to console" stream, it's usually that, but it doesn't have to be. – Davor Ždralo Nov 17 '15 at 11:18
  • 3
    @JAB You send a message to stdout ... obvious :-) – TripeHound Nov 17 '15 at 15:49
  • 7
    @JAB: You might exit with EX_IOERR or so, if that was appropriate. – John Marshall Nov 17 '15 at 16:03
  • 6
    Whatever you do, don't just carry on running as though nothing's happened! If the command dump_database > backups/db_dump.txt fails to write to standard output at any given point, I wouldn't want it to carry on and exit successfully. (Not that databases are backed up that way, but the point still stands) – Ben S Nov 17 '15 at 16:39
15

However, almost all functions from the C library will return 0 or -1 or NULL when there's an error.

Yes, but you know which function you called, don't you?

You actually have a lot of information that you could put in an error message. You know which function was called, the name of the function that called it, what parameters were passed, and the return value of the function. That's plenty of information for a very informative error message.

You don't have to do this for every function call. But the first time you see the error message "An error occurred while displaying the previous error," when what you really needed was useful information, will be the last time you ever see that error message there, because you're immediately going to change the error message to something informative that will help you troubleshoot the problem.

  • 5
    This answer made me smile, because it's true, but doesn't answer the question. – RubberDuck Nov 17 '15 at 0:57
  • How does it not answer the question? – Robert Harvey Nov 17 '15 at 2:00
  • 2
    one reason i think that C (and other languages) got the boolean 1 and 0 mixed up is the same reason that any decent function returning an error code returns "0" for no error. but then you gotta say if (!myfunc()) {/*proceed*/}. 0 was no error, and anything non-zero was some kinda error and each kinda error had its own non-zero code. the way a friend of mine put it was "there is only one Truth, but many falsehoods." "0" should be "true" in the if() test and anything non-zero should be "false". – robert bristow-johnson Nov 17 '15 at 3:20
  • 1
    no, doing it your way, there is only one possible negative error code. and many possible no_error codes. – robert bristow-johnson Nov 17 '15 at 3:34
  • 1
    @robertbristow-johnson: Then read it as "There was no error." if(function()) { // do something } reads as "if function executes with no error, then do something." or, even better, "if function executes successfully, do something." Seriously, those who understand the convention are not confused by this. Returning false (or 0) when an error occurs would not allow error codes to be used. Zero means false in C because that's how math works. See programmers.stackexchange.com/q/198284 – Robert Harvey Nov 17 '15 at 3:48
11

TLDR; you should almost never ignore errors.

The C language lacks a good error handling feature leaving for each library developer to implement its own solutions. More modern languages have exceptions built in which makes this particular problem a lot easier to handle.

But when you're are stuck with C you have no such perks. Unfortunately you'll simply have to pay the price every time you're calling a function which there is a remote possibility of failure. Or else you will suffer much worse consequences such as overwriting data in memory unintentionally. So as a general rule you have to check for errors always.

If you don't check for the return of fprintf you're very likely leaving a bug behind that will in the best case not do what the user expects and worse case explode the entire thing during flight. There's no excuse to undermine yourself that way.

However as a C developer it's also your job to make the code easy to maintain. So sometimes you can safely ignore errors for the sake of clarity if (and only if) they do not pose any threat to the overall behavior of the application..

It's the same problem as doing this:

try
{
    run();
} catch (Exception) {
    // comments expected here!!!
}

If you see that with no good comments inside the empty catch block this is certainly an issue. There's no reason to think a call to malloc() will execute successfully 100% of the time.

  • I agree maintainability is a really important aspect and that paragraph really answered my question. Thanks for the detailed answer. – Derek 朕會功夫 Nov 17 '15 at 2:08
  • I think the gazillions of programs that never bother to check their malloc calls and yet never crash is a good reason to be lazy when a desktop program only uses a few MBs of memory. – whatsisname Nov 17 '15 at 4:29
  • 5
    @whatsisname: Just because they don't crash doesn't mean they aren't silently corrupting data. malloc() is one function you definitely want to check the return values on! – TMN Nov 17 '15 at 13:09
  • 1
    @TMN: If malloc failed the program would immediately segfault and terminate, it wouldn't carry on doing stuff. It's all about risk and what's appropriate, not "almost never". – whatsisname Nov 17 '15 at 15:39
  • 2
    @Alex C does not lack good error handling. If you want exceptions, you can use them. The standard library not using exceptions is a deliberate choice (exceptions force you to have automated memory management and often you don't want that for low-level code). – martinkunev Nov 17 '15 at 16:13
9

The question is not actually language specific, but rather user specific. Think about the question from a user perspective. The user does something, like typing the name of the program on a command line and hitting enter. What does the user expect? How can they tell if something went wrong? Can they afford to intercede if an error occurs?

In many types of code, such checks are overkill. However, in high reliability safety critical code, such as those for nuclear reactors, pathological error checking and planned recovery paths are part of the day-to-day nature of the job. It's deemed worth the cost to take the time to ask "What happens if X fails? How do I get back to a safe state?" In less reliable code, such as those for video games, you can get away with far less error checking.

Another similar approach is how much can you actually improve on the state by catching the error? I cannot count the number of C++ programs that proudly catch exceptions, only to just rethrow them because they didn't actually know what to do with them... but they knew they were supposed to do exception handling. Such programs gained nothing from the extra effort. Only add error checking code that you think may actually handle the situation better than simply not checking the error code. That being said, C++ has specific rules for handling exceptions that occur during exception handling in order to catch them in a more meaningful way (and by that, I mean calling terminate() to make sure the funeral pyre you have built for yourself lights up in its proper glory)

How pathological can you get? I worked on a program that defined a "SafetyBool" whose true and false values where carefully chosen to have an even distribution of 1's and 0's, and they were chosen so that any one of a number of hardware failures (single bit flips, data bus traces getting broken, etc.) did not cause a boolean to be misinterpreted. Needless to say, I would not claim this to be a general purpose programming practice to be used in any old program.

6
  • Different safety requirements demand different levels of correctness. In aviation or automobile control software all return values will be checked, cf. MISRA.FUNC.UNUSEDRET. In a quick proof of concept which never leaves your machine, probably not.
  • Coding costs time. Too many irrelevant checks in non-saftey-critical software is effort better spent elsewhere. But where is the sweet spot between quality and cost? That depends on the debugging tools and the software complexity.
  • Error handling can obscure control flow and introduce new errors. I quite like Richard "network" Stevens' wrapper functions which at least report errors.
  • Error handling can, rarely, be a performance issue. But most C library calls will take so long that the cost of checking a return value is immeasurably small.
3

A bit of an abstract take on the question. And it's not necessarily for the C language.

For larger programs you would have an abstraction layer; perhaps a part of the engine, a library or within a framework. That layer would not care about weather you get a valid data or the output would be some default value: 0, -1, null etc.

Then there's a layer that would be your interface to the abstract layer, that would do a lot of error handling and perhaps other things like dependency injections, event listening etc.

And later you would have your concrete implementation layer where you actually set the rules and handle the output.

So my take on this is that it sometimes is better to completely exclude error handling from a part of code because that part simply doesn't do that job. And then have some processor that would evaluate the output and point to an error.

This is mostly done to separate responsibilities that leads to code readability, and better scalability.

0

In general, unless you have a good reason for not checking that error condition, you should. The only good reason I can think of for not checking for an error condition is when you can't possibly do something meaningful if it fails. This is a very hard bar to meet though, because there's always one viable option: exit gracefully.

  • I would generally disagree with you as errors are situations that you did not account for. It's hard to know how the error might manifest if you don't know under what condition the error sprung. And thus error handling before actual processing of data. – Creative Magic Nov 18 '15 at 0:59
  • Like, I said, if something returns or throws an error, even if you don't know how to fix the error and continue, you can always fail gracefully, log the error, tell the user it didn't work as expected, etc. The point is that the error shouldn't go unnoticed, unlogged, "silently fail", or crash the application. That's what "failing gracefully" or "exit gracefully" means. – Clever Neologism Nov 18 '15 at 16:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.