VM builds still buggy
Jaco van der Merwe
jaco.lists at telkomsa.net
Sun Jul 25 13:52:55 PDT 2004
Hi Lee
I would like to offer a few suggestions that may help to catch these elusive
bugs.
One of the most useful techniques that we use is to write self-checking
code. Such code does all kinds of sanity checks. My rule has always been to
fail as quickly as possible if something goes wrong, and this technique is
very good at doing that. The easiest way to accomplish this is to make
liberal use of ASSERT-style macros to check pre-conditions, invariants,
post-conditions and other sanity checks wherever possible. Yes, it carries a
performance penalty, but it can be compiled out in a release build. The
standard ASSERT macro that is supplied with Visual C has a very nice
feature. In a debug build a failed assertion launches directly into the
debugger at the point where the assertion failed. From that point onwards it
is usually very easy to find the cause. Typically we define our own
assertion macros that use the Visual C macros when targeted for the Windows
platform.
After all that arm waving, here are some specific suggestions:
- I notice that many data structures, for example, the global CurrentMemory
variable which is an instance of the ObjectHeap struct, are not explicitly
initialised. At first glance it seems that a lazy style of initialization is
used, especially for the tables/arrays contained in the structs. I would
suggest initialising all structs, and especially tables/array before usage.
Don't just initialise everything to zero, but rather use initial values that
would cause an immediate failure if used incorrectly, e.g. an array element
one beyond the current end of the array. If an immediate failure cannot be
induced, then at least use a value that would cause a failure as soon as
possible afterwards.
- The current style of the code is not very amenable to self-checking. Why?
Because all the structs and array elements are accessed directly. I would
strongly suggest to hide these element accessors behind functions, that is,
to use encapsulation as far as possible. This will result in a very
object-oriented style of C coding where all operations on a struct are
performed via functions that take the struct pointer as its first argument.
The same should apply to tables/arrays. These functions can all be declared
as inline in order to avoid any performance penalties. However, the major
benefit of this approach is that all the "methods" that are performing
operations on their associated structs can do as many sanity checks as
possible. For example, all array indexing can check for array bounds, or
struct accessors can check for usage of "uninitialised" values, etc.
- Another useful technique that can be used for more complicated data
structures is to run an integrity check whenever required. For example, in a
heap memory structure the integrity of the heap can be checked whenever new
memory is allocated or freed. This code can also be compiled out in a
release build. Whenever the heap becomes corrupted it will become visible
very quickly. These system integrity checks can even be triggered externally
if required.
I believe that this style of coding will catch many of the errors that may
still be lurking in the code. It's not a silver bullet and it won't catch
all errors, but it usually catches a large percentage of them. This approach
requires some effort, but the return on investment is big and I would
strongly suggest using it if the goal is production-quality and reliable
code.
I hope this helps.
Regards
Jaco van der Merwe
More information about the Slate
mailing list