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.
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; } } |
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.
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.
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
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!