| Debugging C and C++ code in a Unix environment | ||
|---|---|---|
| Prev | Chapter 3. Aspects of debugging C and C++ code | Next |
Experience with bugs shows that they are seldom unique. In deciding how to tackle a particular bug, it is often helpful to attempt classify it.
We will give a very coarse-grained classification; you are encouraged to modify and refine it according to your own insights. We list the categories in order of increasing difficulty (which, luckily, is also in order of diminishing frequency).
Syntactical errors. These are errors that your compiler should catch. Note the `should': compilers are complex pieces of software, that can be buggy themselves. For example a missing ';' or a missing '}'. Often the place where the compiler remarks the bug is (far) after the place where the bug really is.
Build errors. Some errors can result from linking object files from before and after a change together. Make sure you use a Makefile, and that it accurately reflects the dependencies involved in building your project. See the section called An example makefile in Appendix A for a way to track dependencies automatically.
Basic semantic bugs, such as using uninitialised variables, dead code [1] and certain type problems. A compiler can often bring these to your attention, but it must be told to do so explicitly (e.g. through warning and optimisation flags [2] ; see the section called Using the compiler's features).
Semantic bugs, such as using the wrong variable or using `&' '&&'. No compiler or other tool can find these. You'll have to do some thinking here. Testing your program step by step using a debugging tool can help you here.
Note that there are many ways of classification, most of which are orthogonal to each other. For example, hackers tend to distinguish between Bohr bugs and Heisenbugs ([JARGON]). Bohr bugs are `reliable' bugs: given a particular input, they will always manifest themselves. Heisenbugs are bugs that are difficult to reproduce reliably; they appear to depend on the phase of the moon (environmental factors like time, particular memory allocation etc.). A Heisenbug is very often the result of errors in pointers: using memory that is not allocated. So use tools (Electric Fence, see the section called Memory allocation debugging tools) to check all pointers and array boundaries. (Another cause is the use of uninitialised variables).
| [1] | Dead code is code that will never be used. Often it is a bug, sometimes it can be an example of defensive programming: `if this part of the code is reached then something is terribly wrong, thus give a warning on the screen and stop this program'. Also automatically generated code often contains dead code. The GNU CC does not give warnings for unreachable code. However, it will be removed during optimisation. |
| [2] | To optimise, a compiler has to do some quite sophisticated analysis, including data-flow analysis, which can detect dead code. |