Most programmers don't know what to do when they encounter a bug in their program. I believe that the cause for this is the fact that most programmers don't know how a computer works. Face the facts people, if you think that you're a programmer (*) then you should know how your computer works - being a programmer is about making a computer work! If you don't know the basic concepts behind the operation of your machine then you've got little hope of ever writing good code.

(*) Some people who are employed to write computer programs say that they are not programmers. I can't understand this attitude, if you deliver food to someone's table then you're a waiter, if you answer phones then you're a receptionist, if you write computer programs (or parts therof) then you're a programmer. If you think otherwise then you're probably trying to delude yourself because you think of programmers as "geeks" - see a therapist.

Here's a list of the basics that I think you should know to be a competant C programmer:
  1. The basic concepts of assembly programming - you don't have to be able to write assembler code, just understand it when you see it.
  2. How an internal Harvard architecture CPU works, how the stack operates, and what registers do and how they are used.
  3. How a computer motherboard works. What transistors, VLSI chips, and buses do. The relative speeds of the different types of storage (registers, cache, and RAM).
  4. How a computer system works, how all the basic bits of hardware work and talk to each other. The programming interfaces for all of them. You don't need to know the details, just the concepts involved - I couldn't write a device driver for any piece of hardware, but I know how they all work.
  5. How your OS works. If you don't know how the OS works then you will never have a clue about how to get it to efficiently do what you want. Knowing how other OSs than the one you're programming work is always handy, but not absolutely required.
  6. How your compiler works. You should know about the different parts of a C compiler (the pre-processor, parser, assembler, and the linker) and what they do.

Some quick tips on really debugging a program (as opposed to making it limp through a demo only to crash in production).

  1. Compile and test the code in both debugging and non-debugging modes of the compiler. Most compilers will shift memory around in strange ways in debugging mode to try and shake out bugs. When you do an optimising compile all the objects are crammed together in memory so a single byte memory over-run will generally cause something bad to happen. This means that if you want a reliable program then it may be a good idea to test in both methods. Of course a well written program won't be so terribly buggy. But in any moderately sized program you'll have at least 3 programmers, and at least 2 of them probably won't be able to write such great code. ;-) Alternatively you may find that when writing a small program on your own you may be a bit slack when designing and coding and need to do extra debugging to compensate. Also note that some compilers such as Microsoft Visual C++ don't find errors such as uninitialised data when compiling in debug mode! This is not a bug in VC as such, just an issue you have to work with when trying to fix code on that platform.

    Some programmers will go through a program and test each module in both debug and optimised builds and use the version that works. This is one of the dumbest things you can do as a programmer. If you do this you shouldn't expect any large program that you write to work, and you shouldn't expect job security either! If you are a professional programmer then you will want to have all the code you write work in both debug and release builds. That is because you will want to fix every bug you find.

  2. Your code should ideally compile with no warnings. If it does give a warning that you can't prevent then you should either use a #pragma to disable the warning for that section of code or comment the code as to why you had to do this. You might think that warnings are a minor thing, but at least 10% of all warnings are serious issues that may be bugs in your code. Also some warnings may be latent bugs, IE they won't actually be bugs at the time you write the code, but if you change some of the context it may do something you don't expect or desire.

    Remember - the person who wrote the compiler is probably a better programmer than you ever dream of becomming. They put the warnings in for good reasons, although you may not understand them!

  3. Memory analysis checking is always a good idea. If your compiler supports this (AFAIK they all do now) then you should turn it on for debugging builds. Also be very wary of doing things such as using your own macros to wrap the new/delete operators in C++ or the malloc()/free() functions in C, such macros can prevent memory analysis with compilers such as IBM's VisualAge C++.
  4. Enable profiling for debugging builds as a matter of course. Profiling allows a record to be kept of the order that functions are called which allows you to trace through the program's execution in the way you might do when running in a debugger. However any timing dependant program (IE anything that runs serial ports, network communications, or multiple threads) won't work the same way when being single-stepped in the debugger. Such programs can be run under the profiler and crash for you giving you a good trace of what went wrong (as opposed to running without crashing under the debugger which gives you little chance to discover the problem).
Copyright © 1998 Russell Coker, may be distributed freely.