CarND: Assertions and Debugging your C++ projects with CLion
The second term of Udacity’s Self-Driving Car Engineer Nanodegree comes with a big change in programming languages: While in the first term we learned how to code up deep learning and computer vision solutions in the interpreted python language, this time around the projects focus on sensor fusion and localization that have to be completed in the compiled C++ language.
For most students that have never worked with C++ this is a giant leap, as they have to be aware of the type of every variable and are responsible for their correct initialization for the first time. The compiler seems to be a constant adversary, nagging the user about missing semicolons and mismatched types. In reality it is here to help, looking for bugs before the program is even run for the first time, giving vital hints based on the static typing system that is one of the pillars of C++.
After going through this process, we finally manage to build the project! So nothing can go wrong now, let’s run it:
Assertion failed: (m_row<m_xpr.rows() && “Too many rows passed to comma initializer (operator<<)”), function operator,, file /Users/wolfgang/CarND/extended-kalman-filter/src/Eigen/src/Core/CommaInitializer.h, line 66.
What is an assertion?
Assertions are run-time checks that are a key instrument in contract-based programming. In short, when defining a function, we not only define the types of the arguments, but we also specify the valid value ranges of its arguments. Take this small example:
double CalcSquareRoot(double x)
assert(x >= 0);
int main(int argc, char* argv)
We implement a small function that computes the square root of its argument, which is defined as
double . But we need to add a “contract”: We are only allowed to compute the square root of non-negative numbers! Both the implementation and the client are aware of this fact, of course, but things can go wrong in the messy reality of the world.
We know that there is a bug in the client code if it calls our little function with a negative argument and we want to find and fix bugs like these as soon as possible. So we do not obfuscate it by “fixing” it with an
if statement and we also do not throw an exception (although that might be an option in some circumstances), we want the code to fail hard and as soon as possible, and tell us what is wrong (at least during development and beta-testing!). And for this we add the intend of the contract in form of and
assert statement. When running this little program, we get the following output:
Assertion failed: (x >= 0), function CalcSquareRoot, file example.cpp, line 6.
fish: ‘./example’ terminated by signal SIGABRT (Abort)
Great, it tells us exactly what is wrong in our code! It says that in line 6 of our
example.cpp there was a failing assertion
(x >= 0). But wait, just like in the case of the Eigen assertion above, we still don’t know where the bug in the client code is! What to do?
Debugger to the Rescue!
We are actually not really interested in the line of code where the assertion failed. What we really need is the line of code in our client code that caused this assertion to fail. If we knew that, we could probably find our what went wrong in the first place. For this we need the debugger. As an excercise, we will add the symbolic breakpoint
abort which will be called when an assertion fails. This is not strictly necessary, but will illustrate how we can work in the debugger. To add this breakpoint, we will do the following:
After adding the symbolic breakpoint, we can now start debugging our program. So we select
Run -> Debug, and this time the debugger stops at the
In the lower left we see the call stack, which shows us the stack of subroutines that are currently active in our program at this point in its execution. At the bottom of this stack we see our most parent function, which is, unsurprisingly, the
main function of our program. One step up we see that we are currently in the constructor of the
FusionEKF class that has in turn just called
Eigen::CommaInitializer. At the top we see some assertion call
__assert_rtn, and the now familiar
abort, where the debugger actually stopped because of our breakpoint. When we now select the top-most line of the call stack that still belongs to our own code we get:
And now, the line that actually caused the assertion is highlighted in the code editor! Ah, I see, there is one excess element in the initialization of the matrix
Knowing how to work with the debugger is an essential skill for any engineer working with code. Of course, finding the bug will often be much, much harder than in this example, but the basic steps are always the same: Set an appropriate breakpoint in your code and examine the call stack to get an idea of what went wrong in your code. Adding assertions to your own code, in order to make your assumptions explicit, are a great tool to facilitate the debugging process. And keep in mind: We all make mistakes all of the time, so our lives will be easier when we spend less time looking for them.