Koenig’s first rule of debugging
In his C++ blog over at DDJ Andrew Koenig presents his first rule of debugging, which is to make sure the program you’re trying to debug is the program you’re actually running. He explains:
For example, before you ran your program, did you compile it? Are you sure? Are you sure that you compiled every piece of source code that you’re using? With the same compiler? Are you sure that you saved the code before you compiled it.
I’ve lost track of how many times I’ve had a program crash on me, then removed all object and executable files, then recompiled everything from scratch, and had it work perfectly.
Notice that Koenig doesn’t suggest you save all changes and rebuild before running: a decent IDE makes it hard to work in any other way. No, if you want to be safe go in and remove all object and executable files — actually, remove all derived files, since maybe your build generates intermediate files other than just objects and executables.
This suggests to me that there’s something wrong with the build itself. Dependencies aren’t working properly. I’ve certainly lost count of the times that happens. Isn’t the Rebuild All button on Visual Studio unnecessary? Shouldn’t a simple Build always get the job done? How many times has make clean failed to clean up? Just how long will it take to rebuild all that heavily templated C++ code?
C++ programmers, share your pain!
If a C++ expert like Andrew Koenig has trouble with build dependencies, what hope for the rest of us?
Makefiles are a proven and stable technology which, at heart, are based on a simple idea. I’ve been using them for a long time. I still can’t get them right, and judging by the number of variant build systems, I’m not the only one. What’s worse, in a multi-platform environment, you’ll need to tweak them for each platform, maintain them in parallel with project files and solutions. I’ve known people who can get this right, but it takes considerable effort. Most programmers, like me, want to write and run code, and to minimise the distance between the two activities. I don’t like sorting out the build.
Suppose the problem you’re debugging manifests itself in a release build — according to Koenig’s rule, it’s the release build you must debug. Release builds, though, aren’t very tractable. Assertions are gone, memory access checks suppressed, debugging symbols absent. I’ve pointed out the madness of developing two versions of the code before. It’s all too common to use one build for development and then ship something completely different.
In Overload 80, Roger Orr confronts this problem in his fine article “Release Mode Debugging”. He states his preference for a single build and then goes on to provide specific advice on compiler options to achieve a build which suits both developers and customers. He also notes that only a small percentage of any code base is performance critical, and only compiler optimisations should be selectively applied to these hot spots. I agree with most of what Orr says but I’m not sure about adding this subtle mix of compiler options to the build system: my preference is to code everything in a high-level language like Python, then rewrite the hot spots in C++, optimised as necessary.
On the subject of rules and laws, have a look at Proebsting’s Law, which asserts:
… while hardware computing horsepower increases at roughly 60%/year, compiler optimizations contribute only 4%.
I can honestly say I’ve never once needed to apply Koenig’s first rule of program debugging when working with Python, or any other high level language. It just isn’t an issue.
C is a great language for certain domains but its compile and build model is a weak spot. C++ surpasses C for most things but its compile and build model is worse.
/usr/include/c++/4.0.0/bits/stl_algo.h: In function '_OutputIterator std::set_union(_InputIterator1, _InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator) [with _InputIterator1 = std::_Rb_tree_const_iterator<ip_address>, _InputIterator2 = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]': ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_algo.h:4107: error: no match for 'operator*' in '*__result' ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_algo.h:4112: error: no match for 'operator*' in '*__result' ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_algo.h:4117: error: no match for 'operator*' in '*__result' /usr/include/c++/4.0.0/bits/stl_algo.h:4121: error: no match for 'operator++' in '++__result' /usr/include/c++/4.0.0/bits/stl_iterator_base_types.h: At global scope: /usr/include/c++/4.0.0/bits/stl_iterator_base_types.h: In instantiation of 'std::iterator_traits<std::vector<ip_address, std::allocator<ip_address> > >': /usr/include/c++/4.0.0/bits/stl_algobase.h:310: instantiated from '_OI std::__copy_aux(_II, _II, _OI) [with _II = std::_Rb_tree_const_iterator<ip_address>, _OI = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algobase.h:326: instantiated from 'static _OI std::__copy_normal<<anonymous>, <anonymous> >::copy_n(_II, _II, _OI) [with _II = std::_Rb_tree_const_iterator<ip_address>, _OI = std::vector<ip_address, std::allocator<ip_address> >, bool <anonymous> = false, bool <anonymous> = false]' /usr/include/c++/4.0.0/bits/stl_algobase.h:387: instantiated from '_OutputIterator std::copy(_InputIterator, _InputIterator, _OutputIterator) [with _InputIterator = std::_Rb_tree_const_iterator<ip_address>, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algo.h:4124: instantiated from '_OutputIterator std::set_union(_InputIterator1, _InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator) [with _InputIterator1 = std::_Rb_tree_const_iterator<ip_address>, _InputIterator2 = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_iterator_base_types.h:129: error: no type named 'iterator_category' in 'class std::vector<ip_address, std::allocator<ip_address> >' /usr/include/c++/4.0.0/bits/stl_algobase.h: In static member function 'static _OI std::__copy<<anonymous>, <template-parameter-1-2> >::copy(_II, _II, _OI) [with _II = std::_Rb_tree_const_iterator<ip_address>, _OI = std::vector<ip_address, std::allocator<ip_address> >, bool <anonymous> = false, <template-parameter-1-2> = std::bidirectional_iterator_tag]': /usr/include/c++/4.0.0/bits/stl_algobase.h:317: instantiated from '_OI std::__copy_aux(_II, _II, _OI) [with _II = std::_Rb_tree_const_iterator<ip_address>, _OI = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algobase.h:326: instantiated from 'static _OI std::__copy_normal<<anonymous>, <anonymous> >::copy_n(_II, _II, _OI) [with _II = std::_Rb_tree_const_iterator<ip_address>, _OI = std::vector<ip_address, std::allocator<ip_address> >, bool <anonymous> = false, bool <anonymous> = false]' /usr/include/c++/4.0.0/bits/stl_algobase.h:387: instantiated from '_OutputIterator std::copy(_InputIterator, _InputIterator, _OutputIterator) [with _InputIterator = std::_Rb_tree_const_iterator<ip_address>, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algo.h:4124: instantiated from '_OutputIterator std::set_union(_InputIterator1, _InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator) [with _InputIterator1 = std::_Rb_tree_const_iterator<ip_address>, _InputIterator2 = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_algobase.h:269: error: no match for 'operator++' in '++__result' /usr/include/c++/4.0.0/bits/stl_algobase.h:270: error: no match for 'operator*' in '*__result' /usr/include/c++/4.0.0/bits/stl_algobase.h: In static member function 'static _OI std::__copy<_BoolType, std::random_access_iterator_tag>::copy(_II, _II, _OI) [with _II = ip_address*, _OI = std::vector<ip_address, std::allocator<ip_address> >, bool _BoolType = false]': /usr/include/c++/4.0.0/bits/stl_algobase.h:317: instantiated from '_OI std::__copy_aux(_II, _II, _OI) [with _II = ip_address*, _OI = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algobase.h:335: instantiated from 'static _OI std::__copy_normal<true, false>::copy_n(_II, _II, _OI) [with _II = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OI = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algobase.h:387: instantiated from '_OutputIterator std::copy(_InputIterator, _InputIterator, _OutputIterator) [with _InputIterator = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' /usr/include/c++/4.0.0/bits/stl_algo.h:4124: instantiated from '_OutputIterator std::set_union(_InputIterator1, _InputIterator1, _InputIterator2, _InputIterator2, _OutputIterator) [with _InputIterator1 = std::_Rb_tree_const_iterator<ip_address>, _InputIterator2 = __gnu_cxx::__normal_iterator<ip_address*, std::vector<ip_address, std::allocator<ip_address> > >, _OutputIterator = std::vector<ip_address, std::allocator<ip_address> >]' ip_sets.cpp:106: instantiated from here /usr/include/c++/4.0.0/bits/stl_algobase.h:285: error: no match for 'operator*' in '*__result' /usr/include/c++/4.0.0/bits/stl_algobase.h:287: error: no match for 'operator++' in '++__result'
Feedback
-
"C++ programmers, share your pain!"
Build systems are a thorn in my side too. So much so, that I decided to create my own. It's not as "feature complete" as some others, but it complete enough to remain simple and yet allow me to build uniformly and accurately using a variety of compilers on a bunch of different operating systems.
I would like to start from scratch and create a more complete system at some point. But if you're interested, take a look at slam:
http://www.mr-edd.co.uk/?page_id=4