7.12 — Handling errors, cerr and exit

When writing programs, it is almost inevitable that you will make mistakes. In this section, we will talk about the different kinds of errors that are made, and how they are commonly handled.

Errors fall into two categories: syntax and semantic errors.

Syntax errors

A syntax error occurs when you write a statement that is not valid according to the grammar of the C++ language. For example:

if 5 is not equal to 6 then write "not equal";

Although this statement is understandable by humans, it is not valid according to C++ syntax. The correct C++ statement would be:

Syntax errors are almost always caught by the compiler and are usually easy to fix. Consequently, we typically don’t worry about them too much.

Semantic errors

A semantic error occurs when a statement is syntactically valid, but does not do what the programmer intended. For example:

The programmer may have intended this statement to print 0 1 2, but it actually prints 0 1 2 3.

Semantic errors are not caught by the compiler, and can have any number of effects: they may not show up at all, cause the program to produce the wrong output, cause erratic behavior, corrupt data, or cause the program to crash.

It is largely the semantic errors that we are concerned with.

Semantic errors can occur in a number of ways. One of the most common semantic errors is a logic error. A logic error occurs when the programmer incorrectly codes the logic of a statement. The above for statement example is a logic error. Here is another example:

What happens when x is exactly 5? The conditional expression evaluates to true, and the program prints “x is greater than 5”. Logic errors can be easy or hard to locate, depending on the nature of the problem.

Another common semantic error is the violated assumption. A violated assumption occurs when the programmer assumes that something will be either true or false, and it isn’t. For example:

See the potential problem here? The programmer has assumed that the user will enter a value between 0 and the length of “Hello, world!”. If the user enters a negative number, or a large number, the array index will be out of bounds. In this case, since we are just reading a value, the program will probably print a garbage letter. But in other cases, the erroneous statement might corrupt other variables or cause the program to crash.

Defensive programming is a form of program design that involves trying to identify areas where assumptions may be violated, and writing code that detects and handles any violation of those assumptions so that the program reacts in a predictable way when those violations do occur.

Detecting assumption errors

As it turns out, we can catch almost all assumptions that need to be checked in one of three locations:

  • When a function has been called, the caller may have passed the function parameters that are incorrect or semantically meaningless.
  • When a function returns, the return value may indicate that an error has occurred.
  • When program receives input (either from the user, or a file), the input may not be in the correct format.

Consequently, the following rules should be used when programming defensively:

  • At the top of each function, check to make sure any parameters have appropriate values.
  • After a function has returned, check the return value (if any), and any other error reporting mechanisms, to see if an error occurred.
  • Validate any user input to make sure it meets the expected formatting or range criteria.

Let’s take a look at examples of each of these.

Problem: When a function is called, the caller may have passed the function parameters that are semantically meaningless.

Can you identify the assumption that may be violated? The answer is that the caller might pass in a null pointer instead of a valid C-style string. If that happens, the program will crash. Here’s the function again with code that checks to make sure the function parameter is non-null:

Problem: When a function returns, the return value may indicate that an error has occurred.

Can you identify the assumption that may be violated? The answer is that the user may enter a character that isn’t in the string. If that happens, the find() function will return an index of -1, which will get printed.

Here’s a new version with error checking:

Problem: When program receives input (either from the user, or a file), the input may not be in the correct format. Here’s the sample program you saw previously that illustrates this flaw:

And here’s the version that checks the user input to make sure it is valid:

Note that this last example has two-levels of error checking: first, we need to make sure the user’s input was properly read into variable index. Then we needed to ensure index was within range of the array.

Handling assumption errors

Now that you know where assumption errors typically occur, let’s talk about different ways to handle them when they do occur. There is no best way to handle an error -- it really depends on the nature of the problem and whether the problem can be fixed or not.

Here are some typical methods:

1) Quietly skip the code that depends on the assumption being valid:

In the above example, if cstring is null, we don’t print anything. We have skipped the code that depends on cstring being non-null. This can be a good option if the statement being skipped isn’t critical and doesn’t impact the program logic. The primary challenge with doing so is that the caller or user have no way to identify that something went wrong.

2) If we are in a function, return an error code back to the caller and let the caller deal with the problem.

In this case, the function returns -1 if the caller passes in an invalid index. Returning an enumerated value would be even better.

This method only works if you can find a return value that can’t naturally be in set of normal return values for that function. For example, if the array above could hold negative numbers, then array might return -1 normally. This precludes using that value to indicate an error.

