Letβs say youβve written a program, and itβs not working correctly -- the code compiles fine, but when you run it, youβre getting an incorrect result. You must have a semantic error somewhere. How can you find it? If youβve been following best practices by writing a little bit of code and then testing it, you may have a good idea where your error is. Or you may have no clue at all.
All bugs stem from a simple premise: Something that you thought was correct, isnβt. Actually figuring out where that error is can be challenging. In this lesson, weβll outline the general process of debugging a program.
Because we havenβt covered that many C++ topics yet, our example programs in this chapter are going to be pretty basic. That may make some of the techniques weβre showing here seem excessive. However, keep in mind that these techniques are designed to be used with larger, more complex programs, and will be of more use in such a setting (which is where you need them most).
A general approach to debugging
Once a problem has been identified, debugging the problem generally consists of six steps:
- Find the root cause of the problem (usually the line of code thatβs not working). Weβll discuss some strategies on how to do this in the next lesson.
- Ensure you understand why the issue is occurring.
- Determine how youβll fix the issue.
- Repair the issue causing the problem.
- Retest to ensure the problem has been fixed.
- Retest to ensure no new problems have emerged.
Letβs use a real-life analogy here. Letβs say one evening, you go to get some ice from the ice dispenser in your freezer. You put your cup up to the dispenser, press the lever, and β¦ nothing comes out. Uh oh. Youβve discovered some kind of defect. What would you do? Youβd probably start an investigation to see if you could identify the root cause of the issue.
Find the root cause: Since you hear the ice dispenser trying to deliver ice, itβs probably not the ice delivery mechanism itself. So you open the freezer, and examine the ice tray. No ice. Is that the root cause of the issue? No, itβs another symptom. After further examination, you determine that the ice maker does not appear to be making ice. Is the problem the ice maker or something else? The freezer is still cold, the water line isnβt clogged, and everything else seems to be working, so you conclude that the root cause is that the ice maker is non-functional.
Understand the problem: This is simple in this case. A broken ice maker wonβt make ice.
Determine a fix: At this point, you have several options for a fix: You could work around the issue (buy bags of ice from the store). You could try to diagnose the ice-maker further, to see if thereβs a part that can be repaired. You could buy a new ice maker and install it in place of the current one. Or you could buy a new freezer. You decide to buy a new ice maker.
Repair the issue: Once the ice maker has arrived, you install it.
Retest: After turning the electricity back on and waiting overnight, your new ice maker starts making ice. No new issues are discovered.
Now letβs apply this process to our simple program from the previous lesson:
#include <iostream>
int add(int x, int y) // this function is supposed to perform addition
{
return x - y; // but it doesn't due to the wrong operator being used
}
int main()
{
std::cout << "5 + 3 = " << add(5, 3) << '\n'; // should produce 8, but produces 2
return 0;
}
This code is nice in one regard: the bug is very apparent, because the wrong answer gets printed to the screen via line 10. That gives us a starting point for our investigation.
Find the root cause: On line 10, we can see that weβre passing in literals for arguments (5 and 3), so there is no room for error there. Since the inputs to function add are correct, but the output isnβt, itβs pretty apparent that function add must be producing the wrong value. The only statement in function add is the return statement, which must be the culprit. Weβve found the problem line. Now that we know where to focus our attention, noticing that weβre subtracting instead of adding is something youβre likely to find via inspection.
Understand the problem: In this case, itβs obvious why the wrong value is being generated -- weβre using the wrong operator.
Determine a fix: Weβll simply change operator- to operator+.
Repair the issue: This is actually changing operator- to operator+ and ensuring the program recompiles.
Retest: After implementing the change, rerunning the program will indicate that our program now produces the correct value of 8. For this simple program, thatβs all the testing thatβs needed.
This example is trivial, but illustrates the basic process youβll go through when diagnosing any program.
