Search

3.x — Chapter 3 summary and quiz

Quick Summary

A syntax error is an error that occurs when you write a statement that is not valid according to the grammar of the C++ language. The compiler will catch these.

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

The process of finding and removing errors from a program is called debugging.

We can use a five step process to approach debugging:

  1. Find the root cause
  2. Understand the problem
  3. Determine a fix
  4. Repair the issue
  5. Retest

Finding an error is usually the hardest part of debugging.

Static analysis tools are tools that analyze your code and look for semantic issues that may indicate problems with your code.

Being able to reliably reproduce an issue is the first and most important step in debugging.

There are a number of tactics we can use to help find issues:

  • Commenting out code
  • Using output statements to validate your code flow
  • Printing values

When using print statements, use std::cerr instead of std::cout. But even better, avoid debugging via print statements.

A log file is a file that records events that occur in a program. The process of writing information to a log file is called logging.

The process of restructuring your code without changing what it actually does is called refactoring.

unit testing is a software testing method by which small units of source code are tested to determine whether they are correct.

Defensive programming is a technique whereby the programmer tries to anticipate all of the ways the software could be misused. These misuses can often be detected and mitigated.

All of the information tracked in a program (variable values, which functions have been called, the current point of execution) is part of the program state.

A debugger is a tool that allows the programmer to control how a program executes and examine the program state while the program is running. An integrated debugger is a debugger that integrates into the code editor.

Stepping is the name for a set of related debugging features that allow you to step through our code statement by statement.

Step into executes the next statement in the normal execution path of the program, and then pauses execution. If the statement contains a function call, step into causes the program to jump to the top of the function being called.

Step over executes the next statement in the normal execution path of the program, and then pauses execution. If the statement contains a function call, step over executes the function and returns control to you after the function has been executed.

Step out executes all remaining code in the function currently being executed and then returns control to you when the function has returned.

Run to cursor executes the program until execution reaches the statement selected by your mouse cursor.

Continue runs the program, until the program terminates or a breakpoint is hit.
Start is the same as continue, just from the beginning of the program.

A breakpoint is a special marker that tells the debugger to stop execution of the program when the breakpoint is reached.

Watching a variable allows you to inspect the value of a variable while the program is executing in debug mode. The watch window allows you to examine the value of variables or expressions.

The call stack is a list of all the active functions that have been executed to get to the current point of execution. The call stack window is a debugger window that shows the call stack.

Quiz time

Question #1

Use the integrated debugger to step through this program and watch the value of x. Based on the information you learn, fix the following program:

Show Solution

Question #2

Use the integrated debugger to step through this program. For inputs, enter 8 and 4. Based on the information you learn, fix the following program:

Show Solution

Question #3

What does the call stack look like in the following program when the point of execution is on line 4? Only the function names are needed for this exercise, not the line numbers indicating the point of return.

Show Solution

Author's note

It’s hard to find good examples of simple programs that have non-obvious issues to debug, given the limited material covered so far. Any readers have any suggestions?


4.1 -- Introduction to fundamental data types
Index
3.10 -- Finding issues before they become problems

