Bugs

When programming, it is virtually impossible not to make mistakes. Programming errors are called bugs. The etymology of the term ``bug'' is discussed in http://en.wikipedia.org/wiki/Computer_bug. Some of the bugs concern the syntax, and can be caught by the compiler during compilation (i.e. at compile-time). Others change the semantics of the program but do not invalidate the syntax. These bugs only manifest themselves during execution (i.e. at run-time) and are the hardest to trace.

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 $ \le$ 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