65

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?

10
  • 15
    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, 2015 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, 2015 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, 2015 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, 2015 at 4:22
  • 1
    @ErikEidt C does not support try/catch, although you could simulate it with jumps.
    – Mast
    Nov 17, 2015 at 9:50

9 Answers 9

33

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.

6
  • 23
    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. Nov 17, 2015 at 8:23
  • 5
    @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. Nov 17, 2015 at 11:18
  • 3
    @JAB You send a message to stdout ... obvious :-)
    – TripeHound
    Nov 17, 2015 at 15:49
  • 7
    @JAB: You might exit with EX_IOERR or so, if that was appropriate. Nov 17, 2015 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, 2015 at 16:39
16

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.

20
  • 5
    This answer made me smile, because it's true, but doesn't answer the question.
    – RubberDuck
    Nov 17, 2015 at 0:57
  • How does it not answer the question? Nov 17, 2015 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". Nov 17, 2015 at 3:20
  • 1
    no, doing it your way, there is only one possible negative error code. and many possible no_error codes. Nov 17, 2015 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 Nov 17, 2015 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.

10
  • 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, 2015 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". Nov 17, 2015 at 15:39
  • 1
    @whatsisname You can't make such assumptions - you get undefined behavior. If you are lucky, it will crash. If you're not, it can summon the nasal demons or start a nuclear war. Nov 17, 2015 at 16:08
  • 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). Nov 17, 2015 at 16:13
  • 1
    @whatsisname there's no excuse for being lazy... all software developers eventually learn that if there's a small chance a certain situation happens then it's the same as saying it will definitely happen. If it happens only once then it's your responsibility to handle it properly.
    – Alex
    Nov 20, 2015 at 14:38
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.

3

Always ask yourself the following question:

If I test and there is an error, can I handle it in a meaningful way?

If the answer is "No", then why would you test for it?

E.g. lets take Hello World:

#include <stdio.h>
int main() {
   printf("Hello, World!");
   return 0;
}

We all know, that printf() may fail. What are you going to do when it fails? Print an error? Do you think you will be able to print an error in cases you were not even able to print "Hello World!"? Okay, you might be able to print an error to stderr as just because you cannot print to stdout doesn't mean you cannot print to stderr but how meaningful would that be? What would the error say? "I couldn't print Hello World! to standard out"? How is that helpful? What do you expect the user of your program to do with that information? Which reaction do you expect from the user? Shall the user fix standard out and call your program again? Maybe he can, more likely he doesn't even know what standard out is when he calls programs that only serve the purpose to print "Hello World!".

Assume you would not catch that error, what will happen? Nothing. Well, the program will not print "Hello World!" but that will be obvious to the user, right? The user will see that no Hello World appeared on the screen, right? You don't have to tell the user that.

Of course, if the only task of your program is to print "Hello World!" and it cannot do that, you may argue that your program has failed. And there is a meaningful, standard way to communicate to the caller, that a program has failed:

#include <stdio.h>
int main() {
   int printRes = printf("Hello, World!");
   return (printRes < 0 ? EXIT_FAILURE : EXIT_SUCCESS);
}

Yes, that is somehow meaningful, you can do that.

But what will you do when malloc(12) fails? Print an error? If malloc(12) failed because the system is out of memory, do you think printf() will work? Can printf() work without using malloc()? Well, the answer is, you don't know it and it probably depends what you print but it might as well depend on a memory allocation and that will fail as well. Honestly, what meaningful error handling can you perform if your system is so much out of memory that you cannot even allocate another 12 bytes?

So what would you do? Just call exit(EXIT_FAILURE)? Is that better than actually crashing because you are accessing the NULL result of malloc(12)? Yes, crashing is not nice but at least you will know where the program crashed doing what exactly if a crashlog is written, you know nothing about it if you had just exited. If you had exited, nobody will ever know what went wrong, nobody can ever fix that problem.

What if malloc(100 * 1024 * 1024) fails? Can you still do error handling? Can you tell the user, that the system is out of memory? Well, most likely you can. You won't need another 100 MB of memory to do that and most likely the system is not completely out of memory, it just cannot provide another 100 MB. At least you should give it a try.

