University of Leeds

De-bugging using print statements

Introduction

The simplest way to track down run-time bugs is by printing values of variables to the terminal. We can then watch as the code runs and spot any strange or unexpected values. A similar method is to 'dump' the contents of variables and arrays into a text file (or log file) which can then be analysed.

Example

It is actually rather hard to come up with examples of how to de-bug. The examples often feel contrived and do not reflect that many bugs are often very subtle and hard to spot when made 'genuinely'. Therefore, this will actually be a 'real' example in the sense that I will write and run it and document whats happens without first polishing it (most of the sample code I present didn't work the first time I wrote it. I actually spend a lot of time de-bugging to make sure the examples work properly and then present a polished version).

I will write some code to print the Fibonacci sequence (0, 1, 1, 2, 3, 5, 8, 13...).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 0; i < n; i++) {
    // work out the next term in the sequence
    int fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;
    // shift the values in the sequence
    fn_minus1 = fn;
    fn_minus2 = fn_minus1;
  }
}

Testing

The code actually compiled first time without warnings or errors! This is no real surprise, the code is fairly simple and I always structure code properly and am careful to check semi-colons and brackets as I write. Now I'll run it and print the first 10 terms.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
1
2
4
8
16
32
64
128
256
512

It doesn't work. The sequence is wrong. I actually thought it would work first-time and I would have to go back and put a deliberate mistake in it so I could then go back and pretend to look for a mistake! I can see that the numbers are going up by a factor of 2 each time (I have taught digital electronics for a long time and so recognise those numbers!) so my first thought is that I'm multiplying something by 2 inside the loop, but looking back at the code there are no multiplications. Mmmmm.

Adding print statements

The code currently just outputs the Fibonacci number, but I'll print out the intermediate variable values to see if that can help to track down the bug.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 0; i < n; i++) {
    // work out the next term in the sequence
    int fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;
    // shift the values in the sequence
    fn_minus1 = fn;
    fn_minus2 = fn_minus1;
    std::cout << "fn = " << fn << " fn-1 = " << fn_minus1
              << " fn-2 = " << fn_minus2 << std::endl;
  }
}

I can now compile and run again, and then inspect the values.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
1
fn = 1 fn-1 = 1 fn-2 = 1
2
fn = 2 fn-1 = 2 fn-2 = 2
4
fn = 4 fn-1 = 4 fn-2 = 4
8
fn = 8 fn-1 = 8 fn-2 = 8
16
fn = 16 fn-1 = 16 fn-2 = 16
32
fn = 32 fn-1 = 32 fn-2 = 32
64
fn = 64 fn-1 = 64 fn-2 = 64
128
fn = 128 fn-1 = 128 fn-2 = 128
256
fn = 256 fn-1 = 256 fn-2 = 256
512
fn = 512 fn-1 = 512 fn-2 = 512

That's strange, fn, fn_minus1 and fn_minus2 all have the same value. Actually, looking back at the code it's not strange at all. What an idiot! I calculate the new value of fn on line 16. On line 20, I then assign this new value to fn_minus1 and then on line 21, I then also assign it (via fn_minus1) to fn_minus2. That's why the same value is stored in all the variables. Thinking about it, I need to update the values of fn_minus1 and fn_minus2 before calculating the new value of fn. Try again.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  int fn;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 0; i < n; i++) {
    // shift the values in the sequence
    fn_minus1 = fn;
    fn_minus2 = fn_minus1;
    // work out the next term in the sequence
    fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;

    std::cout << "fn = " << fn << " fn-1 = " << fn_minus1
              << " fn-2 = " << fn_minus2 << std::endl;
  }
}

Note that I moved the declaration of fn outside of the for loop as it needs to be declared before it is used on line 17. I was actually prompted to do this by a red squiggly line appearing under the variable in the editors (Visual Studio Code). This highlights the usefulness of using a good IDE which can warn you about potential errors. I wouldn't have spotted this otherwise until I compiled and got an error. Run the new version.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
2
fn = 2 fn-1 = 1 fn-2 = 1
4
fn = 4 fn-1 = 2 fn-2 = 2
8
fn = 8 fn-1 = 4 fn-2 = 4
16
fn = 16 fn-1 = 8 fn-2 = 8
32
fn = 32 fn-1 = 16 fn-2 = 16
64
fn = 64 fn-1 = 32 fn-2 = 32
128
fn = 128 fn-1 = 64 fn-2 = 64
256
fn = 256 fn-1 = 128 fn-2 = 128
512
fn = 512 fn-1 = 256 fn-2 = 256
1024
fn = 1024 fn-1 = 512 fn-2 = 512

Still not working. Annoying. I thought that would have fixed it. Looking at the values printed I can now see that fn has a different value to fn_minus1 and fn_minus2 (which are still the same). Need to go and look back at the good.

Ah. Should have realised this earlier. On line 17, fn_minus1 is updated, and then fn_minus2 is updated to the same value on line 18. These lines need to be swapped i.e. fn_minus2 should store the 'old' version of fn_minus1 before it is updated (with the 'old' version of fn). Try again.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  int fn;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 0; i < n; i++) {
    // shift the values in the sequence
    fn_minus2 = fn_minus1;
    fn_minus1 = fn;

    // work out the next term in the sequence
    fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;

    std::cout << "fn = " << fn << " fn-1 = " << fn_minus1
              << " fn-2 = " << fn_minus2 << std::endl;
  }
}