70 comments to 3.x — Chapter 3 summary and quiz

  • nascardriver

    Your comments are correct. Assuming that `readNumber` does the same every time (ie. it doesn't have an internal counter or similar), you can call `readNumber` directly, because the order doesn't matter. For subtraction and division, the order matters, so we have to know which number was entered first.

    This will still perform a-b, but we don't know which `readNumber` gets called first, so the user might think they're providing a value for `a` when they're actually in the call of `b`, or the other way around. The order is undefined.
    If you need a specific order, you have to call the functions one after the other. Otherwise your code is broken.

  • sami

    I think as the Best Practice, you should have initialized x to zero.

  • Typo

    "Stepping is the name for a set of related debugging features that allow you to step through our code statement by statement."

    I think it should be "your" code not "our" code.

  • Jacy

    This is my answer for question 1, and it somehow works...anything I did unconventionally?

  • Alek

    Hello,in #1 question i tried below code using pointer or even reference but it's undefiend behavior(everytime I get different wrong values of summation unless I input two same numbers!) :

    I know where the problem occurs It's most likely in this part:x = x + readNumber(&x);
    something is happening there in memory address and the value of x but I don't know what's happening behind the scene, any idea ? thanks in advance!

    • nascardriver

      You identified the correct line. Modifying and accessing a variable in the same statement is undefined. Maybe `readNumber` gets called first, maybe the value of `x` is read first.
      We talk more about this in the lesson about increment and decrement operators.

      • Alek

        thanks for your answer! what about that minor issue where you say:A minor issue is that readNumber is taking an argument when it should have a local variable instead.could you tell me please why it's better to be this way ? cuz the other way can work just fine too!

        • nascardriver

          You need to create a variable, then assign to it. If `readNumber` returned the value and `x` was local, you could just do

  • Mitchell G

    Before looking at the solution this was my fix to the first question:

    I would like to say thanks for making this website an open resource for everyone looking to learn C++. I have had a really fun time going through these articles and I commend all of the people who helped make this possible.

  • Al

    First, thank you for all your hard work: this guide is absolutely excellent!

    Next, I have been playing a bit with gdb, and I was wondering how could the following semantic error be caught. Imagine that I pass an integer as an argument to my program, the program doesn't perform any type conversion, and I try to calculate that number + 1. For example, let's say this is a line of the program (assuming `argv[1]` is the first arguments passed):

    Obviously, the resulting output would be far from correct. Now g++ doesn't warn me about this, so my question arises: what information should I ask gdb for in order to pinpoint the cause of the dysfunction? The problem in this case is pretty obvious, but  how would you look for this kind of semantically wrong type errors in large, complex programs using gdb?

    Thank you in advance.

    • nascardriver

      If `argv[1]` exists (ie. `argc >= 1`), the error cannot be caught automatically by a debugger. The behavior is well defined, `argv[1] + 1` produces no output at all or removes the first character. We talk about this later in the lesson about pointer arithmetic. A tool cannot know if you did this intentionally or on accident.
      You'd catch this kind of problem by writing tests. Another program, one dedicated to testing software, launches your program with specific inputs and checks if the output is the expected one. For small programs you can do this by hand, just launch your program and you'll see that it's not working.
      The rest is manual work. Search for the failing piece of code and fix it.

  • Ryan

    I had a possible suggestion for an error that may be less-obvious, only uses elements learned up to this point, and is able to be diagnosed with the debugger. I'm not sure if it would necessarily always work out to be an apparent issue, though.

    The error is that the order in which parameters are evaluated is not universally defined. On my system (using Visual Studio Community 2019), the right parameter is evaluated first, and so entering inputs '4' and '3' ends up outputting -1. Using the debugger, I can see x ends up holding my second input, and y holds my first input. As a solution, I would instead initialize separate variables with the function calls, in the desired order.

  • Apaulture

    I think it's a great opportunity for learners to try and fix example #2, not just semantic errors, but also the naming of variables. The pass by value x/y can be confused with writeAnswer's parameter int x.

  • alex

    Im trying to do the first quiz, debugging the code? everytime i try to press debug the program just gives me 1 failed and nothing happens, whats going on here?

    i can debug fine on code that has no errors but 1 error and  i cant debug, so whats the point of debugging wtf lol, im confused, what am i missing?

  • Giang

    Thank for these lessons. But I still can't distinguish the main differences between Run to cursor and Breakpoint

    • Humam

      The difference is simple.

      You have the following program:

      If you use "Run To Cursor" on Line 3 of my example code above, the program will execute until it reaches Line 3 for the first time after it has been invoked by my first function call on Line 8, then I will have regained control in debug mode. If I chose to "Continue", the rest of the program will execute ~NON STOP~ until the program ends (reaches return 0;).

      However, if I set my "Breakpoint" on Line 3, when I execute the program, it will give me control back at Line 3 ~EACH TIME~ execution reaches the breakpoint I placed!
      So for the first time it is invoked at Line 8, I will regain control once it reaches Line 3. (The breakpoint we set there)
      If I chose to "Continue", the second time it is invoked at Line 9, I will again regain control once it reaches Line 3. (The breakpoint we already have there)
      If I chose to "Continue", the third time it is invoked at Line 10, I will regain control once it reaches Line 3. (The breakpoint we already have there)
      If I chose to "Continue", the program will end at return 0 as we want.

      So:

      Breakpoints will continue to stop the point of execution at the point it is placed at, until we remove the breakpoint itself!

      Even better, we can use multiple breakpoints in our program! Unlike "Run To Cursor" which can only be used once every time we want to use it.

      Hope that makes sense! :)

  • HolzstockG

    I did the first one a little bit easier by using expression inside of curly braces of "x" variable

    • chai

      you don't want to get into a habit of this. The problem is the machine does't know which function call is executed first. Might not be a problem now but may be later on. Not a good practice.

      • Apaulture

        When Alex mentioned the order of execution, I believe it was regarding the order of arguments passed to the called function. In this case, it looks valid. The left readNumber() gets called then the right readNumber() gets called. Nevertheless, I would still avoid this as it is less readable than

        and it is good practice to initialize the variable with a literal or zero-initialize if the value is expected to be replaced.

        • nascardriver

          It's unspecified which `readNumber` gets called first.
          If you know the value of a variable at its declaration, you should initialize it. Only initialize with empty braces if you don't know the value.

          • Viktor

            Just checking for clarification, as I am a bit confused: If we compare this to the commutative law in mathematics, does it mean that:

            Also, is one better practise, or is it purely based on what you wish to use it for?

            P.S: Thanks for an excellent website, best C++ tutorial I've seen so far! I see you've put a lot of effort into this, and I greatly appreciate you sharing your knowledge to others, free of charge. Just a massive thank you for that!

            • nascardriver

              Please edit comments instead of deleting and re-posting. Syntax highlighting works after refreshing the page. My reply is here
              https://www.learncpp.com/cpp-tutorial/chapter-3-summary-and-quiz/comment-page-1/#comment-463570

  • Jake

    Hi Alex.  Another wonderment at not being properly stopped, but this time at the run level.

    In question 2, line 22, we are obviously supposed to run into the zero-divide error.  However, if I run without debugging, or run it in a CMD window, all I get is the standard Windows error box "Program Quiz-3.2 has stopped working". The only time I see the "Unhandled Exception" box is if I try to step over the

    line.

    Why won't is kick me out the way it would in Unix?  OK, maybe not the crash dump but at least a zero-divide error croak message! (I am trying to provoke the bouncer.  I'm SO sociopathic today!  :-)

  • Jake

    Question, Alex. (Yuh think? :-)

    Since we defined the readNumber() function as returning an int, why didn't I get a compile error, or at least a warning, at line 13, where we are discarding the return value and treating it like a void function?  Of course I saw this by eyeball but even with the "treat warnings like errors" setting (as well as level 4), I got no error.

    Thanks for this tutorial!

    • nascardriver

      Hi Jake,

      it's perfectly legal to discard the return value of a function. For `readNumber`, it doesn't make sense to discard it, because the whole point of the function is to produce the value that it returns.
      There are functions that return values we might not care about, because their purpose is something else and the return value may or may not be relevant.
      When you write a function like `readNumber`, where discarding the return value is considered to be a mistake, mark it as `[[nodiscard]]`.

      `[[nodiscard]]` tells the compiler that you want a warning to be printed if the return value is discarded.

      • Jake

        WOW!  In all the C++ books I have, I found one mention of [[nodiscard]] in the Stroustrup book "A Tour of C++
        Second Edition", page 216,  without a description of what that does.  I'm using the community version of Visual Studio 19 and when I added [[nodiscard]] that to the code I got this error message:

        On the other hand, I do have a complete Cygwin environment and when I compiled it with that release of g++ I did get the expected error message:

        But it still produces an executable.  And when I ran that executable with the bad code I got the error I had originally expected:

        Floating point??  No mention of zero-divide? OK, not exactly the expected error message.

        This has been VERY instructive, although I'm not totally ready to start using the Cygwin environment for this tutorial.

        • nascardriver

          `[[nodicard]]` asks the compiler to produce a warning, not an error. If you want an error, you need to treat Wunused-result as an error

          VS should support `[[nodiscard]]` too, you just didn't set up your project correctly. In you project's settings, enable the highest standard support that's available (Should be C++17). `[[nodiscard]]` was added in C++17.

        • Math

          Hello, is "A Tour of c++" a good book for a begginer??

  • Mike

    For Quiz #2, I copied and pasted your sample code twice now, and it compiles and executes, but as soon as I press enter after entering the second number, it crashes every time with a Windows error that my test.exe has stopped working. One of my options is "Debug the Program" which I thought was fitting considering this chapter. I tried it, but it seems way to advanced for me.

    Any suggestions as to why this is happening? BTW: I'm using Visual Studio 2019.

    The Exception code is c0000094, if that helps. I just googled it. Appears it may be related to dividing by zero. But why, I entered 8 and 4 as per your instructions?

    • It's supposed to crash. The purpose of the quiz is that you figure out why. You can solve it by reading the code too, but you won't learn how to use the debugger that way.

      • Mike

        Ha! too funny! I guess I just wasn't expecting a full blown crash of the program at this point, though it makes perfect sense. Can't wait to try to debug it now!

        Sorry for wasting your time on this one.

  • Jose

    Hi, question #3 shows the functions sorted this way:

    but if I had to write that code I would have sorted them this way:

    Is there any rule/best practice about sorting the functions called from main()?

  • Joe Das

    Outstanding Tutorial. You are really helping me get my old coding chops back so quickly, and your discussion points about good working practices are invaluable.

    You were asking about nasty problems in debugging: An example which will quickly aggravate coders but which does happen is when a variable is misspelled because it is declared twice by similar names and the wrong variable is used in a function. This would tie back into your recommendations about variable naming. This can be made even nastier with confusing pre-processor statements >:D.

    • Alex

      > An example which will quickly aggravate coders but which does happen is when a variable is misspelled because it is declared twice by similar names and the wrong variable is used in a function

      Agreed -- that's pretty similar to quiz question #2, no?

  • Lawrence

    Why can't I add enter a value when I am stepping in during debugging?

  • NotJoeRogan

    Hello Alex or Nas,

    Let's say that I enter a breakpoint at line 7 of the program in the first question, and for the number I enter 4. Why is it that when I hover my cursor over all of the x variables throughout the entire program, they show a value of 4?

    Regards,
    NotJoeRogan

    • Alex

      When you hover over a variable, the debugger will show you the _current_ value for that variable, not the value the variable might have wherever you are hovering. This can be misleading the variable had a different value at the point you're hovering

      To do otherwise would require the debugger to remember what values every variable had at every point in the program, which would be significantly more complex, and potentially misleading in other ways (you might think you're seeing the current value when you aren't).

  • Maura

    Please better explain on Question #3 why the answer for the call stack is
    d
    b
    a
    main

    and not instead
    d
    c
    b
    a
    main

    • Jason

      Because when the stack reaches function b, it begins it execution.

      First it reaches the call to function c.
      At this point the stack is c/b/a/main.
      However c does not make any calls and fully resolves itself.
      At which it returns to the next object in the stack b, and moves on to the call to function d.
      Thus the stack is d/b/a/main

    • Alex

      main calls a()
      a calls b()
      b calls c()
      c terminates
      b calls d()
      At this point we've hit our breakpoint.

      Working backwards, the functions still in memory are:
      d
      b
      a
      main

      c isn't in the list because it terminated and was removed from the call stack before d was called.

  • John Doe

    What about leveraging your example of function argument order of evaluation issues in chapter 2.3? It would be a weird scenario but would force the user to step through while debugging and see the flow issues in their stack. Testing in visual studio and gcc, this processed right to left, but clang did left to right.

    • Alex

      Good thought! However, the program would only manifest the problem on some machines and not others, making it a hard one to debug on machines that didn't exhibit the issue.

  • Louis Cloete

    Debugging exercise ideas: use "/n" for a newline somewhere instead of "\n" and watch the ensuing chaos when /n prints on the screen... ;-p

    Do something like this:

    [This next idea I borrow from my Delphi textbook. You will have to wait till after you've done loops, strings and arrays for this one.]

    The output line only executes once, because of missing braces around the intended loop body. The function prints only the last character of the string.

    • Hi!

      > Uninitialised variable causes undefined behaviour
      The value of @x is undefined, behavior is well defined.

      > next idea
      @s should be const and @i should be an @std::size_t (Or cast s.length()).
      This code wouldn't compile, because line 9 cannot see @c.
      I like this one. Even though compilers print a warning, it's a common mistake.

      • Louis Cloete

        About prtStrVertical():

        In Pascal, you must define all local variables for a function or procedure (Pascal's name for a group of statements that doesn't return a value) at the top of the function, so the scoping issue doesn't appear. To work around, either declare char c before the loop, or modify the function like this:

        The modification will cause a different, but related, bug.

    • Alex

      Thanks for the thoughts! The '/n' idea should be easy to diagnose by inspection. I'm trying to find things that are not quite as obvious to inspection, to validate the usefulness of having an integrated debugger.

      There are a ton of fun debugging problems once we do loops -- but alas, that chapter is a bit far off from this lesson. I didn't want to wait that long to introduce basic debugging topics.

  • Louis Cloete

    Question #1 solution:

    readNumber():

    main():

    l. 18:

    Question #2:

    The problem could've been avoided if the program didn't declare and zero init variables, just to assign different values to them in the first place. A better solution would be to init x and y with the result of separate calls to readNumber(). I think the solution should reflect that. I.e. replace Lines 18 - 21 in the solution with:

  • Anthony

    For the note at the end, you could maybe add "debugging practice revisited" after some chapters since the basics have been covered. Would allow for some more intricate issues to solve and extra reinforcement for practicing good debugging habits.

  • Ethan Smith

    "The process of finding and removing errors from a programming is called debugging" i think you may have made a grammatical mistake there.

Leave a Comment

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