So you see, despite the fact that both examples used malloc(), it does not depend on the function that failed, it depends on the question whether you can handle that error in a meaningful way.

What shall you do if you cannot handle an error in a meaningful way? Well, this depends on another question:

What is your app doing?

If your app deals with data, you probably don't want data to be lost. So if there is data in flight, which may still be saved and saving is still likely to succeed, you should at least try to save it before you exit. But wait! If you need to ensure data in flight is saved in case of an error, you just did handle it in a meaningful way! So you were lying, the answer to question one was "Yes" and thus you should handle that error. As whenever you can handle an error in a meaningful way, you should do exactly that!

So it does not just depend on the function that fails, it also depends on the purpose of your app. In some cases, you even want to catch and handle malloc(12) failing, as you can still do something meaningful then, in other cases that is pointless and why would you do pointless things. On the other hand, you could also ensure that all data is saved when the program exits or when the program crashes, using an atexit or signal handler. In which case it would make no sense again to catch that for every tiny allocation. Yet if your app doesn't even deal with storeable data, e.g. it just fetches something from the network and displays it to the screen, crashing because of a memory failure will have no problematic consequences, other than that your program stops running which it would have to do anyway in case of such an error. So again, it depends.

There is just one thing you should not do: Check for errors that can only appear if external functions work incorrectly. External functions should work as documented. If they don't do, that's a bug and the function is broken but if you cannot even rely on that and even have to test that functions you call are working correctly, what's next? Testing the compiler or the CPU is calculating correctly?

int x = 1 + 1; assert(x == 2);

Seriously? You cannot write programs at all if you cannot rely on certain functionality. If you know for sure that you feed valid input into a function which should never make this function report an error, don't test for an error as this function is not supposed to report one!

1
  • This. Errors are everywhere, but what does each error mean? This is especially true for libraries, where the writer has no idea of the responsibilities of the client program. In fact, he has no business taking any action that could affect the responsibilities of the client! What if, in fact, a low-level library failure is exactly the measure of success in the client program? The appropriate level of error checking depends entirely on the responsibilities of the program.
    – jimc
    Jul 11, 2023 at 16:22
1

For most developers, the answer is no. But it does depend on the kind of development you're doing.

I often see junior programmers testing for every error condition here there and everywhere, checking for null pointers passed and exiting early from the function if they are, or returning zero, or some other useless nonsense. I assume this is because it's still something emphasized in coding tests.

What you should be doing is ASSERTING, not TESTING AT RUNTIME, if the error condition is something that should never happen in your code. In the majority of the codebase you, the programmer/team, are in control of the legal state space of operations. If that assert trips during development and testing, find the cause and fix it immediately.

If, during the final weeks of development, you have a handful of assertions that STILL keep tripping despite your best efforts, you should consider adding a runtime test/guard for the error condition, and handling it. This amounts to an admission that you do not fully understand how your program runs and how it got into this state, but occasionally that does happen, even for competent teams, if the state space is sufficiently complex. But you should absolutely NOT be handling these kinds of errors up until this point of admitting defeat.

Now, things change if you have some mission-critical software that is already live and where you can't perform adequate testing offline. In that situation, ideally you would have some proper exception-handling system anyway, and you're architecting from the outset around uncertainty and around keeping running at all costs.

But if you're doing something like game development in a closed, knowable system that is fully under your control, this kind of programming philosophy is completely ill-suited to the problem domain. It clutters and bloats the code, and it makes it hard to reason about the logic. It's far better to specify contracts on methods that assert the legal range of states for parameters and data members, and which terminate execution if they ever fail. If you're getting an erroneous parameter passed through, or the object ever gets into an illegal state, it's not your job to handle that situation; it's your job to fix the logic such that it never happens again.

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.

3
  • 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. Nov 18, 2015 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. Nov 18, 2015 at 16:56
  • Exiting, graceful or not, is not always appropriate. If your program is in a "do the best you can" position taking your ball and going home is not appropriate. Even logging might not be appropriate, in a high-scale environment. Perhaps setting an externally-visibly 'unhappy' flag is the best you can do. In other words: it depends.
    – jimc
    Jul 11, 2023 at 16:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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