Search

7.12 — Handling errors (assert, cerr, exit, and exceptions)

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.

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.

Assert

Using a conditional statement to detect a violated assumption, along with printing an error message and terminating the program, is such a common response to problems that C++ provides a shortcut method for doing this. This shortcut is called an assert.

An assert statement is a preprocessor macro that evaluates a conditional expression. If the conditional expression is true, the assert statement does nothing. If the conditional expression evaluates to false, an error message is displayed and the program is terminated. This error message contains the conditional expression that failed, along with the name of the code file and the line number of the assert. This makes it very easy to tell not only what the problem was, but where in the code the problem occurred. This can help with debugging efforts immensely.

The assert functionality lives in the <cassert> header, and is often used both to check that the parameters passed to a function are valid, and to check that the return value of a function call is valid.

If the program calls GetArrayValue(-3), the program prints the following message:

Assertion failed: index >= 0 && index <=9, file C:\\VCProjects\\Test.cpp, line 6

We encourage you to use assert statements liberally throughout your code.

Making your assert statements more descriptive

Sometimes assert expressions aren’t very descriptive. Consider the following statement:

If this assert is triggered, the assert will say:

Assertion failed: found, file C:\\VCProjects\\Test.cpp, line 34

What does this even mean? Clearly something wasn’t found, but what? You’d have to go look at the code to determine that.

Fortunately, there’s a little trick you can use to make your assert statements more descriptive. Simply add a C-style string description joined with a logical AND:

Here’s why this works: A C-style string always evaluates to boolean true. So if found is false, false && true = false. If found is true, true && true = true. Thus, logical AND-ing a string doesn’t impact the evaluation of the assert.

However, when the assert triggers, the string will be included in the assert message:

Assertion failed: found && "Car could not be found in database", file C:\\VCProjects\\Test.cpp, line 34

That gives you some additional context as to what went wrong.

NDEBUG and other considerations

The assert() function comes with a small performance cost that is incurred each time the assert condition is checked. Furthermore, asserts should (ideally) never be encountered in production code (because your code should already be thoroughly tested). Consequently, many developers prefer that asserts are only active in debug builds. C++ comes with a way to turn off asserts in production code: #define NDEBUG.

Some IDEs set NDEBUG by default as part of the project settings for release configurations. For example, in Visual Studio, the following preprocessor definitions are set at the project level: WIN32;NDEBUG;_CONSOLE. If you’re using Visual Studio and want your asserts to trigger in release builds, you’ll need to remove NDEBUG from this setting.

Do note that the exit() function and assert() function (if it triggers) terminate the program immediately, without a chance to do any further cleanup (e.g. close a file or database). Because of this, they should be used judiciously (only in cases where corruption isn’t likely to occur if the program terminates unexpectedly).

Exceptions

C++ provides one more method for detecting and handling errors known as exception handling. The basic idea is that when an error occurs, the error is “thrown”. If the current function does not “catch” the error, the caller of the function has a chance to catch the error. If the caller does not catch the error, the caller’s caller has a chance to catch the error. The error progressively moves up the stack until it is either caught and handled, or until main() fails to handle the error. If nobody handles the error, the program typically terminates with an exception error.

Exception handling is an advanced C++ topic, and we cover it in much detail in chapter 15 of this tutorial.

7.13 -- Command line arguments
Index
7.11 -- Recursion

46 comments to 7.12 — Handling errors (assert, cerr, exit, and exceptions)

  • 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.

  • 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. 😉

  • 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).

  • 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!

  • 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.

  • 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.

  • 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.

  • wildbartty

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

  • 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 cin.fail 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!!!!!???

  • 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 😀 ?

  • Evaldas

    Hey,
    How come in the index input example, after the std::cin.fail() 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. 😀

              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.

  • 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 🙂

  • Chaithu. K.C.K.

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

  • Rob G.

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

    error:
    .\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)?

  • 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.

  • Chris

    Alex,

    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?

  • 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".

  • 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.

Leave a Comment

Put C++ code inside [code][/code] tags to use the syntax highlighter