CPP10 - Debugging


Published on

This is an introductory lecture on C++, suitable for first year computing students or those doing a conversion masters degree at postgraduate level.

Published in: Software, Technology
  • Be the first to comment

  • Be the first to like this

CPP10 - Debugging

  1. 1. Debugging Michael Heron
  2. 2. Introduction • In the last lecture we talked about how to find flaws in computer programs. • Or at least, find that there are flaws. • In this lecture we are going to look at strategies for actually locating the errors. • A more subtle skill. • Debugging a program occurs in two locations. • Compile time (fixing syntax errors) • Run time (fixing logic errors)
  3. 3. Syntax Errors • C++ error messages are tremendously unhelpful. • They are useful for people who already know why their code won’t compile. • Luckily syntax errors are the easiest kind of errors to locate. • The compiler usually knows, to a degree, where the code has fallen down.
  4. 4. Compiler Errors • The key piece of information is the line number. • That’s what narrows down our search. • Code in C++ has a very strict syntax. • Deviations from this syntax are not tolerated. • Eventually it becomes second nature to identify a syntax error. • Humans are very good at pattern recognition. • Until then, need to rely on examples. • Lectures have these, as do your previous programs.
  5. 5. Logic Errors • Logic errors are much more difficult to find. • Your program runs, but it doesn’t do what it’s supposed to do. • A certain kind of mindset is required to be able to find why code doesn’t work. • Must be patient. • For complex programs, it can take hours to track down a single annoying bug. • Must be willing to experiment. • Sometimes errors only reveal clues when the code is subtly changed.
  6. 6. Logic Errors • First of all, it’s important to understand what you think the program is doing. • This won’t necessarily be what it is actually doing. • Pen and paper important here. • Trace the activity of each line of code. • Like we do in the lectures and the tutorials. • Draw the flow of logic through the program. • Represent the state of all variables. • See where they are modified.
  7. 7. Logic Errors • Once you understand what you think the program is doing, find out what the program is actually doing. • We can put in debug statements throughout our code to trace the flow of logic. • Simply using cout to print the contents of variables at key parts of the program can be invaluable. • You can see when things aren’t doing what they should be doing.
  8. 8. The Visual Studio Debugger • Visual studio has a powerful debugger built into it. • It lets us follow the exact operation of the computer at each stage of its execution. • However, also important to learn how to do it without the debugger. • It’s not always available when you code. • In fact, good debuggers are unusual in most programming environments.
  9. 9. Detectin’ • There’s a multi-step process we go through to find errors. • Reproduce the error • So very important! • Use lots and lots of debugging messages • Simplify • Try things • Understand why it’s not working • Don’t just fix it
  10. 10. Reproducing An Error • The most important thing when debugging is to be able to reliably reproduce the error. • The most frustrating kind of error is the one that only happens sometimes. • Sometimes it’s possible to put a band-aid solution on a malfunctioning program. • This will cause problems later on. • Best to solve the underlying issue.
  11. 11. Reproducing an Error • An effective testing strategy is the most important thing here. • You need to direct your tests towards the areas where things are likely to fail. • It doesn’t have to be as formal as defining actual test cases. • Although that can help. • If you can isolate the problem to a specific function, try a test harness.
  12. 12. Example Test Harness int main() { int test_x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int test_y[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int expected[] = {2, 4, 6, 8, 10, 12, 14, 16, 18, 20}; int tmp; for (int i = 0; i < 10; i++) { tmp = add_nums (test_x[i], test_y[i]); if (tmp != expected[i]) { cout << "Error in inputs: " << test_x[i] << ", " << test_y[i] << ". Expected " << expected[i] << ", got " << tmp << endl; } } cout << "Testing concluded." << endl; }
  13. 13. Example Output Error in inputs: 1, 1. Expected 2, got 1 Error in inputs: 3, 3. Expected 6, got 9 Error in inputs: 4, 4. Expected 8, got 16 Error in inputs: 5, 5. Expected 10, got 25 Error in inputs: 6, 6. Expected 12, got 36 Error in inputs: 7, 7. Expected 14, got 49 Error in inputs: 8, 8. Expected 16, got 64 Error in inputs: 9, 9. Expected 18, got 81 Error in inputs: 10, 10. Expected 20, got 100 Testing concluded. What’s the likely error here?
  14. 14. Use Debugging Messages • When you have found where the error is, put lots of debugging messages in. • Simple cout statements that contain the value of variables. • Label these clearly • You need to be able to see where they are coming from at each part of the program. • You can get rid of them when you are done. • Comment out rather than delete. • You may need them again later.
  15. 15. Simplify • Sometimes there’s just too much code to work out what is going wrong. • Important to simplify a program when this is occuring. • Comment out lines of code that are doing complex functionality. • Replace them with simpler versions for testing • For example, if you have a line of code generating a random number… • … replace it with a non random number for testing.
  16. 16. Experimentation • The next phase is simply to experiment with the code. • Change and tweak values to see if it makes a different. • Your process up to this point will give you some code where there are likely problems. • Focus your testing around there.
  17. 17. Understanding • Having found the bug, it’s tempting to just fix it. • Not a great idea, understanding why bugs occur is how you become a better programmer. • Don’t simply recast the code. • Work out why the code as it stands doesn’t work. • This is transferable knowledge.
  18. 18. The Wrong Approach for (int i = 0; i < someVariable; i++) { } for (int i = 0; i < someVariable - 1; i++) { } Your patient detecting has discovered the loop iterates one more time than it should. The easy solution is to simply make it loop one less time. But… that’s not the actual problem. The problem is that you expected the loop to occur less frequently than it did. It’s a symptom of a larger problem (the code isn’t doing what you think it is), and a quick fix like this will leave problems unsolved for the future.
  19. 19. A Debugging Exercise int main() { int num, num2; int ans; num = get_input ("Give me a number"); num2 = get_input ("Give me the power you want that number to"); ans = calculate_power (num, num); display_answer (ans); return 0; }
  20. 20. Get Input int get_input (string str) { int tmp; cout << str; cin >> tmp; return tmp; }
  21. 21. Calculate Power int calculate_power (int num1, int num2) { int total = 0; for (int i = 0; i <= num2; i++) { total = total * num1; } return total; }
  22. 22. Display Answer void display_answer (int x) { cout << "The answer is " << x << endl; } This program compiles fine. It doesn’t give the right output. Where is the error? Need a process to find it!
  23. 23. Summary • Getting programs to compile is part of the battle. • The bigger battle is – making it work properly when it does. • This needs a particular kind of mindset to do. • Bugs don’t get fixed instantly, they need a lot of figuring out in larger programs. • Important to understand why errors occur. • That’s how you get better at programming generally.