3) If we want to terminate the program immediately, the exit function that lives in <cstdlib> can be used to return an error code to the operating system:

If the caller passes in an invalid index, this program will terminate immediately (with no error message) and pass error code 2 to the operating system.

4) If the user has entered invalid input, ask the user to enter the input again.

5) cerr is another mechanism that is meant specifically for printing error messages. cerr is an output stream (just like cout) that is defined in <iostream>. Typically, cerr writes the error messages on the screen (just like cout), but it can also be individually redirected to a file.

In the above example, we not only skip the bad line, we also log an error so the user can later determine why the program didn’t execute as expected.

6) If working in some kind of graphical environment (eg. MFC, SDL, QT, etc…), it is common to pop up a message box with an error code and then terminate the program.

The specific details of how to do this depend on the environment.

Note: Because this lesson was getting long, discussions of assert have been moved to the next lesson.

7.12a -- Assert and static_assert
7.11 -- Recursion

88 comments to 7.12 — Handling errors, cerr and exit

  • jkwi

    The following example

    is only ok if we make an assumtion that the array contains only positive values. What if the array contained the value -1? It would be impossible for the caller to distinguigh between the error code and the return value.

  • Nirbhay


    In section "Handling assumption errors", the sentence after the code snippet "In the above example, if cstring is null, we don’t print anything. We have skipped the code that depends on cstring being non-null.(non-null or null?)"


    • Alex

      It's correct as written. The if statement only executes if cstring is not null. Thus, if cstring is null, we don't print anything, skipping over the code that would execute if cstring were non-null.

      • Nirbhay


        I guess I got confused with the verb 'have' as in we skipped the code already in the above snippet.

        Anyway, thanks for the clarification.

  • daniel

    You should also said that semantic error's are also called a bug

  • Alireza

    Hi dear,
    What about std::clog ?

  • Zha Weihua

    In the above example, if cstring is null, we don’t print anything. We have skipped the code that depends on cstring being non-null. This can be a good option if the statement being skipped isn’t critical and doesn’t impact the program logic. The primary challenge with doing so is that the caller or user have no way to identify that something went wrong.

    May I think you have a typo in above paragraph?

    We have skipped the code that depends on cstring being null instead of non-null?

    • Alex

      No, it's correct as written. The if statement body only executes if cstring is non-null.

      Regardless, I think it's a little hard to parse, so I'm removing the second sentence altogether, as it doesn't add much to the explanation.

  • Blackbot

    Something I noticed on my machine: While the order within cout and cerr is always the correct one, the order relative to each other is not. On my machine (using CLion), this code produces a different order of outputs every time (for example, Cerr and Cout might take turns from line to line, or the last few lines might be all Cerr and the first few lines all Cout):

    While this might be dependant on the environment it could be a good idea to mention that this depends on the IDE because it confused the hell out of me (and again when it happened in the examples of Chapter 14.3). :)

    • Hi Blackbot!

      @std::cout and @std::cerr use different output streams. Whenever you send something to it (using <<) it doesn't immediately get printed, instead it's stored in a buffer until some time later. @std::cout might decide to print the buffer before @std::cerr does, or the other way around. Using @std::endl rather than '\n' should cause a fixed order, because @std::endl causes the buffer to be printed immediately.

      * Line 4: Use uniform initialization.

      • Blackbot

        Hi nascardriver,

        unfortunately, @std::endl (and @std::flush, for that matter) does not cause fixed order for me.
        Again, this is dependant on the IDE used (when I run the same program in Terminal everything is fine with and without @std::endl) so not everybody will notice this behavior.

        I'm aware that the output streams are different, I just think it could be helpful to mention that (for someone very new to programming maybe unexpected) the streams do not necessarily keep their order relative to each other depending on the environment.

        • Jeff

          Understanding why the terminal output may not be ordered the same way as what your application did in time is, for most practical purposes, outside of the application's control.

          Your program wrote to one queue that the OS calls "stdout" and then wrote to another queue called "stderr" a fraction of a second later. From that moment on, what happens is up to the OS and  not your application. It is the OS that then chooses which queue is "more important" to process first and where to send the data, if anywhere.  For all your C++ program knows, "stderr" may be sent to  the "bit bucket" with a redirect in sh/bash such as "2 > /dev/null", in which case it just gets ignored. It might be to a file, it might be to a physical device like a serial port. It is very common in Unix-like OSes for stdout to go to stdin of another program ("fast" data transfer) and stderr to  go to a terminal or file ("slow" data transfer). Waiting for stderr might slow down stdout and all the programs that depend on it being "fast" unacceptably. It is very common to expect that a well-behaved program "never" writes to stderr (since nothing should ever go wrong). If you're in a microcontroller or embedded environment, there may not even be any place that stdout and stderr "go".

          Even with a modern "desktop/server" OS, there is never a guarantee on the order or time delays involved. That you happen to be viewing both stdout and stderr is not something that your C++ program knows. If you watch a desktop or phone app redrawing the screen in slow motion, the way that the pixels get rendered might surprise you. Those spots of light that you interpret as characters might be left to right, right to left, top to bottom, bottom to top, or whatever the graphics chip, its microcode, its drivers, and the OS libraries decide is "best".

          There are OSes that address this, such as "Real-Time Operating Systems" ("RTOS"), where the programmer has great control over both the order of events and when things happen. RTOS are more common when precise timing is important or a "guarantee" of response within a certain time are needed (such as control systems). You'd probably agree that deploying your airbag is much more important that writing a message to the console "Deploying airbag in 3 microseconds" at 9600 baud, even if the code "sent" the text before sending the trigger to the airbag.

  • Micah

    I'm not sure if it's just me but this gives me problems

    Using 'const std::array &array' doesn't work for me as a parameter, I had to change it to a vector to get the code to compile. Was that a mistake or does it work for you?

    • nascardriver

      Hi Micah!

      This is a mistake in the lesson.
      Lokesh pointed this out 2 years ago and Alex said he fixed it ( ), apparently something didn't go as planned. Alex will most likely fix it in a couple of days.

      When using std::array you need to specify the data type in amount of elements.

  • James Ray

    but it explicitly signals and intent

    an intent

  • nikos-13

    "std::cout << cstring;" is the code that depends on the assumption being valid?

  • jenifer

    What's the major difference between std::cout and std::cerr?  Both prints on console output.  Can we not just use std::cout ?

    • Alex

      In practice, very little -- however, it's possible to reroute the output of a particular channel (std::cout or std::cerr) elsewhere. In some cases, it's useful to let std::cout go to the monitor, but route std::cerr to a log file for analysis later.

      • Matias

        I have also seen large projects come up with their "Logger" classes and handle them in files. I do not know if they implement std::cerr to be directed to a specific file or not, but I am a bit curious to see if you know how they implement said Logger classes

        • Alex

          I like to use logging classes myself. Writing one of these can be a little tricky, but it's doable via a combination of static members (so you don't have to instantiate a logging object to call the logging member function) and some useful preprocessor functionality (particularly __FILE__ and __LINE__).

          Most often I'll write the log to a file, so it can be opened and examined later.

  • Ayush Goel

    Hey Alex, can you please explain the starred lines, i didn't get it???

    if (
                std::cin.clear(); // reset any error flags
          ***** std::cin.ignore(32767, '\n'); // ignore any characters in the input buffer
          ***** index = -1; // ensure index has an invalid value so the loop doesn't terminate
                continue; // this continue may seem extraneous, but it explicitly signals and intent to terminate this loop iteration

    • Alex

      std::cin will hold input that hasn't been extracted yet, so it can be extracted later. In the case of invalid input, this input that hasn't been extracted can cause problems.

      The std::cin.ignore line gets rid of all the characters up to (and including) a '\n'. In cases where lines of input are terminated with a '\n' (which is all user input), this should clear any extra characters out of std::cin, so we can start from a fresh state (where std::cin is not holding any characters).

      The index = -1 is just a convenient way to keep looping that's specific to the example.

  • Matt

    I am curious about assert() being a preprocessor macro... because it seems to look and act just like any other c++ code as far as I can see. What makes it a preprocessor macro, and why must it be that way?

    • Alex

      Remember that the preprocessor just does text substitution. Because assert() is a macro, the preprocessor will replace any call to macro assert() with a call to a real assert function (with a similar name, like _assert()), along with 3 arguments: the expression being asserted, the current filename, and the current line number.

      assert() is implemented as a macro because the C++ specification requires it to be implemented that way. :) The why question is more interesting. The short answer is: because the macro can generate the arguments for us.

      Consider what would happen if you wrote assert() as a function. In this case, whenever the user called the assert() function, they would have to explicitly pass in: the expression to be evaluated, the expression to be evaluated as a string (so the assert can print the expression if the assertion fails), the current filename, and the current line number. What a pain that would be!

      Now, you might think, why not just have the assert() function determine the current filename and line number itself? The problem is that the assert() function can't ask for the current filename or line number itself until it's already been called -- and at that point, the current filename and line number have already been changed by virtue of calling the assert function! There's also no easy way to get a string version of the assertion expression.

      So, in short, assert() is super easy to implement as a macro, and either super hard to implement as a function, or super burdensome on the user to implement as a function.

  • Matt

    I think there's a minor typo in the commented code above section "Handling assumption errors", on line number 20. I believe that "and" should be "an".

  • Chris


    at handling assumptions errors section number 5 comment code line 3:

    "Only print if strString is non-null" strString? it is typo? it should be cstring, isn't it?

  • Nyap

    with this example

    how could you enter a null pointer
    do escape sequences work during runtime?

    • Alex

      would try to print a nullptr.

      Escape sequences may work at runtime, but it depends on how your console interprets them.

  • Rob G.

    No compiler version I use resolves this error. Just making a null pointer snippet to test. Is this operator error or otherwise?

    .\src\main.cpp:6:13: error: ISO C++ forbids comparison between pointer and integer [-fpermissive]
      if(ptr != -1)

    • Alex

      The problem is this line:

      You're comparing a pointer value (which holds a memory address) to a non-zero integer. This is not allowed. The only valid integer you can compare a pointer to is 0 (null).

      Are you sure you didn't mean this?

      • Rob G.

        Yes, meant that.
        if(*ptr != -1) // if the value that ptr is pointing to is -1
        So dereferencing the pointer removes the violation. *ptr = int? Got it.

        Thought I didn't violate the condition:
        "The only valid integer you can compare a pointer to is 0 (null)."
        I set ptr to nullptr, passed through fx test. I compare nullptr(ptr) to -1.
        So didn't I compare a valid int (-1) to a null ptr(ptr)?

  • Chaithu. K.C.K.

    How the c compiler will check the syntax, semantic  errors ?

  • Bernhard

    Hello! I found a small mistake in one of the code examples (I posted a bit of the rest of the code with it to provide context):

    "input" in line 19 of the original code you posted should be "index", there is no variable "input".

    Once again, thank you for the great tutorial :)

  • Evaldas

    How come in the index input example, after the the continue command doesn't work for me?
    I've tried replacing it with an std::cout and it printed me it, but when I use continue, the do while loop just breaks, like it would count index as 0 and move on. Is it supposed to be like that?

    • Alex

      No, it's not supposed to be like that. I'm not sure why you'd be seeing that behavior.

      • Evaldas

        Continue doesn't seem to work at all for me in do while loops.. I'm using Code::Blocks. I've tried writing even a simple program and it would just terminate the program for some reason.

        If I input 6, it just terminates the loop.
        I've tried it with a while as well.

        It terminates as well for some reason.

        • Alex

          The behavior in the second case makes sense -- when you continue, you're going back up to the top of the loop. At that point, number == 6, and while (6 < 5) evaluates to while (false), so the loop terminates. The first one is a little more perplexing. It turns out that inside a do while loop, continue takes you to the bottom of the loop, not the top! So the same thing is happening here. I actually didn't realize this, so you just taught me something new. :)

          • Shiva

            Yes, that example is broken. As far as I know continue can't  jump past the loop condition; it skips only the rest of the statements inside the loop body, and moves the control to the loop condition for the next iteration. So the continue statement in this example does nothing. Now, even if std::cin fails, it will (somehow) read 0 into index (you can verify this by printing index after each extraction).

            So even if I enter a non-integer, it will pass the loop condition and break out of it, and print Letter #0 ('H') for me. That's not what the programmer intended - a semantic error in the chapter about semantic errors? ;)

            I deleted the continue statement and replaced it with:

            Now it works as expected.

            The same example comes twice in the lesson, I hope Alex will update both. :)

            • Alex

              Although the continue in the example above is extraneous from a functionality perspective (because the loop ends immediately after anyway), in my mind, it's more correct to have it than not. The intent of the nested block is to restart the iteration after executing -- therefore, it's more correct to do this explicitly than rely upon the loop ending naturally to do this implicitly.

              You're right that the example has an error, since the value of index could be undefined when the loop condition evaluates if the error condition triggered. I'll fix the example. :)

            • Shiva

              Totally agree. Boy, I was the guy who asked you how I wanted to have a continue statement in a default switch-case (I said including that made the logic clearer), and you told me to go with it if I liked it that way, remember?. Of course it doesn't appear extraneous to me.

              The reason why I pointed it out is, I imagined you to be still thinking that continue would make the control jump to the top of the do block, while it actually goes to the bottom. Looks like you were not. :D

              Anyway good update. Instead of replacing the continue like I did, you added index = -1; above it. This way is better. But you misnamed the variable, as reader Bernhard rightly spotted, please correct it as well. :)

      • Evaldas

        Perhaps I’ve made a mistake rewriting everything in the do while? I’m not sure.

  • Eyad

    i know its not related to the content but i have to ask this question......
    can i click a random bunch of ads to support this site, then enable the adblock :D ?

  • Connor

    Hello again Alex, I've a couple questions:

    1. I understand how to handle an error of a null pointer passed to a function but how would you handle a dangling pointer? Is there a way to check if a pointer is dangling vs only null?

    2. With respect to

    why do we use:

    after? Also I still don't understand why 32767 - is this the max size of a standard buffer?

    3. With respect to using

    for an int; if I enter a char type is true (so I assume it doesn't cast the char to type int) but if I enter a float or double it takes the value in front of the '.', is there a way to handle a float -> int error?

    4. How/where do we view the

    error sent to the OS?

    5. How is cerr any different than cout?

    6. For the assert() piece, as well as

    , for this example we also have to

    . It took me a while to figure this out lol.

    7. Finally, back in 2009 you mentioned something about wanting to do a book, well I'll be the first to buy it! WHEN CAN WE BUY YOUR BOOK!!!!!???

  • wildbartty

    It might be a good idea to update the if statements so that they use proper syntax

  • Lokesh

    In the assert statement code example, there is no type or size of std::array. It produces a compile time error:
    argument list for class template "std::array" is missing
    I think it should be:

    since its checking between index 0 and 9, where "type" is the data type of the array.

  • Len

    Hi Alex,
    Assert is fantastic, I had never heard of this before.
    What about the try/catch keywords?  This is normally what I use (although now I see that "assert" is often a better choice).

    • Alex

      Assert and try/catch are really for different things.

      Try/catch is best when you expect something to succeed, but want to cover the case where it may not.

      Assert is best when you need to ensure something is true before you do something else.

      I cover try/catch later, in the lesson on exception handling.

  • Mr D

    Hi Alex,

    You're using the word "user" to describe both the computer programmer AND the end user of the program, which is a bit confusing!! Why not use "programmer" instead?!

    • Alex

      Good comment. I've updated the language slightly to help clarify when I'm talking about the end-user and when I'm talking about the programmer or program.

  • cpplx

    liberally - i dont like the word, consider using more appropriate one.

    does using asserts "liberally" create overhead?
    in other words, does it take time to check the condition?

    • Alex

      Yes, using assert has an overhead. Consequently, many people make them conditional so they're only compiled into debug builds. That way if somethings goes wrong during testing, the assert will (hopefully) fire, but your production code won't have the performance hit.

      • Pablo

        This is perhaps an advanced topic, but how do you make part of your code compile only in debug builds?

        • Alex

          Easiest way is to use the preprocessor:

          (and make sure that DEBUG is defined only for your debug build only).

          Visual Studio defines _DEBUG automatically as part of default debug builds, so you can use that if you want.

          • Pablo

            I think it will be a long time until I can really take advantage of this, but good to know! (Also, it is good to know a bit of these more advanced features, in case you have to read other people's code).

            As always, thank you. It is impressive the way you keep up to date with the comments!

  • newUser

    I'm still wondering how a good assert should be written. Like should it be written a line above a for statement while copying the statement's arguements? If this is correct, might we want to put this in the tutorial as a "best practices" while giving context to how it's written?

    • Alex

      As noted in the article, assert statements are typically used at the top of functions to check that the parameters are valid, as well as the function return value, to ensure an error didn't occur. Assert is only appropriate if the condition can't be fixed (e.g. by asking the user to try again).

  • To answer that question Michel, it's good practice to put your own error handling in where ever possible.

    The error handling can be used to put the errors into terms that the end user can understand, not just the programmer.

    Error handling is not just good for debugging code, it's also good to help point out errors in live programs being used by a client or user.

    Alex THANK YOU FOR THIS ARTICLE!! I just sent it to a friend of mine who needed to see this, shall we say. ;)

  • On Visual Studio 2008 Professional Edition, when an error is created, it states all the information in the Error List including which file and which line in that file the error occured. Therefore, do you need to use the assert() function when you are using VS 08 Pro?

    • Alex

      Yes. Your compiler will only catch syntax errors during compile time. Semantic errors and other runtime errors are your responsibility to detect and handle.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]