Compile-time bugs are easy to detect (the program does not compile) but it may be hard to pinpoint the line of code where the bug actually is: although compile-time bugs invalidate the language syntax, they may not invalidate the syntax at the line where they appear. For example, inserting a spurious open brace `{' on a line by itself is often perfectly legal. It is only at the end of the file that the compiler detects a mismatched number of brackets, and may not be able to trace the even to the spurious open brace. Compiler error messages often refer to the implications of the bug rather than the bug itself, so may be next to useless (or rather, they must be interpreted in the light of experience).
The run-time bugs that are hardest to trace are those that manifest themselves rarely and in nondeterministic ways. Since the computer is a deterministic machine, this sentence needs an explanation. One source of nondeterminism, for example, is given by the values stored in unused memory. Since memory is never implicitly initialized, these may be values left over from a terminated process, or simply random values from the computer power-on procedure. Since C++ pointers make it possible to read the whole computer memory, an erroneous use of pointers may yield random values nondeterministically. Nondeterministic runtime bugs are hard to trace because it is often impossible to recreate the conditions by which they occur.
I personally had two nightmarish nondeterministic runtime bug
experiences, and both took me months to eradicate. The first one had
something to do with the C++ Standard Template Library (STL) sort() algorithm with a user-defined ``less than'' relation. I had
erroneously defined my less than as a
relation instead of a
strict ordering
relation. Depending on the contents and memory
position of the array, then, in a completely irreproducible fashion,
sorting succeeded or terminated in a SIGSEGV abort. In the second one
I was iterating over an STL vector<int> v as follows:
for(vector<int>::iterator vi = v.begin(); vi < v.end();
vi++) {...}, mimicking the usual integer iteration
for(int i = 0; i < n; i++) { ...}. STL iterators,
however, are more like pointers than like integers, and STL vectors
are not necessarily organized linearly in memory: which means that
although logically the array element corresponding to the iterator
vi may be before the end of the array (signalled by v.end()), the actual memory address contained in vi might be
after it in the physical memory organization. Consequently,
depending on the values contained in v, the iterator loop
sometimes aborted before reaching the end of the vector. Save
yourselves a lot of trouble by employing the correct syntax with a
``different'' (!=) instead of a ``less than'' (<)
operator: for(vector<int>::iterator vi = v.begin(); vi
!= v.end(); ++vi) {...}.
As a last piece of advice when confronted with a bug that just wouldn't go away: if you spent weeks looking for a runtime bug without finding it, it probably means it's so simple it has escaped your attention. Question your simplest statements.
Leo Liberti 2008-01-12