Compile and run the new version.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
474362322
fn = 474362322 fn-1 = 474362322 fn-2 = 0
948724644
fn = 948724644 fn-1 = 474362322 fn-2 = 474362322
1423086966
fn = 1423086966 fn-1 = 948724644 fn-2 = 474362322
-1923155686
fn = -1923155686 fn-1 = 1423086966 fn-2 = 948724644
-500068720
fn = -500068720 fn-1 = -1923155686 fn-2 = 1423086966
1871742890
fn = 1871742890 fn-1 = -500068720 fn-2 = -1923155686
1371674170
fn = 1371674170 fn-1 = 1871742890 fn-2 = -500068720
-1051550236
fn = -1051550236 fn-1 = 1371674170 fn-2 = 1871742890
320123934
fn = 320123934 fn-1 = -1051550236 fn-2 = 1371674170
-731426302
fn = -731426302 fn-1 = 320123934 fn-2 = -1051550236

What! This is actually getting worse. My office is suddenly starting to feel very warm. I should have actually spent some time de-bugging it before documenting this! This could turn out to be embarrassing. Need to focus.

Right, fn is completely wrong. I know this is usually caused by using an incorrect data type (e.g. trying to store a float in an int) or not initialising properly. Bingo! What a moron. I use fn on line 18, but I didn't actually initialise it on line 11. That is exactly why in an earlier lab I told you to always ensure you initialise your variables! Not following my own advice and it has come back to bite me. I should just need to initialise fn with 1 (0 + 1 = 1) and hopefully it will work!

#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  int fn = 1;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 0; i < n; i++) {
    // shift the values in the sequence
    fn_minus2 = fn_minus1;
    fn_minus1 = fn;

    // work out the next term in the sequence
    fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;

    std::cout << "fn = " << fn << " fn-1 = " << fn_minus1
              << " fn-2 = " << fn_minus2 << std::endl;
  }
}

Try and run it again.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
1
fn = 1 fn-1 = 1 fn-2 = 0
2
fn = 2 fn-1 = 1 fn-2 = 1
3
fn = 3 fn-1 = 2 fn-2 = 1
5
fn = 5 fn-1 = 3 fn-2 = 2
8
fn = 8 fn-1 = 5 fn-2 = 3
13
fn = 13 fn-1 = 8 fn-2 = 5
21
fn = 21 fn-1 = 13 fn-2 = 8
34
fn = 34 fn-1 = 21 fn-2 = 13
55
fn = 55 fn-1 = 34 fn-2 = 21
89
fn = 89 fn-1 = 55 fn-2 = 34

Woo hoo! Finally. That shouldn't have taken so long. Now I can see why it takes you a two-hour lab session to do a few simple tasks...

I'll just go and comment out the de-bug print lines to tidy the output.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
1
2
3
5
8
13
21
34
55
89

I don't believe this. I just looked at the output and thought 'that looks like more than 10 numbers'. Yes, counted them and there are 12. I told it to print 10 values. Ah. My loop iterates n times, but I actually print the first 2 values outside of the loop. I just need to modify the loop so it starts at 2 instead of 0.

Enter the number of terms in the Fibonacci sequence to print: 10
0
1
1
2
3
5
8
13
21
34

Done. Time for a brew. I'm actually sweating.

Debug mode

In the previous example, I added in the de-bug print statement during development, and then commented it out for the final 'release' version. This is similar to what was covered in the testing lab. We can use the pre-processor to turn on the debug messages using a compile flag e.g.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>

int main() {
  int n = 0;
  std::cout << "Enter the number of terms in the Fibonacci sequence to print: ";
  std::cin >> n;

  // seed values
  int fn_minus1 = 0;
  int fn_minus2 = 1;
  int fn = 1;
  std::cout << fn_minus1 << "\n" << fn_minus2 << std::endl;

  // loop through the number of terms
  for (int i = 2; i < n; i++) {
    // shift the values in the sequence
    fn_minus2 = fn_minus1;
    fn_minus1 = fn;

    // work out the next term in the sequence
    fn = fn_minus1 + fn_minus2;
    // print to the terminal
    std::cout << fn << std::endl;

#ifdef DEBUG
    std::cout << "fn = " << fn << " fn-1 = " << fn_minus1
              << " fn-2 = " << fn_minus2 << std::endl;
#endif
  }
}

Here the de-bug print is contained within an ifdef directive. We can therefore turn on the debug messages by compiling as follows.

g++ -Wall -std=c++11 -o main.exe main.cpp -D DEBUG

Summary

That was stressful. I didn't expect that many bugs, but I'm actually glad as it goes some way to showing a typical development cycle. If even an experienced programmer can make silly mistakes when writing a simple program, why should a novice be any different? Making mistakes isn't a problem, but being unable to find and fix them is. I didn't have any one here to help me. I just had to print the values out, inspect them and then think about what could have caused them by looking back at the code. I can teach you syntax and techniques, but I can't teach you how to think. You need to develop that by practising fixing your own code. Please bear this in mind the next time you are about to put your hand up and get help!