0% found this document useful (0 votes)
27 views

Writing Bug-Free C Code

Uploaded by

tehkensei
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
27 views

Writing Bug-Free C Code

Uploaded by

tehkensei
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 134

<b>Limiting text size in a Microsoft Windows edit control</b>

SendDlgItemMessage( hDlg, nID, EM_LIMITTEXT, wSize, 0L );

<b>EmLimitText function</b> void APIENTRY EmLimitText( HWND


hDlg, int nID, WORD wSize ) {

SendDlgItemMessage( hDlg, nID, EM_LIMITTEXT, wSize, 0L );

} /* EmLimitText */

<b>Template interface for a new module</b> NEWHANDLE(HOBJ);

#ifdef USE_HOBJ

/*--------------------------------------------------------------

* Short one line description of module *

*-------------------------------------------------------------*/

EXTERNC HOBJ APIENTRY ObjCreate ( arguments ); EXTERNC type


APIENTRY ObjMethodName1 ( HOBJ, arguments ); EXTERNC type
APIENTRY ObjMethodName2 ( HOBJ, arguments ); ...

EXTERNC type APIENTRY ObjMethodNameN ( HOBJ, arguments );


EXTERNC HOBJ APIENTRY ObjDestroy ( HOBJ ); #endif

<b>The copyright header</b>


/************************************************************
****/
/* */

/* (project name) */

/* */

/* Copyright (date) (Company Name). All rights reserved. */

/* */

/* This program contains the confidential trade secret */

/* information of (Company Name). Use, disclosure, or */

/* copying without written consent is strictly prohibited. */

/* */

/************************************************************
****/

<b>The module comment header</b> /*pm---------------------------------------


-----------------------

* OUTLINE:

* (Describes the purpose of the module) *

* IMPLEMENTATION:

* (Describes in high level terms how the module works) *

* NOTES:
*

* (Enumerate noteworthy items) *

*--------------------------------------------------------------*/

<b>Include section example</b> #define USE_HRAND

#define USE_HDOSFH

#include "app.h"
USEWINASSERT
<b>Random number generator class declaration</b> CLASS(hRand,
HRAND) {

long lRand; };

<b>The function comment header template</b> /*pf----------------------------


----------------------------------

* DESCRIPTION: (A few word description) initials *

* (A long description of the function) *

* ARGUMENTS:

* Arg1 - Arg1 description

* ...

* ArgN - ArgN description

* RETURNS:

* (A description of the return value) *

* NOTES:

*
* (optional notes section)

*--------------------------------------------------------------*/

<b>Template for entry point function</b> return-type APIENTRY


FunctionName( type arg1, ..., type argN ) {

(function body, usually in fault-tolerant form)

} /* FunctionName */

<b>The APIENTRY define</b> #define APIENTRY FAR PASCAL

<b>The APIENTRY define, specific to Microsoft C8 Windows DLL


programming</b> #define APIENTRY EXPORT FAR PASCAL

<b>Template for a LOCAL function</b> return-type LOCAL


FunctionName( type arg1, ..., type argN ) {

(function body, usually in fault-tolerant form)

} /* FunctionName */

<b>The LOCAL and LOCALASM define</b> #define LOCAL static


NEAR FASTCALL

#define LOCALASM static NEAR PASCAL

<b>HDOSFH include file section</b> NEWHANDLE(HDOSFH);

#ifdef USE_LOWIO
/*--------------------------------------------------------------

* Access to low-level I/O run-time library functions *

*-------------------------------------------------------------*/

#include <fcntl.h>

#include <sys\types.h>

#include <sys\stat.h>

#include <io.h>

#endif

#ifdef USE_HDOSFH

/*--------------------------------------------------------------

* Code wrapper to low-level file I/O

*-------------------------------------------------------------*/

EXTERNC HDOSFH APIENTRY DosOpenFile ( LPSTR ); EXTERNC


WORD APIENTRY DosRead ( HDOSFH, LPVOID, WORD ); EXTERNC
WORD APIENTRY DosWrite ( HDOSFH, LPVOID, WORD );
EXTERNC HDOSFH APIENTRY DosCloseFile ( HDOSFH ); #endif

<b>HDOSFH module</b>
/************************************************************
****/

/* */

/* (project name) */

/* */

/* Copyright (date) (Company Name). All rights reserved. */

/* */

/* This program contains the confidential trade secret */

/* information of (Company Name). Use, disclosure, or */

/* copying without written consent is strictly prohibited. */

/* */

/************************************************************
****/

/*pm--------------------------------------------------------------

* OUTLINE:

* This module provides access to the low-level file I/O


* functions of the standard Microsoft C run-time library.

* IMPLEMENTATION:

* This module is simply a code wrapper module.

* NOTES:

*--------------------------------------------------------------*/

#define USE_LOWIO

#define USE_HDOSFH

#include "app.h"
USEWINASSERT

/*--- The class object ---*/

CLASS(hDosFh, HDOSFH) {

int fh; };

/*pf--------------------------------------------------------------

* DESCRIPTION: (Open File) JLJ

* Attempt to open a file

* ARGUMENTS:

* lpFilename - The name of the file to open *

* RETURNS:

* A file object handle or NULL if there was some error * in opening the
specified file.

*
*--------------------------------------------------------------*/

HDOSFH APIENTRY DosOpenFile( LPSTR lpFilename ) {

HDOSFH hDosFh=NULL; int fh=open(lpFilename,


_O_RDWR|_O_BINARY); if (fh!=-1) {

NEWOBJ(hDosFh); hDosFh->fh = fh; }

return (hDosFh);

} /* DosOpenFile */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Close File) JLJ

* Close a previously opened file *

* ARGUMENTS:

* hDosFh - The file object or NULL

* RETURNS:

* NULL
*

*--------------------------------------------------------------*/

HDOSFH APIENTRY DosCloseFile( HDOSFH hDosFh ) {

VERIFYZ(hDosFh) {

int nResult=close(hDosFh->fh); WinAssert(!nResult); FREE(hDosFh); }

return (NULL);

} /* DosCloseFile */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Read File) JLJ

* Attempt to read a block of information from a file.

* ARGUMENTS:

* hDosFh - The file object

* lpMem - Pointer to memory buffer * wCount - Number of bytes to read


into the memory buffer *

* RETURNS:
*

* The number of bytes that were actually read *

*--------------------------------------------------------------*/

WORD APIENTRY DosRead( HDOSFH hDosFh, LPVOID lpMem,


WORD wCount ) {

WORD wNumRead=0; VERIFY(hDosFh) {

wNumRead = (WORD)read(hDosFh->fh, lpMem, wCount); }

return (wNumRead);

} /* DosRead */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Write File) JLJ

* Attempt to write a block of information to a file.

* ARGUMENTS:

* hDosFh - The file object


* lpMem - Pointer to memory buffer * wCount - Number of bytes to write
to the file *

* RETURNS:

* The number of bytes that were actually written *

*--------------------------------------------------------------*/

WORD APIENTRY DosWrite( HDOSFH hDosFh, LPVOID lpMem,


WORD wCount ) {

WORD wNumWritten=0; VERIFY(hDosFh) {

wNumWritten = (WORD)write(hDosFh->fh, lpMem, wCount); }

return (wNumWritten);

} /* DosWrite */

6.7.3 Commentary

Use this module as a template on how to write modules. It is bare-bones and


targeted to MS-DOS using Microsoft C8, but you should be able to adapt it
easily to other environments. I would like to emphasize some parts of this
module.

Completeness. This module is not complete. There are other low-level I/O
functions that should be implemented.

The includes. This module is intended to be a code wrapper that totally


replaces the run-time library low-level I/O. Therefore, this module should
be the only module that needs to do a #define USE_LOWIO. Notice that
the #includes for USE_LOWIO are not done in the source file but are
instead done in the global include file.

Accessing low-level I/O. Whenever any source file wants to access the low-
level I/O, it should now use the code wrapper code and do a #define
USE_HDOSFH.

DosCloseFile uses VERIFYZ. It is important that only method functions


that destroy an object allow NULL to be passed in as an argument. You do
not want to trigger a run-time object verification failure. VERIFYZ
performs this task.

Fault-tolerant methods. Whenever possible, the fault-tolerant form of


VERIFY should be used. For functions returning a value, a reasonable
failure return value is in place before the run-time verification takes place.
This way, even if a bad object handle is unknowingly passed in, the calling
code will react to the failure value.

6.8 Chapter Summary

The key to successfully coding a hierarchy of modules is that you must


always code what to do, not how to do it.
In this way, you avoid spreading knowledge about how to do
something and instead put this knowledge in a function in one place.
You are now free to call the function as many times as you want in as
many places as you want. Changing the implementation down the road
is a lot easier since the implementation is now isolated in one function.
An important benefit of this technique is that it allows for the rapid
prototyping of changes to an implementation, because changing the
implementation changes only one source file as opposed to many.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
Writing Bug-Free C Code A Programming
Style That Automatically Detects Bugs in C Code
by Jerry Jongerius / January 1995

0. Preface C Programming with:


1. Understand Why Bugs Exist
2. Know Your Environment Class Methodology §4
3. Rock-Solid Base Data hiding §4.2
4. The Class Methodology Runtime type checking §4.4
5. A New Heap Manager Compile time type checking §4.3
6. Designing Modules Fault tolerant asserts §3.3
7. General Tips Fault tolerant functions §4.6
8. Style Guide Compile-time asserts §2.1.4
9. Conclusion Symbolic heap walking §5.2.9
A. Appendix: Code Listings Heap leak detection §5.5
References

Book C Source Code (9k): source.zip (for DOS, Windows, UNIX, etc)

Sections of this book that talk about

Microsoft or Windows are generally marked with the


'Windows' graphic.

You can safely skip those sections of this book if you


want to.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius, [email protected]


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
Check Amazon for paperback availability
<b>Linked list of nodes</b> typedef struct tagNODE {

struct tagNODE *pNext; ...

} NODE, *PNODE;

<b>Circular reference problem, first cut</b> typedef struct tagNODEA {

struct tagNODEB *pNodeB; ...

} NODEA, *PNODEA;

typedef struct tagNODEB {

PNODEA pNodeA; ....

} NODEB, *PNODEB;

<b>Circular reference problem, second cut</b> typedef struct tagNODEA


*PNODEA;

typedef struct tagNODEB *PNODEB;

typedef struct tagNODEA {

PNODEB pNodeB; ...

} NODEA;

typedef struct tagNODEB {

PNODEA pNodeA; ....

} NODEB;
<b>The NEWHANDLE() macro</b> #define NEWHANDLE(Handle)
typedef struct tag##Handle *Handle

<b>HRAND handle declaration</b> NEWHANDLE(HRAND);

<br/>

<b>NEWHANDLE(HRAND) macro expansion</b> typedef struct


tagHRAND *HRAND;

<b>Random number generator interface</b> NEWHANDLE(HRAND);

EXTERNC HRAND APIENTRY RandCreate ( int ); EXTERNC HRAND


APIENTRY RandDestroy ( HRAND ); EXTERNC int APIENTRY
RandNext ( HRAND );

<b>Function that uses a random number generator object</b> void Testing(


void )

HRAND hRand=RandCreate(0); LOOP(100) {

printf( "Number %d is %d\n", loop, RandNext(hRand) ); } ENDLOOP

hRand = RandDestroy( hRand );

} /* Testing */

<b>Random number generator implementation, first cut</b> TYPEDEF


struct tagHRAND {

long lRand; };
HRAND RandCreate( int nSeed )

HRAND hRand; (allocate memory); hRand->lRand = nSeed; return


(hRand);

} /* RandCreate */

HRAND RandDestroy( HRAND hRand )

(free hRand memory) return (NULL);

} /* RandDestroy */

int RandNext( HRAND hRand )

hRand->lRand = NEXTRAND(hRand->lRand);
return(FINALRAND(hRand->lRand));

} /* RandNext */

<b>Using type information, a bad implementation</b> static int


nTypeOfHRAND=(hard-coded number); .

.
HRAND RandCreate( int nSeed )

HRAND hRand=(allocate memory using nTypeOfHRAND); hRand-


>lRand = nSeed; return (hRand); }

<b>A class descriptor</b> typedef struct {

LPSTR lpVarName; } CLASSDESC, FAR*LPCLASSDESC;

<b>The _CD() macro, for use only in other macros</b> #define _CD(hObj)
hObj##_ClassDesc

<b>Using _CD() for the hRand object</b> _CD(hRand)

<br/>

<b>_CD(hRand) macro expansion</b> hRand_ClassDesc

<b>HRAND class descriptor</b> static CLASSDESC _CD(hRand)=


{"hRand"};

<b>The CLASS() macro</b> #define CLASS(hObj,Handle) \

static CLASSDESC _CD(hObj)={#hObj}; TYPEDEF struct tag##Handle


<br/>

<b>HRAND using CLASS() macro</b> CLASS(hRand, HRAND) {

long lRand; };

<b>Ideal VERIFY() macro syntax</b> VERIFY(hObject);

<i>or</i>

VERIFY(hObject) {

(block of code) }
<br/>

<b>VERIFY() and VERIFYZ() macros</b> #define VERIFY(hObj)


WinAssert(_VERIFY(hObj)) #define VERIFYZ(hObj) if (!(hObj)) {} else
VERIFY(hObj)

<b>_VERIFY() macro</b> #define _S4 (sizeof(LPCLASSDESC)) #define


_S8 (sizeof(LPCLASSDESC)+sizeof(LPVOID)) #define _VERIFY(hObj) \

( FmIsPtrOk(hObj) && \ (((LPVOID)hObj)==*(LPVOID FAR*)


((LPSTR)hObj-_S8)) \ && ((&_CD(hObj))==*(LPCLASSDESC FAR*)
((LPSTR)hObj-_S4)) )

<b>My _VERIFY() macro</b> #define _VERIFY(hObj)


Verify_##hObj((long)hObj, (WORD)&_CD(hObj))

<b>The verification code used by my _VERIFY() macro</b> ; DX:AX =


far pointer to verify

; BX = offset to object class descriptor ;

; WARNING: This code assumes the register calling convention ; used by


Microsoft C8. It may change in future compiler ; versions.

xchg ax, bx xor cx, cx ;; assume a bad selector lsl cx, dx ;; verify selector,
length cmp bx, cx mov cx, 0 ;; assume false return jae done ;; long pointer
was bad mov es, dx cmp word ptr es:[bx-8], bx ;; test offset jne done

cmp word ptr es:[bx-6], dx ;; test segment jne done

cmp word ptr es:[bx-4], ax ;; test class desc offset jne done

inc cx ;; true return done:

mov ax, cx ret

<b>The heap manager interface</b> EXTERNC LPVOID APIENTRY


FmNew ( SIZET, LPCLASSDESC, LPSTR, int ); EXTERNC LPVOID
APIENTRY FmFree ( LPVOID );
<b>NEWOBJ() and FREE() implementation, first cut</b> #define
NEWOBJ(hObj) \

hObj = FmNew(sizeof(*hObj),&_CD(hObj),__FILE__,__LINE__))
#define FREE(hObj) hObj = FmFree(hObj)

<b>_LPV() macro</b>

#define _LPV(hObj) *(LPVOID FAR*)&hObj

<b>NEWOBJ() and FREE() implementation, final form</b> #define


NEWOBJ(hObj) \

(_LPV(hObj)=FmNew(sizeof(*hObj),&_CD(hObj),szSRCFILE,__LINE
__)) #define FREE(hObj) (_LPV(hObj)=FmFree(hObj))

<b>Fault-tolerant method function, no return information</b> void


APIENTRY Method( HOBJECT hObject, (other arguments) ) {

VERIFY(hObject) {

(body of code) }

} /* Method */

<b>Fault-tolerant method function, with return information</b> TYPE


APIENTRY Method( HOBJECT hObject, (other arguments) ) {

TYPE var=(failure value) VERIFY(hObject) {

(body of code) var = (success value) }

return (var)

} /* Method */
<b>Fault-tolerant RandNext() method function</b> int APIENTRY
RandNext( HRAND hRand ) {

int nRand=0; VERIFY(hRand) {

hRand->lRand = NEXTRAND(hRand->lRand); nRand =


(int)FINALRAND(hRand->lRand); }

return(nRand);

} /* RandNext */

<b>Random number generator implementation, final version</b> #include


"app.h"
USEWINASSERT

CLASS(hRand, HRAND) {

long lRand; };

HRAND APIENTRY RandCreate( int nSeed ) {

HRAND hRand; NEWOBJ(hRand); hRand->lRand = nSeed; return


(hRand);

} /* RandCreate */

HRAND APIENTRY RandDestroy( HRAND hRand ) {

VERIFYZ(hRand) {

FREE(hRand); }

return (NULL);

} /* RandDestroy */

int APIENTRY RandNext( HRAND hRand ) {

int nRand=0; VERIFY(hRand) {

hRand->lRand = NEXTRAND(hRand->lRand); nRand =


(int)FINALRAND(hRand->lRand); }
return(nRand);

} /* RandNext */

The CLASS() macro is used in the source file that implements the HRAND
class object, not in an include file.

This random number generator implementation is contained in its own


source file or module separate from all other modules. This ensures that the
HRAND implementation is known only to the functions that implement the
random number generator and is not known to functions that simply use
random numbers.

The interface specification for this random number module is contained in


app.h and is accessed through #include "app.h". The interface specification
contains the NEWHANDLE(HRAND) declaration and prototypes for
RandCreate(), RandDestroy() and RandNext().

USEWINASSERT allows the code to use the WinAssert() macro, which is


used by the VERIFY() and VERIFYZ() macros, and makes the current
filename known through the szSRCFILE variable, which is used by the
NEWOBJ() and WinAssert() macros.

The CLASS() macro allocates and initializes a class descriptor for HRAND
and binds the HRAND handle to an actual data structure. The class
descriptor is used by the NEWOBJ(), VERIFY() and VERIFYZ() macros.
The binding of the HRAND handle to an actual data structure allows us to
implement the random number generator, because indirections on hRand
are now possible.

RandCreate() uses NEWOBJ() to create a new object, initializes it and


returns the handle to the caller.

RandDestroy() performs run-time object verification on the hRand variable


by using VERIFYZ(). If hRand is non-zero and valid, the object is freed by
using FREE(). Finally, NULL is returned because all destroy methods
return NULL by convention.

RandNext() performs run-time object verification on the hRand variable by


using VERIFY(). If hRand is valid, a new random number is generated.
Finally, the next random number (or an error random number of zero) is
returned.

There is a lot going on behind the scenes in this code. The object-oriented
macros are actually hiding a lot of code. It is instructive to see everything
that is going on by running this source through the preprocessor pass of the
compiler and viewing the resulting output.

In Microsoft C8, this is done with the /P command line option and
results in an .i file.

4.8 A Comparison with C++ Data Hiding

C++ does have a lot to offer (inheritance and virtual functions), but one of
the things I do not like about C++ is that it does not allow for the complete
data hiding of class declarations. For example, in order to use a class, you
must have access to the full declaration of the class. To change the
implementation (private part) of a class means that more than likely you
have to recompile all source files that just use the object.

This gets even more complicated when inheritance is used and a class
implementation (private part) is changed because all classes that are derived
from the changed class have definitely changed. This will cause a recompile
of a lot of code when all you did was change the private part of one class.
The bottom line in C++ is that the private parts of classes are not so private!
I do not consider the private part of a C++ class to be complete data hiding.

The data hiding problem is one of the reasons that the class methodology
was developed. The problem with almost every large project is that there is
simply too much information in the form of data (class) declarations. This
results in a project that is hard to work on because of the information
overload. The class methodology allows every data (class) structure to be
completely hidden. This is done by moving data declarations out of include
files and into the modules that implement the objects.

Take, for example, the random number generator just discussed. Users of a
random number generator can see only the random number generator
interface and nothing more. They see the HRAND data type and the
prototypes of the method functions but not the implementation. In fact, the
implementation can change totally and only the one source file that
implements the random number generator needs to be recompiled. This is
because the implementation (class declaration) is declared in only the one
source file that implements the object. This is a powerful concept when
applied to an entire project.

4.8.1 Another View

In §13.2.2 (Abstract Types) of The Design and Evolution of C++,


Stroustrup laments that the data hiding view I have expressed above is a
common view about C++ but that it is wrong. I disagree. Stroustrup goes on
to explain how data hiding can be accomplished in C++ by using abstract
classes.

The solution involves using two classes: a base class and a derived class.
The base class is declared in an include file to be an abstract class. This
declares the interface to the object (not the data) which is visible to all users
of the object. The derived class is declared in the source file that
implements the object. It includes the (private) data and is derived from the
abstract base class. This derived class is the real object, which is invisible to
all users of the object.

However, Stroustrup fails to point out the problems in using abstract types
to perform data hiding in C++.

Problem one: Creating a new instance. Code that creates an instance of the
class must have access to the derived class declaration. Therefore, code that
uses the class cannot use the new operator to create a new instance of the
class because the code has access to only the abstract base class, not the
derived class. One possible solution is to declare a static member function
in the abstract base class that is implemented in the derived class module.
This function can then create a new derived object, returning a pointer to
the base class.

Problem two: All method functions must be virtual. All functions that
interface to the object must be declared as virtual, which adds function call
overhead. Therefore, calling a function declared in the base class is really
calling a derived class function. Consider what would happen if the
functions were not virtual. They could be implemented, but how would the
base class functions access data in the derived class? The problem is that
the this pointer in the base class member functions points to the base class,
not the derived class. You could type cast from the base class to the derived
class, but this is a bad practice and would give you access to only the public
section of the derived class, not the private section. The result is that you
are forced to use virtual functions for all member functions.

Problem three: Two class declarations. Data hiding requires an abstract base
class declaration and a derived class declaration that are very similar, but
not identical. And all this duplication just because we wanted data hiding.

Problem four: Inheritance is disabled. To use inheritance on the class in


which data is hidden, you need access to the derived class. But you have
access to only the abstract base class declared in an include file, not the
derived class declared in the source file that implements the derived class. If
you inherit from the abstract base class, you lose the implementation. If you
inherit from the derived class, you lose the data hiding.

The bottom line is that implementing data hiding in C++ by using abstract
classes disables other advanced features of C++ and adds execution
overhead. To use data hiding, you give up inheritance. To use inheritance,
you give up data hiding. Data hiding through abstract classes and
inheritance do not coexist. This is why I disagree with Stroustrup.

I feel that using abstract classes to perform data hiding in C++ is an


afterthought (abstract classes were not added until C++ version 2.0) and a
weak solution to an underlying C++ problem that complete data hiding is
not built into the language. However, this underlying problem is also a
major strength when it comes to execution speed and standard C structure
layout compatibility.

In §10.1c of The Annotated C++ Reference Manual, Ellis and Stroustrup


hint at a solution (a level of indirection) but dismisses it due to the resulting
code being "both larger and slower." Too bad. Those people that want
complete data hiding now have to implement it manually.

4.9 Chapter Summary

The class methodology solves the information overload problem by


moving data declarations out of include files and into a module, where
the data declaration is turned into a private class object.
There is a fundamental shift away from public access to the data to
private access through calling a method function. This object model
supports an unlimited number of dynamically allocated objects and
object handles that are type checkable by the compiler. A handle is
simply a pointer to the object.
With the class data declaration hidden away in one source file, how
can other source files use handles to this class when the class
declaration is not even visible? How are these handles type checked by
the compiler? The breakthrough in accomplishing this feat is to use an
incomplete type. This declares a pointer to a structure tag and allows
full usage and type checking of the handle/pointer in other source files.
Then, in the implementation module, a data declaration with the same
structure tag is declared. This binds the handle to the data declaration
and allows handles to be dereferenced only in this one module.
To dramatically reduce bugs, an object system must provide a means
of type checking objects at run-time. An object's type identifier is
simply a pointer to a class descriptor structure.
To avoid changing the sizeof() a class declaration by including type
information, the underlying heap manager is improved to support run-
type type checking.
The run-time object verification macro, VERIFY(), supports a fault-
tolerant syntax. It allows a block of code to be executed if and only if
the handle the block of code relies on is valid.
The NEWOBJ() and FREE() macros hide the programmer from how
classes are implemented.
Class objects should be considered state machines. Method functions
then transition the state machine from one valid state to another valid
state.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>An example in using static</b> void Testing( void )

LOOP(3) {

static int nTest1=100; int nTest2=100; printf( "nTest1=%d,


nTest2=%d\n", nTest1, nTest2 ); ++nTest1; ++nTest2; } ENDLOOP

/* Testing */

<br/>

<b>Output from calling Testing() three times</b> nTest1=100, nTest2=100

nTest1=101, nTest2=100

nTest1=102, nTest2=100

nTest1=103, nTest2=100

nTest1=104, nTest2=100

nTest1=105, nTest2=100

nTest1=106, nTest2=100

nTest1=107, nTest2=100

nTest1=108, nTest2=100

<b>Limiting the scope of a variable</b> LOOP(strlen(pString)) {

char c=pString[loop]; ...

} ENDLOOP

<b>NewScope define</b> #define NewScope


<b>Using NewScope</b> void APIENTRY Function( args )

/*--- Comment ---*/

(code block) /*--- Using NewScope ---*/

NewScope {

type var; (code block that uses var) }

} /* Function */

<b>A program with a subtle bug</b> #include <stdlib.h>

#include <stdio.h>

#include <string.h>

char *myitoa( int nNumber )

char buffer[80]; sprintf( buffer, "%d", nNumber ); return (buffer); }

int main(void)

printf( "Number = %s\n", myitoa(234) ); return 0; }

<b>An example of bad type casts</b> MSG msg;


while (GetMessage((LPMSG)&msg, NULL, 0, 0)) {

TranslateMessage((LPMSG)&msg); DispatchMessage((LPMSG)&msg);
}

<b>GetCpuSpeed(), demonstrating implicit type cast problem</b> int


APIENTRY GetCpuSpeed( LPSTR lpBuffer ) {

int nSpeed=(calculation); if (lpBuffer) {

(fill buffer with text description of speed) }

return (nSpeed);

} /* GetCpuSpeed */

<b>Calling GetCpuSpeed()</b> PSTR pBuffer=NULL;

int nSpeed1=GetCpuSpeed(NULL);

int nSpeed2=GetCpuSpeed(pBuffer);

<b>Using sizeof(), a bad example</b> int nVar;

...

DumpHex( &nVar, sizeof(int) );

<b>Using sizeof(), a good example</b> int nVar;

...

DumpHex( &nVar, sizeof(nVar) );

<b>Deeply nested code</b> void DeepNestFunction( void )

if (test1) {
(more code) if (test2) {

(more code) if (test3) {

(more code) if (test4) {

(more code) }

} /* DeepNestFunction */

<b>Unrolling the deep nesting</b> void UnrollingDeepNesting( void ) {

BOOL bVar=(test1); if (bVar) {

(more code) bVar = (test2); }

if (bVar) {

(more code) bVar = (test3); }

...

} /* UnrollingDeepNesting */

<b>GetTextOfMonth() function, no error checking</b> LPSTR


APIENTRY GetTextOfMonth( int nMonth ) {

CSCHAR TextOfMonths[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"

};

return (TextOfMonths[nMonth]);

} /* GetTextOfMonth */

<b>GetTextOfMonth() function, with error checking</b> LPSTR


APIENTRY GetTextOfMonth( int nMonth ) {

CSCHAR szBADMONTH[]="???"; LPSTR lpMonth=szBADMONTH;


WinAssert((nMonth>=0) && (nMonth<12)) {

CSCHAR TextOfMonths[12][4] = {

"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"

};

lpMonth = TextOfMonths[nMonth]; }

return (lpMonth);

} /* GetTextOfMonth */

<b>OutputDebugString() prototype in windows.h (v3.1)</b> void WINAPI


OutputDebugString(LPCSTR);

<b>OutputDebugString() for MS-DOS programmers</b> void APIENTRY


OutputDebugString( LPSTR lpS ) {

LPSTR lpScreen=(LPSTR)0xB0000000; /* base of mono screen */

int nPos=0; /* for walking lpS string */

/*--- Scroll monochrome screen up one line ---*/


_fmemcpy( lpScreen, lpScreen+2*80, 2*80*24 ); /*--- Place new line
down in 25'th line ---*/

for (int loop=0; loop<80; ++loop) {

lpScreen[2*(80*24+loop)] = (lpS[nPos]?lpS[nPos++]:' '); }

} /* OutputDebugString */

<b>PutMonoChar(), for MS-DOS</b> void APIENTRY PutMonoChar( int


nRow, int nCol, char c ) {

if ((nRow>=0) && (nRow<25) && (nCol>=0) && (nCol<80)) {

*(LPSTR)(0xB0000000+2*(nRow*80+nCol)) = c; }

} /* PutMonoChar */

PutMonoChar() works by first validating that the input nRow and nCol are
valid. It then writes the character directly into monochrome screen video
memory.

The advantage of using PutMonoChar() as opposed to OutputDebugString()


for debug messages is that it is so much faster and is unlikely to adversely
affect the timing sensitive code you want to debug. This is because
PutMonoChar() is just placing one character down instead of
OutputDebugString(), which is placing an entire line down and scrolling the
entire monochrome screen.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>C program that shows MS-DOS version 3.3 lost cluster bug</b>
#include <fcntl.h>

#include <sys\types.h> #include <sys\stat.h> #include <io.h>

#include <stdio.h>

int main(void)

char c; int fh = creat( "it.tmp", S_IREAD|S_IWRITE ); lseek( fh, 81920,


SEEK_SET ); write( fh, &c, 1 ); chsize( fh, 81920 ); lseek( fh, 122880,
SEEK_SET ); write( fh, &c, 1 ); close( fh ); return 0;

} /* main */

<b>Code with an assumption</b> int fh = open(....);

if (fh!=-1) {

close(fh); }

<b>Porting aids for Microsoft C8 segmented architecture</b> #define FAR


_far

#define NEAR _near

#define FASTCALL _fastcall #define PASCAL _pascal

#define EXPORT _export

#define BASEDIN(seg) _based(_segname(#seg)) <br/>

<b>Porting aids for flat model programs</b> #define FAR


#define NEAR

#define FASTCALL

#define PASCAL

#define EXPORT

#define BASEDIN(seg)

<b>Using a far pointer to a char, not a good idea</b> char FAR*lpMem;

<b>LPSTR typedef, a better idea</b> typedef char FAR*LPSTR;

<b>EXTERNC macro</b> /*--- EXTERNC depends upon C/C++ ---*/

#ifdef __cplusplus

#define EXTERNC extern "C"

#else

#define EXTERNC

#endif

<b>Using EXTERNC</b> EXTERNC type APIENTRY FunctionName(


argument-types );

<b>Microsoft C8 assert() macro, do not use</b> #define assert(exp) \

( (exp) ? (void) 0 : _assert(#exp, __FILE__, __LINE__) )

<b>WinAssert(), non-fault-tolerant syntax</b> WinAssert(expression);

<br/>

<b>WinAssert(), fault-tolerant syntax</b> WinAssert(expression) {

(block of code) }
<b>WinAssert() implementation</b> #define USEWINASSERT CSCHAR
szSRCFILE[]=__FILE__; \ BOOL static NEAR _DoWinAssert( int nLine )
{ \ ReportWinAssert(szSRCFILE, nLine); \ WinAssert(nLine); \
return(FALSE); \ }

#define AssertError _DoWinAssert(__LINE__) #define WinAssert(exp) if


(!(exp)) {AssertError;} else

<b>ReportWinAssert() function prototype</b> EXTERNC void


APIENTRY ReportWinAssert( LPSTR, int );

<b>Using WinAssert()</b> #include <app.h>


USEWINASSERT
.

void LOCAL TestingWinAssert( int nValue ) {

WinAssert(nValue>0) {

...

} /* TestingWinAssert */

<b>WinAssert(), used incorrectly</b> WinAssert((x/=2)>0);

<br/>

<b>WinAssert(), used correctly</b> x /= 2;

WinAssert(x>0);

<b>Using typedef to create a new data type</b> typedef char*PSTR;

<br/>

<b>Using macros to create a new data type, a bad practice</b> #define


PSTR char*

<br/>

<b>Using the new PSTR data type</b> PSTR pA, pB;


In the above example, what is the type of pA and what is the type of pB? In
the case of using typedef to create the new data type, the type of pA and pB
is a character pointer, which is as expected. However, in the case of using
the macro to create the new data type, the type of pA is a character pointer
and the type of pB is a character. This is because PSTR pA, pB really
represents char *pA, pB which is not the same as char *pA, *pB.

This example shows the danger in using macros to declare new data types
in the system. Therefore, you should avoid using macros to declare new
data types.

3.4.4 Naming Variables

All variables should be named using the Hungarian variable naming


convention with mixed upper- and lowercase text and no underscore
characters.

Variables should be named using Hungarian notation.

The Hungarian naming convention states that you should prefix all variable
names with a short lowercase abbreviation for the data type of the variable
name. (See Table 3-1).

Prefix Data Type


a array of given type
b BOOL: true/false value
by BYTE
c char
dw DWORD
h handle or abstract pointer
l long
lp long pointer
n int
p pointer
w WORD
Table 3-1: Hungarian Notation Prefixes

For example, nSize is an integer, bOk is a BOOL and hIcon is an abstract


handle. Prefixes may be combined to produce a more descriptive prefix. An
example would be lpnCount, which is a long pointer to an integer and
lpanCounts, which is a long pointer to an array of integers.

The advantage of Hungarian notation is that you are much more likely to
catch a simple programming problem early in the coding cycle, even before
you compile. An example would be nNewIndex = lIndex+10. Just by
glancing at this you can see that the left-hand side is an integer and the
right-hand side is a long integer. This may or may not be what you
intended, but the fact that this can be deduced without seeing the original
data declarations is a powerful concept.

Hungarian notation allows you to know a variable's data type without


seeing the data declaration.

The Hungarian notation handles all built-in data types, but what about
derived types? A technique that I have found useful is to select an
(uppercase) data type name that has a natural mixed upper- and lowercase
name.

An example from the Windows system is the HICON data type and the
hIcon variable name. As another example, let's consider a queue entry data
type called LPQUEUEENTRY. A variable name for this could be
lpQueueEntry.

This convention works great for short data type names like HICON, but not
so well for long data type names like LPQUEUEENTRY. The resulting
variable name lpQueueEntry is just too long to be convenient. In this case,
an abbreviation like lpQE should be used. However, make sure that lpQE is
not also an abbreviation for another data type in your system.
Whatever technique you use to derive variable names from data type names
is fine provided that there is only one derivation technique used in your
entire program. A bad practice would be to use lpQE in one section of code
and lpQEntry in another section of code.

A data type must have a single and unique variable derivation.

3.4.5 Naming Functions

Functions should be named using the module/verb/noun convention in


mixed upper- and lowercase text with no underscore characters.

Functions should be named using the module/verb/noun convention.

Suppose you have just started a project from scratch. There is only one
source file and the number of functions in it is limited. You are naming
functions whatever you feel like and coding is progressing rapidly. Two
months go by and you are working on a new function that needs to call a
specialized memory copy routine you wrote last month. You start to type in
the function name, but then you hesitate. Did you call the function
CopyMem() or MemCopy()? You do not remember, so you look it up real
quick.

This actually happened to me and the solution was simple. Follow the
Microsoft Windows example of naming functions using the verb/noun or
action/object technique. So, the function should have been called
CopyMem().

This solved my immediate problem, but not the long-term problem. It


wasn't long before I had thousands of function names, some with similar
sounding verb/noun names. My solution was to prefix the verb/noun with
the module name.

Suppose you have a module that interfaces with the disk operation system
of your environment. An appropriate module name would be Dos and
several possible function names are DosOpenFile(), DosRead(), DosWrite()
and DosCloseFile().

Module names should contain two to five characters, but an optimum length
for the module name is three to four characters. You can almost always
come up with a meaningful abbreviation for a module that fits in three to
four characters.

3.5 Chapter Summary

A rock-solid layer needs to be built upon all system level interfaces


because system level interfaces contain bugs. You cannot assume that
they are bug-free. I have been burned too many times to trust system
level calls blindly.
When you do make assumptions in calling system code, it is best to
WinAssert() these situations in a code wrapper. When a function does
fail and you assumed that it never would, you want to know about it so
that you can fix the problem and reevaluate your assumption.
Macros can be used as an abstraction layer to allow your code to be
ported without having to change your source files. Instead, just change
the macros contained in your include files and recompile. The macro
name in the source file is specifying what to do. The macro body in the
include file is specifying how to do it.
It is a good idea to use WinAssert()'s generously in your code. The
WinAssert() provides run-time checking of design-time assumptions
and signals a design flaw when it occurs. Use the fault-tolerant form of
WinAssert() whenever possible.
When programming in a large project, it is crucial that a consistent
naming convention be used throughout the entire project. This helps
prevent misunderstandings that produce buggy code.
Macro names should be in uppercase. A macro beginning with an
underscore character is intended to be used only in other macros, not
explicitly in source code.
New data types should be in uppercase and declared with a typedef
statement, not a macro definition.
Variables should be named using the Hungarian notation. This notation
allows you to know the type of a variable without seeing the data
declaration.
Functions should be named using the module/verb/noun convention.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>Pragma to disable warning number 4505 in Microsoft C8</b> #pragma
warning(disable:4505)

<b>CompilerAssert() define</b> #define CompilerAssert(exp) extern char


_CompilerAssert[(exp)?1:-1]

<b>Sample CompilerAssert() usage</b> ...

char buffer[BUFFER_SIZE];

...

CompilerAssert(ISPOWER2(sizeof(buffer))); ...

<b>Testing CompilerAssert(), file test.c</b> #define CompilerAssert(exp)


extern char _CompilerAssert[(exp)?1:-1]

int main(void)

/*--- A forced CompilerAssert ---*/

long lVal;

CompilerAssert(sizeof(lVal)==0); return 0;

} /* main */

<br/><br/>

<b>Compiling test.c under Microsoft C8, should produce an error


C2118</b> cl test.c
<font color="blue">test.c(7) : error C2118: negative subscript</font> <br/>
<br/>

<b>Compiling test.c under UNIX cc, should produce an error message</b>


cc test.c

<font color="blue">test.c: In function `main': test.c:7: size of array


`_CompilerAssert' is negative</font>

<b>Sample variable declaration one</b> char (*buffer)[80];

^^^^

4213

<b>Sample variable declaration two</b> int (*(*testing)(int))[10];

^^^^^^

642135

<b>Sample variable declaration three: signal function</b> void


(*signal(int, void (*)(int)))(int); ^ ^ ^ ^ ^ ^ ^ ^

86125347

<b>PSTR is a variable that is a character pointer</b> char *PSTR;

<b>PSTR is a new type that is a character pointer</b> typedef char *PSTR;

<b>Code that works, but is a bad programming practice</b> typedef int x;

int main(void)

x x;

return 0;
} /* main */

<b>CSCHAR define</b>

#define BASEDIN(seg) _based(_segname(#seg)) #define CSCHAR static


char BASEDIN(_CODE)

<b>Using CSCHAR</b>

CSCHAR szFile[]=__FILE__;

...

OutputName(szFile);

<b>Sample code</b>

#include <stdio.h>

int main(void)

printf( "String: %s" "One","Two" "Three" ); return 0;

} /* main */

<br/>

<b>Output from the sample code</b> String: TwoThreeOne

<b>Splitting up long strings</b> #include <stdio.h>

int main(void)

{
printf( "This is an example of using C's ability to\n"

"contatenate adjacent string literals. It\n"

"is a great way to get ONE printf to display\n"

"a help message.\n" ); return 0;

} /* main */

<b>Placing macro arguments in a string literal</b> #define PRINTD(var)


printf( "Variable " #var "=%d\n", var )

<b>PRINTD() example</b> #include <stdio.h>

#define PRINTD(var) printf( "Variable " #var "=%d\n", var ) int main(void)

int nTest=10; PRINTD(nTest); return 0;

} /* main */

<br/>

<b>PRINTD() example output</b> Variable nTest=10

<b>Printf() prototype for Microsoft C8 in stdio.h</b> int __cdecl


printf(const char *, ...);

<b>Printf example</b>

printf( "Testing %s %d", pString, nNum );

<b>Absolute value macro</b> #define ABS(x) (((x)>0)?(x):-(x))


<b>Code generated by absolute value macro without optimizations</b>
cmp variable reference,OFFSET 0 ;; is number negative?

jle L1 ;; .yes, handle neg number mov ax, variable reference ;; no, get
number jmp L2 ;; .and exit L1: mov ax, variable reference ;; get negative
number neg ax ;; .and make positive
L2:
<b>Code generated by absolute value macro with /Os optimizations</b> 1.
mov ax, variable reference ;; get number 2. cwd ;; sign extend

3. xor ax,dx ;; if number is positive, do 4. sub ax,dx ;; .nothing, otherwise


negate

<b>Code sample showing dangling else problem</b> if (test1)

if (test2) {

...

else {

...

<b>Code sample rewritten to eliminate dangling else problem</b> if (test1)


{

if (test2) {

...

else {

...

}
<b>strncpy() problem, but code may still work</b> #include <stdio.h>

#include <string.h>

int main(void)

char buffer[11]; strncpy( buffer, "hello there", sizeof(buffer) ); printf(


"%s", buffer ); return 0;

} /* main */

<b>Short-circuit logic example</b> if ((lpMem) && (*lpMem=='A')) {

(code body) }

<b>De Morgans laws</b> 1. !(a && b) == (!a || !b) 2. !(a || b) == (!a &&
!b)

<b>SQUARE() macro, has problems</b> #define SQUARE(x) x*x

<b>SQUARE() macro, has problems</b> #define SQUARE(x) (x)*(x)

<b>SQUARE() macro, final form</b> #define SQUARE(x) ((x)*(x))

<b>A macro that needs a scope, has problems</b> #define POW3(x,y) int
i; for(i=0; i<y; ++i) {x*=3;}

<b>A macro with scope, has problems</b> #define POW3(x,y) {int i;


for(i=0; i<y; ++i) {x*=3;}}

<b>Using POW3(), has problems</b> if (expression)

POW3(lNum, nPow); else


(statement)

<b>A macro with scope, final form</b> #define POW3(x,y) do {int i;


for(i=0; i<y; ++i) {x*=3;}} while(0)

<b>Pragma to disable warning number 4127 in Microsoft C8</b> #pragma


warning(disable:4127)

<b>Macro containing if/else, has problems</b> #define ODS(s) if


(bDebugging) OutputDebugString(#s)

<b>Macro containing if/else, problem solved</b> #define ODS(s) if


(!bDebugging) {} else OutputDebugString(#s)

<b>WinAssert() syntax</b> /*--- Ended with a semicolon ---*/

WinAssert(expression);

/*--- Or ended with a block of code ---*/

WinAssert(expression) {

(block of code) }

<b>WinAssert() macro</b> #define WinAssert(exp) if (!(exp))


{AssertError;} else

<b>The LOOP(), LLOOP() and ENDLOOP macros</b> #define


LOOP(nArg) { int _nMax=nArg; int loop; \ for (loop=0; loop<_nMax;
++loop) #define LLOOP(lArg) { long _lMax=lArg; long lLoop; \ for
(lLoop=0; lLoop<_lMax; ++lLoop) #define ENDLOOP }

<b>Sample code that uses LOOP(), LLOOP() and ENDLOOP</b>


LOOP(10) {

printf( "%d\n", loop ); } ENDLOOP

LLOOP(10) {
printf( "%ld\n", lLoop ); } ENDLOOP

<b>C++ loop code, with variable declaration problems</b> for (int loop=0;
loop<10; ++loop) {

...

...

for (int loop=0; loop<10; ++loop) {

...

<b>NUMSTATICELS() desired behavior example</b> #include <stdio.h>

#define NUMSTATICELS(pArray) (determine pArray array size) int


main(void)

long alNums[100]; printf( "array size=%d\n",


NUMSTATICELS(alNums) ); return 0;

} /* main */

<br/>

<b>Desired output</b>

array size=100

<b>NUMSTATICELS() macro</b> #define NUMSTATICELS(pArray)


(sizeof(pArray)/sizeof(*pArray))
<b>Stringizing operator example</b> #define STRING(x) #x

<b>Token pasting operator example</b> CSCHAR szPE7008[]="function


ptr";

CSCHAR szPE700A[]="string ptr";

CSCHAR szPE7060[]="coords";

...

#define EV(n) {0x##n,szPE##n}

static struct {

WORD wError; LPSTR lpError; } BASEDCS ErrorToTextMap[]={


EV(7008), EV(700A), EV(7060) };

<b>Optimize On/Off macros, which do not work</b> #define


OPTIMIZEOFF #pragma optimize("",off) #define OPTIMIZEON #pragma
optimize("",on)

<b>Testing extra preprocessor pass in Microsoft C8 for C code</b> cl -P


test.c

cl -Tctest.i

<br/>

<b>Testing extra preprocessor pass in Microsoft C8 for C++ code</b> cl -P


test.cpp

cl -Tptest.i

<b>Macro that determines if a number is a power of two</b> #define


ISPOWER2(x) (!((x)&((x)-1)))

<b>Swapping integers x, y without a third integer using C</b> 1. x ^= y;


2. y ^= x;

3. x ^= y;

<b>Two's complement negation as a macro</b> #define NEGATE(x)


(((x)^~0)+1)

<b>Base eight number line</b> 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6


7 0 1 2 3 ...

<b>Base eight number line as signed numbers</b> 0 1 2 3 -4 -3 -2 -1 0 1 2


3 -4 -3 -2 -1 ...

<b>MakeProcInstance() prototype</b> FARPROC MakeProcInstance(


FARPROC lpProc, HINSTANCE hInst );

<b>Changing a window's client area to behave like the caption area</b>


case WM_NCHITTEST: {

LRESULT lRet=DefWindowProc(hWnd, message, wParam, lParam);


return ((lRet==HTCLIENT)?HTCAPTION:lRet); break;

Placing this code in the window procedure of the appropriate window


solves the problem. Since WM_NCHITTEST is an action message, we first
call the default window procedure, DefWindowProc(), to perform the
standard hit testing. Next we return the result of the hit test, but change the
client area (HTCLIENT) to look like the caption area (HTCAPTION).

2.5 Chapter Summary

Before you can efficiently institute new programming methodologies


that help reduce bugs in your programs, you need to fully understand
your programming environment. Try to become an expert in your
environment.
Study your environment and learn as much about it as you can. The
learning process never stops. Even if you have been programming in C
for many years, you will still learn new nuances about C all the time.
Your goal should be to become an expert in your programming
environment. While becoming an expert is not a prerequisite to
developing programming methodologies, it does help you develop
more advanced methodologies.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
Preface Writing Bug-Free C Code

Quick Overview of the Book Contacting the Author


How It All Started Acknowledgments

Note to this online book: On April 29, 2002, I reacquired the publishing

rights to my book (from Prentice Hall; published in January 1995), and


have decided to publish it online, where it is now freely available for
anyone to read. The book is showing its age, but for people who still
program in C, the techniques described in this

book are still a 'little gem' worth knowing about. Enjoy!

- Jerry Jongerius, [email protected]

This book describes an alternate class methodology that provides complete


data hiding and fault-tolerant run-time type checking of objects in C
programs. With it, you will produce

code that contains fewer bugs.

The class methodology helps to prevent bugs by making it easier to write C


code. It does this by eliminating data structures (class declarations) from
include files, which makes a project easier to understand (because there is
not as much global information), which makes it easier to write C code,
which helps to eliminate bugs.

This class methodology, which uses private class declarations, is different


from C++, which uses public class declarations.
The class methodology helps detect bugs by providing for both compile-
time and run-time type checking of pointers (handles) to class objects. This
run-time type checking catches a lot of bugs for you since invalid object
handles (the cause of a lot of bugs) are automatically detected and reported.

We have all, at some point in our programming careers, spent several hours
or days tracking down a particularly obscure bug in our code.

Have you ever stepped back and wondered how following a different
programming methodology might have prevented such a bug from
occurring or have automatically detected it? Or have you tracked down the
same type of bug several times?

The key to eliminating bugs from your code is learning from your
mistakes.

I have had my fair share of bugs, but when one does occur, I immediately
try to institute a new programming methodology that helps prevent and/or
automatically detect the bug. That is what this book is all about. It
documents the techniques that I use to write virtually bug-free code. You
may not agree with 100 percent of the techniques I use and that is OK and
only fair, since every programmer has his or her own style. Where you do
disagree, however, I challenge and urge you to come up with an alternate
methodology that works for you.

Quick Overview of the Book

The examples in this book were taken directly from a large production
Windows application. Because of this, some of the names you will find are
tied to the environment in which the program resides. However, I prefer to
show you code taken directly from a real-life production application rather
than code that has been made up simply because this book is being written.

"Understand Why Bugs Exist." Chapter 1 explores why I think bugs

exist in programs today. This is the first step in writing bug-free code. How
can we hope to eliminate bugs from our code unless we understand how
they come to exist?

"Know Your Environment." In order to implement your own programming

methodologies that detect bugs, it is important to fully understand your


programming environment. Chapter 2 examines the C language and
preprocessor in an attempt to show you something new about your
programming environment, even if you have been programming in C for
many years. For example, did you know that buffer[nIndex] is equivalent
to nIndex[buffer]? Chapter 2 also contains a few programming puzzles to
get you thinking about your programming environment.

"Rock-Solid Base." Chapter 3 stresses the importance of coding upon

a rock-solid base. Without a bug-free base to code upon, how can you be
expected to write bug-free code? Chapter 3 suggests what should be done.

"The Class Methodology." The class methodology is the key to writing

bug-free code and is described in Chapter 4. The class methodology allows


complete data hiding and fault-tolerant run-time type checking of objects in
C. It accomplishes complete data hiding by moving data declarations out of
include files and into the one source file that needs the declaration (very
much unlike the public class declarations in C++). The breakthrough to this
class methodology is that pointers (handles) to objects of a class can be type
checked by the compiler in other source files that use the class but do not
have access to the data declaration.

"A New Heap Manager." The heap manager that comes with C does not

detect programmer bugs. Chapter 5 shows how to create a heap manager


that is rock-solid. In addition, the heap manager is needed to support the
class methodology.

"Designing Modules." Now that everything is in place for writing

bug-free code, how should a module be designed? Chapter 6 shows that the
key to designing and coding modules is to always code what to do, not how
to do it.

"General Tips." Chapter 7 contains a variety of tips for writing

bug-free code. For example, a key to writing bug-free code is learning from
your mistakes. Also, designing stack trace support into an application
allows you to track down problems without having to reproduce the
problem.

"Style Guide." Chapter 8 describes the style that I use to write C

code. While every programmer has his or her own way of coding, I feel it is
important that a style guide exist in written form.
"Conclusion." Chapter 9 contains concluding remarks. The class

methodology and run-time type checking are key features presented in this
book.

"Code Listings." This appendix brings together all the code

presented in the book into one convenient location.

How It All Started

It all started when I was a sophomore in high school. Back then, I was
introduced to an Apple II computer and was completely amazed by what
the computer could do. The Apple had great graphics with plenty of games
and educational software. I began to wonder how all the magic was
accomplished.

It wasn't long before I wrote my first BASIC program. Shortly thereafter,


however, I encountered my first bug. The only tools and resources available
were the computer, the computer manuals and myself. Those were trying
times. It taught me to think things through before jumping into coding.

In my probing around into how the computer worked, I quickly came across
something called assembly language and the command CALL -151

(the equivalent of DEBUG for the PC, but in the Apple II ROM). The speed
at which programs executed in relation to BASIC was remarkable. At that
time, all my assembly language programming was done by hand. The
assembly program was entered by typing in the hex digits of the opcode and
operands. To this day I still remember a lot of opcodes even though I have
not coded in 6502 assembly for many years. I quickly realized the benefits
of designing a piece of code before writing it because making any changes
essentially meant rewriting the entire assembly program. Later, I purchased
an assembler.

My quest for finding out how the computer worked has never ended. I
disassembled (reverse engineered) and commented the entire Apple II Disk
Operating System (DOS 3.3 at that time), the bootstrap ROMs and parts of
the BASIC interpreter ROMs. I started writing programs that performed
disk protection, added keyboard buffers, and so on -- in other words,
systems programming.

Today, I am working on an 80486 66-MHz DX2 with 32 megabytes of


memory, local bus video and 600 megabytes of hard disk space -- quite a
change from a 1-MHz, 64 kilobytes of memory, 140-kilobyte floppy disk
drive Apple II. The hardware has changed drastically, but the
methodologies used to write programs haven't changed as fast. The quality
of code certainly hasn't improved by several orders of magnitude.

After college, I started working at Datametrics Systems Corporation, a firm


specializing in the performance of Unisys mainframes. Their goal was to
produce a top-of-the-line performance monitoring package that ran under
Microsoft Windows. A year later, that goal was realized with the release of
ViewPoint 1.0. ViewPoint as it stands today is now approximately a quarter
million lines of code and very stable.

ViewPoint originally started out under Windows 1.0. Shortly thereafter,


Windows 2.0 was released. Since then, Windows 3.0 and now Windows 3.1
have become mega hits in the PC industry.

The great thing about Windows 3.0 was that programming bugs could be
caught much quicker than before because programming errors caused a
general protection fault instead of just trashing memory and more than
likely hanging the PC. This is because under pre-3.0 Windows, programs
had full access to all the memory in the PC, even memory of other
processes. So a bug could trash anything and usually would.

Windows 3.0 supported protected address spaces. Therefore, an access to a


random memory location most likely would be detected (the infamous
UAE, Unrecoverable Application Error) by the operating system and your
program halted.

When I converted ViewPoint to Windows 3.0, it ran the first time.

The protected-mode architecture turned up no programming bugs, not a


single one! In fact, the ViewPoint 1.0 binary, which was targeted to
Windows 2.0, a non-protected OS, can still be run under Windows 3.1, a
protected OS, without any problems. This indicates that the program
contains no invalid memory references and indicated to me that the
programming methodologies that were used to write ViewPoint are valid.

In the last few years, the programming industry has moved from procedural
programming to object-oriented programming (OOP).

However, OOP alone does not solve the underlying quality control
problems facing the industry. OOP alone does not make you a better
programmer.
I wrote this book to document the programming methodologies that I have
used to write ViewPoint under Windows. The methodologies were designed
and developed using Microsoft C5 through C8. Since the introduction of
Microsoft C/C++ 7.0, the code has been ported to compile under C++.

Contacting the Author

If you have any comments on this book, I would like to hear them. I can be
reached through the Internet at

[email protected].

Acknowledgments

Special thanks to P.J. Plauger for taking the time to listen to a first-time
book author. Your help is greatly appreciated and invaluable.

I would like to thank everyone at Prentice Hall who helped to make this
book a reality, especially Paul W. Becker and Patti Guerrieri.

Thanks to John Kelly, the president of Datametrics Systems Corporation,


for allowing me to write a book that discloses the programming techniques
that I developed at Datametrics Systems Corporation.

Thanks to Jim Kelliher, who provided feedback throughout the entire book
writing process. Thanks to John McGowan, who provided feedback on the
class methodology. Thanks to John Dripps, who helped during the early
stages.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>Hard to understand code?</b> nY = (nTotal-1)/nX + 1;

Is this code fragment hard for you to understand? If not, then you know the
technique being used. If you do not know the technique, then it is sure
frustrating to figure out. This code is one way to implement the ceil
function on the nTotal/nX value.

Properly commenting your code is a good first step. However, keep in the
back of your mind that someone else is reading your code and avoid
obscure programming techniques unless they are fully commented and
documented in the project.

1.8 Not Using the Windows Debug Kernel

I am primarily a Microsoft Windows developer and it astounds me the


number of times I have run commercial Windows applications on my
machine only to have them bomb because the Windows debug kernel is
reporting a programming error such as an invalid handle. The error is in
such an obvious place that you know the developers did not use the
debugging kernel of Windows during their development. Why would
anyone develop an application and not use the debugging kernel of the
underlying environment? It catches errors in your code automatically.

The Windows development environment allows for running either the


retail kernel or the debugging kernel. The retail kernel is the kernel as it is
shipped to the customer. The debugging kernel has extra error checking not
present in the retail kernel. Error messages from the kernel may then be
redirected to a secondary monochrome screen attached to the system. This
redirection is an option in the DBWIN.EXE program provided with
Microsoft C8. You should run with the debugging kernel all the time.

Use the debugging kernel of your development environment.

The Windows SDK provides D2N.BAT (debug to non-debug) and


N2D.BAT (non-debug to debug) batch files in the BIN directory to switch
between debug and non-debug Windows. It is easy to accidentally leave
your version of Windows in the non-debug mode. It has happened to me a
couple of times. To detect this situation, I finally printed a special symbol
in my application's status line to signify that it is being run under debug
Windows. I suggest that you do something similar in a place that is easy to
spot in your Windows application. To detect if you are running under
debug Windows, use the GetSystemMetrics(SM_DEBUG) call. It returns
zero under retail Windows and a non-zero value under debug Windows.

1.9 Chapter Summary

The first step in writing bug-free code is to understand why bugs exist.
The second step is to take action. That is what this book is all about.
Programming methodologies that are developed to prevent and detect
bugs must work equally well for both small and large programming
projects.
A technique that helps eliminate data structure declarations from
include files needs to be found. Doing so will allow programmers to
come up to speed on an existing project much quicker.
Global variables that are known to more than one source file should be
avoided. Global variables make it hard to maintain a project.
Debuggers should be used only as a last resort. Having to resort to a
debugger means that your programming methodologies used to detect
bugs have failed.
When you fix a bug, make sure you are really fixing the underlying
cause of the bug and not just the symptom of the bug. Ask yourself
how many times you have fixed the same type of bug.
Strive to write code that is straightforward and easily understandable
by others. Avoid writing code that pulls a lot of tricks.
Finally, make sure that you use the Windows debug kernel all the time.
It contains extra error checking that can automatically detect certain
types of bugs that go undetected in the retail release of Windows.
Copyright © 1993-1995, 2002-2003 Jerry Jongerius
This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>Standard C library memory function prototypes</b> void *malloc (
size_t size );

void free ( void *memblock );

<b>C program, with memory management problems</b> #include


<stdlib.h>

int main(void)

void *pMem=malloc(100); free(pMem);

free(pMem);

return 0;

} /* main */

<b>The heap manager interface</b> EXTERNC LPVOID APIENTRY


FmNew ( SIZET, LPCLASSDESC, LPSTR, int); EXTERNC LPVOID
APIENTRY FmFree ( LPVOID ); EXTERNC LPVOID APIENTRY
FmRealloc ( LPVOID, SIZET, LPSTR, int ); EXTERNC LPVOID
APIENTRY FmStrDup ( LPSTR, LPSTR, int ); EXTERNC void
APIENTRY FmWalkHeap ( void ); EXTERNC BOOL APIENTRY
FmIsPtrOk ( LPVOID );

<b>The prefix and postfix structures</b> /*--- Declare what


LPPREFIX/LPPOSTFIX are ---*/

typedef struct tagPREFIX FAR*LPPREFIX;

typedef struct tagPOSTFIX FAR*LPPOSTFIX;


/*--- Prefix structure before every heap object---*/

typedef struct tagPREFIX {

LPPREFIX lpPrev; /* previous object in heap */

LPPREFIX lpNext; /* next object in heap */

LPPOSTFIX lpPostfix; /* ptr to postfix object */

LPSTR lpFilename; /* filename ptr or NULL */

long lLineNumber; /* line number or 0 */

LPVOID lpMem; /* FmNew() ptr of object */

LPCLASSDESC lpClassDesc; /* class descriptor ptr or NULL */

} PREFIX;

/*--- Postfix structure after every heap object ---*/

typedef struct tagPOSTFIX {

LPPREFIX lpPrefix; } POSTFIX;

<b>Guaranteeing correct prefix structure alignment</b> #define


ALIGNMENT (sizeof(int))

CompilerAssert(ISPOWER2(ALIGNMENT));

CompilerAssert(!(sizeof(PREFIX)%ALIGNMENT));

<b>Aligning a memory size, assumes no overflow</b> #define


DOALIGN(num) (((num)+ALIGNMENT-1)&~(ALIGNMENT-1))
<b>FmNew() function</b>

LPVOID APIENTRY FmNew( SIZET wSize, LPCLASSDESC


lpClassDesc, LPSTR lpFile, int nLine ) {

LPPREFIX lpPrefix; wSize = DOALIGN(wSize); lpPrefix=


(LPPREFIX)malloc(sizeof(PREFIX)+wSize+sizeof(POSTFIX)); if
(lpPrefix) {

AddToLinkedList( lpPrefix ); lpPrefix->lpPostfix = (LPPOSTFIX)


((LPSTR)(lpPrefix+1)+wSize); lpPrefix->lpPostfix->lpPrefix = lpPrefix;
lpPrefix->lpFilename = lpFile; lpPrefix->lLineNumber = nLine; lpPrefix-
>lpMem = lpPrefix+1; lpPrefix->lpClassDesc = lpClassDesc; memset(
lpPrefix->lpMem, 0, wSize ); }

else {

AssertError; /* Report out of memory error */

return(lpPrefix ? lpPrefix+1 : NULL);

} /* FmNew */

<b>AddToLinkedList() function</b> static LPPREFIX


lpHeapHead=NULL;

void LOCAL AddToLinkedList( LPPREFIX lpAdd ) {

/*--- Add before current head of list ---*/

if (lpHeapHead) {

lpAdd->lpPrev = lpHeapHead->lpPrev; (lpAdd->lpPrev)->lpNext =


lpAdd; lpAdd->lpNext = lpHeapHead; (lpAdd->lpNext)->lpPrev = lpAdd;
}
/*--- Else first node ---*/

else {

lpAdd->lpPrev = lpAdd; lpAdd->lpNext = lpAdd; }

/*--- Make new item head of list ---*/

lpHeapHead = lpAdd;

} /* AddToLinkedList */

<b>FmFree() function</b>

LPVOID APIENTRY FmFree( LPVOID lpMem )

if (VerifyHeapPointer(lpMem)) {

LPPREFIX lpPrefix=(LPPREFIX)lpMem-1; SIZET wSize=(LPSTR)


(lpPrefix->lpPostfix+1)-(LPSTR)lpPrefix; RemoveFromLinkedList(
lpPrefix ); memset( lpPrefix, 0, wSize ); free(lpPrefix);

return (NULL);

} /* FmFree */

<b>RemoveFromLinkedList() function</b> void LOCAL


RemoveFromLinkedList( LPPREFIX lpRemove ) {

/*--- Remove from doubly linked list ---*/

(lpRemove->lpPrev)->lpNext = lpRemove->lpNext; (lpRemove-


>lpNext)->lpPrev = lpRemove->lpPrev; /*--- Possibly correct head pointer
---*/

if (lpRemove==lpHeapHead) {

lpHeapHead = ((lpRemove->lpNext==lpRemove) ? NULL : lpRemove-


>lpNext); }

} /* RemoveFromLinkedList */

<b>VerifyHeapPointer() function</b> BOOL LOCAL VerifyHeapPointer(


LPVOID lpMem ) {

BOOL bOk=FALSE;

if (lpMem) {

WinAssert(FmIsPtrOk(lpMem)) {

LPPREFIX lpPrefix=(LPPREFIX)lpMem-1; WinAssert(lpPrefix-


>lpMem==lpMem) {

WinAssert(lpPrefix->lpPostfix->lpPrefix==lpPrefix) {

bOk = TRUE;

return (bOk);
} /* VerifyHeapPointer */

<b>FmIsPtrOk() function, for Intel segmented protected-mode


application</b> BOOL APIENTRY FmIsPtrOk( LPVOID lpMem )

BOOL bOk=FALSE;

_asm xor ax, ax ;; assume bad selector _asm lsl ax, word ptr [lpMem+2]
;; get selector limit _asm cmp word ptr [lpMem], ax ;; is ptr offset under
limit _asm jae done ;; no, bad pointer _asm mov bOk, 1 ;; yes, pointer OK

_asm done:

return (bOk);

} /* FmIsPtrOk */

<b>FmIsPtrOk() function, for non-protected-mode application</b> BOOL


APIENTRY FmIsPtrOk( LPVOID lpMem )

return ((lpMem) && (!((long)lpMem&(ALIGNMENT-1))));

} /* FmIsPtrOk */

<b>FmWalkHeap() function</b>

void APIENTRY FmWalkHeap( void )

if (lpHeapHead) {

LPPREFIX lpCur=lpHeapHead; while (VerifyHeapPointer(&lpCur[1])) {


char buffer[100];

RenderDesc( lpCur, buffer ); /*--- print out buffer ---*/

/* printf( "walk: %s\n", buffer ); */

lpCur = lpCur->lpNext; if (lpCur==lpHeapHead) {

break;

} /* FmWalkHeap */

<b>RenderDesc() function</b>

void LOCAL RenderDesc( LPPREFIX lpPrefix, LPSTR lpBuffer ) {

if (lpPrefix->lpMem==&lpPrefix[1]) {

sprintf( lpBuffer, "%08lx ", lpPrefix ); if (lpPrefix->lpFilename) {

sprintf( lpBuffer+strlen(lpBuffer), "%12s %4ld ", lpPrefix->lpFilename,


lpPrefix->lLineNumber ); }

if (lpPrefix->lpClassDesc) {

sprintf( lpBuffer+strlen(lpBuffer), "%s", lpPrefix->lpClassDesc-


>lpVarName ); }

else {
strcpy( lpBuffer, "(bad)" ); }

} /* RenderDesc */

<b>NEWSTRING() macro</b>

#define NEWSTRING(lpDst,wSize) \

(_LPV(lpDst)=FmNew((SIZET)(wSize),NULL,szSRCFILE,__LINE__))

<b>MYLSTRDUP() macro</b>

#define MYLSTRDUP(lpDst,lpSrc) \

(_LPV(lpDst)=FmStrDup(lpSrc,szSRCFILE,__LINE__))

<b>FmStrDup()</b>

LPVOID APIENTRY FmStrDup( LPSTR lpS, LPSTR lpFile, int nLine ) {

LPVOID lpReturn=NULL; if (lpS) {

SIZET wSize = (SIZET)(strlen(lpS)+1); lpReturn = FmNew( wSize,


NULL, lpFile, nLine ); if (lpReturn) {

memcpy( lpReturn, lpS, wSize ); }

return(lpReturn);

} /* FmStrDup */

<b>NEWARRAY macro</b>

#define NEWARRAY(lpArray, wSize) \


(_LPV(lpArray)=FmNew((SIZET)(sizeof(*(lpArray))*(wSize)), \
NULL,szSRCFILE,__LINE__))

<b>SIZEARRAY Macro</b>

#define SIZEARRAY(lpArray, wSize) \

(_LPV(lpArray)=FmRealloc((lpArray), \ (SIZET)(sizeof(*(lpArray))*
(wSize)),szSRCFILE,__LINE__))

SIZEARRAY() takes as its first argument the name of the variable that
points to an allocated array. If it is NULL, SIZEARRAY() allocates a new
array. The second argument is the new size of the array in array elements
(not bytes).

5.5 Detecting Storage Leaks

If a program allocates an object from the heap, it should also free the object
before the program exits. If the object is not freed, it is an error and is called
a storage leak.

Do you have any storage leaks in the programs that you write? How do you
detect storage leaks? Unless you have a formal methodology in place, how
do you really know?

I once wrote a large program that did not have any formal storage leak
detection. The program had no problems and I would have claimed that
there were no storage leaks present. However, adding storage leak detection
actually turned up a few obscure leaks. They were so obscure that no matter
how long the program was run, the storage leaks would never have caused a
problem and this is why they were never found.

No matter how small or large a program you write, the program should
always have some form of storage leak detection in place.

Storage leak detection should always be in place.


The simplest way to detect storage leaks is to call FmWalkHeap() the
moment your program is about to exit. You will then see exactly what
objects are left in the heap, if any. Because the filename and line number
where the object are allocated is actually displayed, tracking down the
storage leak is a snap.

5.6 Windows Memory Model Issues

There is a long-standing problem in the Windows environment as to which


memory model should be used for developing a program. It is complicated
by the fact that most compilers for Windows support four basic memory
models. They are small, medium, compact and large. However, the choice
is often between the medium memory model and the large memory model
because small and compact are too limiting.

5.6.1 The Large Memory Model Myth

It is widely believed that the large memory model should not be used for
Windows programming. This was true at one time, but today it is a myth!

In Windows, if a program contains more than one data segment, it can be


run only once, not multiple times. There is also a limit on how many
memory allocations can be made. This limit is around 4,096 or 8,192,
depending upon how Windows was run (standard versus enhanced-mode).
This limit is actually a hardware limit in how many segments it has to
manage.

These limits existed in Microsoft C6, but beginning with Microsoft C7,
these limits were removed. The limits existed due to how the compiler and
the run-time libraries of C6 were written.

In C6 with the large memory model, each source file would gets its own
data segment. However, starting with C7 only one data segment is
produced if possible. So this solves the data segment problem.

Concerning the limit on the number of memory allocations, C6 used to eat


up a segment on every memory allocation. Starting with C7, the run-time
library now employs a subsegment allocation scheme which all but
eliminates the problem.

The bottom line is that if you do not mind the overhead of using far
pointers extensively (instead of near pointers), go ahead and use the large
memory model. It sure makes life a lot easier.

For a more in-depth discussion on the large memory model myth, I suggest
you read Windows Internals by Matt Pietrek.

5.7 Chapter Summary

The problem with traditional heap managers is that they assume the
programmer never makes a mistake in calling the interface. The
traditional heap manager is simply not robust enough for your needs.
The solution is to code a module that provides a code wrapper around
the existing heap manger.
Since the new heap manager must also meet the needs of the class
methodology and run-time object verification, every block of memory
that is allocated through the new heap manager interface is prefixed
and postfixed by a structure.
NEWSTRING() and MYLSTRDUP() provide a new interface for
strings. NEWARRAY() and SIZEARRAY() provide a new interface
for arrays.
Storage leak detection must always be in place in the programs you
write. The simplest way to detect storage leaks is to call
FmWalkHeap() the moment your program is about to exit.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>EndLine comment examples, using C++ //</b> typedef struct {

int x; // the x coordinate of the point int y; // the y coordinate of the point
} POINT;

int main(void)

int nCpuBusy; // how busy is the cpu (code body) return 0; }

<b>EndLine comment examples, using /* */</b> typedef struct {

int x; /* the x coordinate of the point */

int y; /* the y coordinate of the point */

} POINT;

int main(void)

int nCpuBusy; /* how busy is the cpu */

(code body) return 0; }

<b>Inline comment example</b> #include <stdlib.h>

#include <stdio.h>

int main(void)

static char buffer[9000]; /* buffer for input line */


long lLineCount=0; /* number of lines read */

/*--- Count the number of lines in the input ---*/

while (safegets(buffer,sizeof(buffer))) {

++lLineCount; }

/*--- Display the total line count ---*/

printf( "Number of Lines = %ld\n", lLineCount ); return 0;

} /* main */

<b>Function template</b> /*pf-----------------------------------------------------


---------

* DESCRIPTION: (Short Description) programmers-initials *

* (A long description of the function) *

* ARGUMENTS:

* Arg1 - Arg1 description * ...

* ArgN - ArgN description *

* RETURNS:

* (A description of the return value) *

* NOTES:

*
* (optional notes section) *

*--------------------------------------------------------------*/

type APIENTRY FunctionName( arguments ) {

type variable1; /* Comment */

type variable2; /* Comment */

/*--- Comment ---*/

(block of code) /*--- Comment ---*/

(block of code) ...

} /* FunctionName */

<b>if statement template</b> /*--- Comment ---*/

if (expression) {

statement; ...

<b>if/else statement template</b> /*--- Comment ---*/

if (expression) {

statement; ...

/*--- Else comment ---*/


else {

statement; ...

<b>while statement template</b> /*--- Comment ---*/

while (expression) {

statement; ...

<b>do statement template</b> /*--- Comment ---*/

do {

statement; ...

} while (expression);

<b>for statement template</b> /*--- Comment ---*/

for (loop=0; loop<nMax; ++loop) {

statement; ...

<b>switch statement template</b> /*--- Comment ---*/

switch (expression) {

case constant: statement; ...

break; case constant: {

statement; ...
break; }

default: statement; ...

break; }

<b>Bad use of comma in auto variable definition</b> void Testing( void )

char *pBeg, pEnd; /* comment */

...

} /* Testing */

<br/>

<b>Correct way to define auto variables</b> void Testing( void )

char *pBeg; /* comment */

char *pEnd; /* comment */

...

} /* Testing */

<b>Comma operator syntax</b> expression, expression

However, using the comma operator can lead to code that is hard to read
and maintain. It is best to avoid using it.
One possible exception is in macros where it is important that the macro be
viewed by the programmer as a single expression. If you cannot accomplish
everything in one expression, do you give up? In cases like this, it is
important to avoid having to change the syntax of how a macro is used and
usage of the comma operator is allowed.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
Chapter 9: Conclusion Writing Bug-Free C Code

9.1 The Class Methodology 9.3 Isolating Change through Macros


9.2 Run-Time Type Checking 9.4 The Learning Process Never Stops

The class methodology (Chapter 4) is the core methodology described

in this book. It makes writing C code easier because it solves the


information overload problem that occurs when too many data structures
are declared in include files.

9.1 The Class Methodology

The class methodology moves data structure declarations out of include


files and places them instead in class implementation modules. Data
structures are never accessed directly by code that uses a class. Instead, data
structures are turned into private objects that are controlled by calling
method functions -- functions that create, manipulate and destroy an object.

All the code that manipulates an object is now isolated into one source file
(or module). This leads to a program that consists solely of a number of
well-isolated modules. Such a program is easy to enhance and maintain,
since changing the implementation of an object involves only code changes
in one source file.
The method function names are specifying what to do, not how to do it. For
example, DosWriteFile(hDosFh, lpMem, wSize) is specifying that we want
to write some information to a file. The how is left up to DosWriteFile().

Applying the class methodology to a program is a deceptively simple thing


to do, but it is a powerful concept when applied to an entire program.

9.2 Run-Time Type Checking

If all method functions of a class employ run-time type checking on


pointers (handles) passed into the module, a lot of common programming
errors will be detected automatically and reported.

When combined with full symbolic stack traces at the point of

failure, almost all problems can be deduced from the symbolic stack trace
and fixed. A problem does not have to be reproducible in order to track
down the problem.

9.3 Isolating Change through Macros


Over the years, the class methodology has undergone a lot of changes in
how it is implemented. However, through all these changes, the code base
has changed little.

The macros that were used in the source code specify what to do and not
how to do it. For example, the NEWOBJ(hObj) and VERIFY(hObj) macro
syntax has stayed the same, but the implementation of these macros has
changed drastically.

The key is to pick the correct what (or interface). If done

correctly, the what can stay the same and the how can change drastically.

9.4 The Learning Process Never Stops

Writing bug-free C code takes a lot of effort. It is not something that just
happens. You could have all the latest whiz-bang tools and languages, but
they do not help you to write bug-free code unless you have a thorough
knowledge of the tools and languages themselves.

Consider a carpenter's tools. If you were given all of the

carpenter's tools, could you build a house? Of course not. Why?


Mainly because you do not have the knowledge of how to use the tools. The
same goes for programming and writing bug-free code.

How can you be expected to write quality code unless you know your tools
inside out? No matter what skill level you are at, there will always be
something new to learn because the learning process never stops. I am
amazed that even after years of programming in C I am still learning new
nuances about the language.

I have no doubt that the techniques described in this book will continue to
be refined. I would be disappointed if they were not.

They are just a snapshot of the techniques that I use today, techniques that
have been refined over many years of programming in C.

I hope you have learned from my techniques something new about writing
bug-free C code.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
<b>APP.H</b>

/*--- Standard Includes ----------------------------------------*/

#include <string.h> /* strcpy/memset */

#include <stdio.h> /* sprintf */

#include <stdlib.h> /* malloc/free */

/*--- The include file for this book ---------------------------*/

#include "book.h"

/*--- NEWHANDLE section ----------------------------------------*/

NEWHANDLE(HRAND);

NEWHANDLE(HDOSFH);

/*--- USE_* section --------------------------------------------*/

#ifdef USE_HRAND

/*----------------------------------------------------------------

* Random number generator

*
*--------------------------------------------------------------*/

EXTERNC HRAND APIENTRY RandCreate ( int ); EXTERNC HRAND


APIENTRY RandDestroy ( HRAND ); EXTERNC int APIENTRY
RandNext ( HRAND ); #endif

#ifdef USE_LOWIO

/*--------------------------------------------------------------

* Access to low-level I/O run-time library functions *

*-------------------------------------------------------------*/

#include <fcntl.h>

#include <sys\types.h>

#include <sys\stat.h>

#include <io.h>

#endif

#ifdef USE_HDOSFH

/*--------------------------------------------------------------

*
* Code wrapper to low-level file I/O

*-------------------------------------------------------------*/

EXTERNC HDOSFH APIENTRY DosOpenFile ( LPSTR ); EXTERNC


WORD APIENTRY DosRead ( HDOSFH, LPVOID, WORD ); EXTERNC
WORD APIENTRY DosWrite ( HDOSFH, LPVOID, WORD );
EXTERNC HDOSFH APIENTRY DosCloseFile ( HDOSFH ); #endif

<b>BOOK.H</b>

/*--- If target is C8 segmented architecture ---*/

#if (defined(_MSC_VER) && defined(MSDOS)) #define FAR _far

#define NEAR _near

#define FASTCALL _fastcall

#define PASCAL _pascal

#define EXPORT _export

#define BASEDIN(seg) _based(_segname(#seg))

/*--- Else assume target is flat memory model ---*/

#else

#define FAR

#define NEAR

#define FASTCALL
#define PASCAL

#define EXPORT

#define BASEDIN(seg)

#endif

/*--- size_t mapping ---*/

#define SIZET size_t

/*--- Useful defines ---*/

#ifndef _WINDOWS

typedef char FAR*LPSTR;

typedef void FAR*LPVOID;

typedef unsigned short WORD;

typedef int BOOL;

#define FALSE (0)

#define TRUE (1)

#endif

/*--- Assert during compiling (not run-time) ---*/

#define CompilerAssert(exp) extern char _CompilerAssert[(exp)?1:-1]


/*--- TYPEDEF depends upon C/C++ ---*/

#ifdef __cplusplus

#define TYPEDEF

#else

#define TYPEDEF typedef

#endif

/*--- EXTERNC depends upon C/C++ ---*/

#ifdef __cplusplus

#define EXTERNC extern "C"

#else

#define EXTERNC

#endif

/*--- APIENTRY for app/dll ---*/

#ifdef _WINDLL

#define APIENTRY EXPORT FAR PASCAL

#else

#define APIENTRY FAR PASCAL


#endif

/*--- LOCAL/LOCALASM defines ---*/

#define LOCAL static NEAR FASTCALL

#define LOCALASM static NEAR PASCAL

/*--- Other useful defines ---*/

#define CSCHAR static char BASEDIN(_CODE) #define NewScope

/*--- Absolute value macro ---*/

#define ABS(x) (((x)>0)?(x):-(x))

/*--- Is a number a power of two ---*/

#define ISPOWER2(x) (!((x)&((x)-1)))

/*--- Number of static elements in an array ---*/

#define NUMSTATICELS(pArray) (sizeof(pArray)/sizeof(*pArray))

/*--- Loop Macros ---*/

#define LOOP(nArg) { int _nMax=nArg; int loop; \ for (loop=0;


loop<_nMax; ++loop) #define LLOOP(lArg) { long _lMax=lArg; long
lLoop; \ for (lLoop=0; lLoop<_lMax; ++lLoop) #define ENDLOOP }

/*--- WinAssert support ---*/


#define USEWINASSERT CSCHAR szSRCFILE[]=__FILE__; \ BOOL
static NEAR _DoWinAssert( int nLine ) { \ ReportWinAssert(szSRCFILE,
nLine); \ WinAssert(nLine); \ return(FALSE); \ }

#define AssertError _DoWinAssert(__LINE__) #define WinAssert(exp) if


(!(exp)) {AssertError;} else EXTERNC void APIENTRY
ReportWinAssert( LPSTR, int );

/*--- What is a class descriptor---*/

typedef struct {

LPSTR lpVarName; } CLASSDESC, FAR*LPCLASSDESC;

/*--- Declare a new handle ---*/

#define NEWHANDLE(Handle) typedef struct tag##Handle *Handle

/*--- Class descriptor name from object name ---*/

#define _CD(hObj) hObj##_ClassDesc

/*--- The class macro ---*/

#define CLASS(hObj,Handle) \

static CLASSDESC _CD(hObj)={#hObj}; TYPEDEF struct tag##Handle

/*--- Object verification macros---*/

#define VERIFY(hObj) WinAssert(_VERIFY(hObj)) #define


VERIFYZ(hObj) if (!(hObj)) {} else VERIFY(hObj)

/*--- Object verification helper macros ---*/

#define _S4 (sizeof(LPCLASSDESC))


#define _S8 (sizeof(LPCLASSDESC)+sizeof(LPVOID)) #define
_VERIFY(hObj) \

( FmIsPtrOk(hObj) && \ (((LPVOID)hObj)==*(LPVOID FAR*)


((LPSTR)hObj-_S8)) \ && ((&_CD(hObj))==*(LPCLASSDESC FAR*)
((LPSTR)hObj-_S4)) )

/*--- Heap manager prototypes ---*/

EXTERNC LPVOID APIENTRY FmNew ( SIZET, LPCLASSDESC,


LPSTR, int); EXTERNC LPVOID APIENTRY FmFree ( LPVOID );
EXTERNC LPVOID APIENTRY FmRealloc ( LPVOID, SIZET, LPSTR,
int ); EXTERNC LPVOID APIENTRY FmStrDup ( LPSTR, LPSTR, int );
EXTERNC void APIENTRY FmWalkHeap ( void ); EXTERNC BOOL
APIENTRY FmIsPtrOk ( LPVOID );

/*--- NEWOBJ() and FREE() Interface ---*/

#define _LPV(hObj) *(LPVOID FAR*)&hObj #define NEWOBJ(hObj) \

(_LPV(hObj)=FmNew(sizeof(*hObj),&_CD(hObj),szSRCFILE,__LINE
__)) #define FREE(hObj) (_LPV(hObj)=FmFree(hObj))

/*--- String interface macros ---*/

#define NEWSTRING(lpDst,wSize) \

(_LPV(lpDst)=FmNew((SIZET)(wSize),NULL,szSRCFILE,__LINE__))
#define MYLSTRDUP(lpDst,lpSrc) \

(_LPV(lpDst)=FmStrDup(lpSrc,szSRCFILE,__LINE__))

/*--- Array interface macros ---*/

#define NEWARRAY(lpArray, wSize) \

(_LPV(lpArray)=FmNew((SIZET)(sizeof(*(lpArray))*(wSize)), \
NULL,szSRCFILE,__LINE__)) #define SIZEARRAY(lpArray, wSize) \
(_LPV(lpArray)=FmRealloc((lpArray), \ (SIZET)(sizeof(*(lpArray))*
(wSize)),szSRCFILE,__LINE__))

<b>HEAP.C</b>

/*pm--------------------------------------------------------------

* OUTLINE:

* This module REPLACES the memory management routines of * the C


run-time library. As such, this new interface * should be used exclusively.

* IMPLEMENTATION:

* A wrapper is provided around all memory objects that * allows for run-
time type checking, symbolic dumps of * the heap and validation of heap
pointers.

* NOTES:

* - YOU must code an FmIsPtrOk() that works properly for * your


environment.

*--------------------------------------------------------------*/
#include "app.h"
USEWINASSERT

/*--- Heap objects are aligned on sizeof(int) boundaries ---*/

#define ALIGNMENT (sizeof(int))

#define DOALIGN(num) (((num)+ALIGNMENT-1)&~(ALIGNMENT-1))


CompilerAssert(ISPOWER2(ALIGNMENT));

/*--- Declare what LPPREFIX/LPPOSTFIX are ---*/

typedef struct tagPREFIX FAR*LPPREFIX; typedef struct tagPOSTFIX


FAR*LPPOSTFIX;

/*--- Prefix structure before every heap object---*/

typedef struct tagPREFIX {

LPPREFIX lpPrev; /* previous object in heap */

LPPREFIX lpNext; /* next object in heap */

LPPOSTFIX lpPostfix; /* ptr to postfix object */

LPSTR lpFilename; /* filename ptr or NULL */

long lLineNumber; /* line number or 0 */

LPVOID lpMem; /* FmNew() ptr of object */

LPCLASSDESC lpClassDesc; /* class descriptor ptr or NULL */

} PREFIX;
/*--- Postfix structure after every heap object ---*/

typedef struct tagPOSTFIX {

LPPREFIX lpPrefix; } POSTFIX;

/*--- Verify alignment of prefix structure ---*/

CompilerAssert(!(sizeof(PREFIX)%ALIGNMENT));

/*--- Points to first object in linked list of heap objects ---*/

static LPPREFIX lpHeapHead=NULL;

/*--- Local prototypes ---*/

void LOCAL AddToLinkedList ( LPPREFIX ); void LOCAL


RemoveFromLinkedList ( LPPREFIX ); BOOL LOCAL VerifyHeapPointer
( LPVOID ); void LOCAL RenderDesc ( LPPREFIX, LPSTR );

/*pf--------------------------------------------------------------

* DESCRIPTION: (Far Memory New) JLJ

* Allocate a new block of memory from the heap.

* ARGUMENTS:

*
* wSize - Size of object to allocate * lpClassDesc - Class descriptor for
object (or NULL) * lpFile - Filename where object was allocated * nLine -
Line number where object was allocated *

* RETURNS:

* A long pointer to the memory object or NULL

*--------------------------------------------------------------*/

LPVOID APIENTRY FmNew( SIZET wSize, LPCLASSDESC


lpClassDesc, LPSTR lpFile, int nLine ) {

LPPREFIX lpPrefix; wSize = DOALIGN(wSize); lpPrefix=


(LPPREFIX)malloc(sizeof(PREFIX)+wSize+sizeof(POSTFIX)); if
(lpPrefix) {

AddToLinkedList( lpPrefix ); lpPrefix->lpPostfix = (LPPOSTFIX)


((LPSTR)(lpPrefix+1)+wSize); lpPrefix->lpPostfix->lpPrefix = lpPrefix;
lpPrefix->lpFilename = lpFile; lpPrefix->lLineNumber = nLine; lpPrefix-
>lpMem = lpPrefix+1; lpPrefix->lpClassDesc = lpClassDesc; memset(
lpPrefix->lpMem, 0, wSize ); }

else {

AssertError; /* Report out of memory error */

return(lpPrefix ? lpPrefix+1 : NULL);

} /* FmNew */
/*pf--------------------------------------------------------------

* DESCRIPTION: (Far Memory Free) JLJ

* Free a block of memory that was previously allocated * through


FmNew().

* ARGUMENTS:

* lpMem - Heap pointer to free or NULL

* RETURNS:

* NULL

*--------------------------------------------------------------*/

LPVOID APIENTRY FmFree( LPVOID lpMem ) {

if (VerifyHeapPointer(lpMem)) {
LPPREFIX lpPrefix=(LPPREFIX)lpMem-1; SIZET wSize=(LPSTR)
(lpPrefix->lpPostfix+1)-(LPSTR)lpPrefix; RemoveFromLinkedList(
lpPrefix ); memset( lpPrefix, 0, wSize ); free(lpPrefix); }

return (NULL);

} /* FmFree */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Far Memory String Dup) JLJ

* Helper function for the MYLSTRDUP() macro *

* ARGUMENTS:

* lpS - String to duplicate (or NULL) * lpFile - Filename where string is


being duplicated * nLine - Line number where string is being duplicated *

* RETURNS:

* A pointer to the duplicated string or NULL

*--------------------------------------------------------------*/
LPVOID APIENTRY FmStrDup( LPSTR lpS, LPSTR lpFile, int nLine ) {

LPVOID lpReturn=NULL;

if (lpS) {

SIZET wSize = (SIZET)(strlen(lpS)+1); lpReturn = FmNew( wSize,


NULL, lpFile, nLine ); if (lpReturn) {

memcpy( lpReturn, lpS, wSize ); }

return(lpReturn);

} /* FmStrDup */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Far Memory Realloc) JLJ

* Reallocate a block of memory

* ARGUMENTS:

* lpOld - Heap object to reallocate or NULL

* wSize - New size of the object


* lpFile - Filename where realloc is taking place * nLIne - Line number
where realloc is taking place *

* RETURNS:

* A pointer to the reallocated memory or NULL

*--------------------------------------------------------------*/

LPVOID APIENTRY FmRealloc( LPVOID lpOld, SIZET wSize, LPSTR


lpFile, int nLine ) {

LPVOID lpNew=NULL;

/*--- Try to realloc ---*/

if (lpOld) {

if (VerifyHeapPointer(lpOld)) {

LPPREFIX lpPrefix=(LPPREFIX)lpOld-1; LPPREFIX lpNewPrefix;


LPPREFIX lpPre;

/*--- Try to reallocate block ---*/

RemoveFromLinkedList( lpPrefix ); memset( lpPrefix->lpPostfix, 0,


sizeof(POSTFIX) ); wSize = DOALIGN(wSize); lpNewPrefix=
(LPPREFIX)realloc(lpPrefix, sizeof(PREFIX)+wSize+sizeof(POSTFIX));

/*--- Add new (or failed old) back in ---*/

lpPre=(lpNewPrefix?lpNewPrefix:lpPrefix); AddToLinkedList( lpPre );


lpPre->lpPostfix = (LPPOSTFIX)((LPSTR)(lpPre+1)+wSize); lpPre-
>lpPostfix->lpPrefix = lpPre; lpPre->lpMem = lpPre+1;

/*--- Finish ---*/

lpNew = (lpNewPrefix ? &lpNewPrefix[1] : NULL); if (!lpNew) {

/* Report out of memory error */

AssertError; }

/*--- Else try new allocation ---*/

else {

lpNew = FmNew( wSize, NULL, lpFile, nLine ); }

/*--- Return address to object ---*/

return(lpNew);

} /* FmRealloc */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Walk Heap) JLJ

*
* Display a symbolic dump of the heap by walking the * heap and
displaying all objects in the heap.

* ARGUMENTS:

* (void)

* RETURNS:

* (void)

*--------------------------------------------------------------*/

void APIENTRY FmWalkHeap( void )

if (lpHeapHead) {

LPPREFIX lpCur=lpHeapHead; while (VerifyHeapPointer(&lpCur[1])) {

char buffer[100]; RenderDesc( lpCur, buffer ); /*--- print out buffer ---*/

/* printf( "walk: %s\n", buffer ); */

lpCur = lpCur->lpNext; if (lpCur==lpHeapHead) {

break;
}

} /* FmWalkHeap */

/*p---------------------------------------------------------------

* DESCRIPTION: (Add Heap Object to Linked List) JLJ

* Add the given heap object into the doubly linked list * of heap objects.

* ARGUMENTS:

* lpAdd - Prefix pointer to heap object *

* RETURNS:

* (void)

*--------------------------------------------------------------*/
void LOCAL AddToLinkedList( LPPREFIX lpAdd ) {

/*--- Add before current head of list ---*/

if (lpHeapHead) {

lpAdd->lpPrev = lpHeapHead->lpPrev; (lpAdd->lpPrev)->lpNext =


lpAdd; lpAdd->lpNext = lpHeapHead; (lpAdd->lpNext)->lpPrev = lpAdd;
}

/*--- Else first node ---*/

else {

lpAdd->lpPrev = lpAdd; lpAdd->lpNext = lpAdd; }

/*--- Make new item head of list ---*/

lpHeapHead = lpAdd;

} /* AddToLinkedList */

/*p---------------------------------------------------------------

* DESCRIPTION: (Remove Heap Object from Linked List) JLJ

*
* Remove the given heap object from the doubly linked list * of heap
objects.

* ARGUMENTS:

* lpRemove - Prefix pointer to heap object *

* RETURNS:

* (void)

*--------------------------------------------------------------*/

void LOCAL RemoveFromLinkedList( LPPREFIX lpRemove ) {

/*--- Remove from doubly linked list ---*/

(lpRemove->lpPrev)->lpNext = lpRemove->lpNext; (lpRemove-


>lpNext)->lpPrev = lpRemove->lpPrev;

/*--- Possibly correct head pointer ---*/

if (lpRemove==lpHeapHead) {

lpHeapHead = ((lpRemove->lpNext==lpRemove) ? NULL : lpRemove-


>lpNext); }
} /* RemoveFromLinkedList */

/*p---------------------------------------------------------------

* DESCRIPTION: (Verify Heap Pointer) JLJ

* Verify that a pointer points into that heap to a valid * object in the heap.

* ARGUMENTS:

* lpMem - Heap pointer to validate *

* RETURNS:

* Heap pointer is valid (TRUE) or not (FALSE) *

*--------------------------------------------------------------*/

BOOL LOCAL VerifyHeapPointer( LPVOID lpMem ) {

BOOL bOk=FALSE;

if (lpMem) {
WinAssert(FmIsPtrOk(lpMem)) {

LPPREFIX lpPrefix=(LPPREFIX)lpMem-1; WinAssert(lpPrefix-


>lpMem==lpMem) {

WinAssert(lpPrefix->lpPostfix->lpPrefix==lpPrefix) {

bOk = TRUE;

return (bOk);

} /* VerifyHeapPointer */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Does Pointer Point into the Heap) JLJ

* Does the given memory pointer point anywhere into * the heap.

* ARGUMENTS:

*
* lpMem - Heap pointer to check

* RETURNS:

* Pointer points into the heap (TRUE) or not (FALSE) *

*--------------------------------------------------------------*/

#if (defined(_WINDOWS) || defined(_WINDLL)) BOOL APIENTRY


FmIsPtrOk( LPVOID lpMem ) {

BOOL bOk=FALSE; _asm xor ax, ax ;; assume bad selector _asm lsl ax,
word ptr [lpMem+2] ;; get selector limit _asm cmp word ptr [lpMem], ax ;;
is ptr offset under limit _asm jae done ;; no, bad pointer _asm mov bOk, 1 ;;
yes, pointer OK

_asm done:

return (bOk);

} /* FmIsPtrOk */

#else

BOOL APIENTRY FmIsPtrOk( LPVOID lpMem ) {

return ((lpMem) && (!((long)lpMem&(ALIGNMENT-1))));

} /* FmIsPtrOk */

#endif
/*p---------------------------------------------------------------

* DESCRIPTION: (Render Description of Heap Object) JLJ

* Render a text description for the given heap object.

* ARGUMENTS:

* lpPrefix - Prefix pointer to heap object * lpBuffer - Where to place text


description *

* RETURNS:

* (void)

*--------------------------------------------------------------*/

void LOCAL RenderDesc( LPPREFIX lpPrefix, LPSTR lpBuffer ) {

if (lpPrefix->lpMem==&lpPrefix[1]) {

sprintf( lpBuffer, "%08lx ", lpPrefix ); if (lpPrefix->lpFilename) {

sprintf( lpBuffer+strlen(lpBuffer), "%12s %4ld ", lpPrefix->lpFilename,


lpPrefix->lLineNumber ); }
if (lpPrefix->lpClassDesc) {

sprintf( lpBuffer+strlen(lpBuffer), "%s", lpPrefix->lpClassDesc-


>lpVarName ); }

else {

strcpy( lpBuffer, "(bad)" ); }

} /* RenderDesc */

<b>Random number generator</b> /*pm-----------------------------------------


---------------------

* OUTLINE:

* This module implements a random number object.

* IMPLEMENTATION:

* The random numbers are implemented just like they are * in the
Microsoft C8 RTL.

* NOTES:
*

*--------------------------------------------------------------*/

#define USE_HRAND

#include "app.h"
USEWINASSERT

/*--- The random number class object ---*/

CLASS(hRand, HRAND) {

long lRand;

};

/*--- Implement random numbers just like the MS C8 RTL ---*/

#define NEXTRAND(l) (l*214013L+2531011L) #define FINALRAND(l)


((l>>16)&0x7FFF)

/*pf--------------------------------------------------------------

* DESCRIPTION: (Create Random Number Object) JLJ

* Given a seed, create a new random number generator object *

* ARGUMENTS:

* nSeed - Seed of the new random number generator *

* RETURNS:

*
* A new random number generator object *

*--------------------------------------------------------------*/

HRAND APIENTRY RandCreate( int nSeed ) {

HRAND hRand; NEWOBJ(hRand); hRand->lRand = nSeed; return


(hRand);

} /* RandCreate */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Destroy Random Number Object) JLJ

* Destroy the given random number generator object *

* ARGUMENTS:

* hRand - Random number generator object or NULL

* RETURNS:

* NULL

*
*--------------------------------------------------------------*/

HRAND APIENTRY RandDestroy( HRAND hRand ) {

VERIFYZ(hRand) {

FREE(hRand); }

return (NULL);

} /* RandDestroy */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Generate Next Random Number) JLJ

* Generate the next random number for the given random * number
generator object.

* ARGUMENTS:

* hRand - Random number generator object *

* RETURNS:

* The next random number


*

*--------------------------------------------------------------*/

int APIENTRY RandNext( HRAND hRand ) {

int nRand=0; VERIFY(hRand) {

hRand->lRand = NEXTRAND(hRand->lRand); nRand =


(int)FINALRAND(hRand->lRand); }

return(nRand);

} /* RandNext */

<b>A DOS interface</b>

/************************************************************
****/

/* */

/* (project name) */

/* */

/* Copyright (date) (Company Name). All rights reserved. */

/* */

/* This program contains the confidential trade secret */

/* information of (Company Name). Use, disclosure, or */

/* copying without written consent is strictly prohibited. */

/* */
/************************************************************
****/

/*pm--------------------------------------------------------------

* OUTLINE:

* This module provides access to the low-level file I/O

* functions of the standard Microsoft C run-time library.

* IMPLEMENTATION:

* This module is simply a code wrapper module.

* NOTES:

*--------------------------------------------------------------*/

#define USE_LOWIO

#define USE_HDOSFH
#include "app.h"
USEWINASSERT

/*--- The class object ---*/

CLASS(hDosFh, HDOSFH) {

int fh;

};

/*pf--------------------------------------------------------------

* DESCRIPTION: (Open File) JLJ

* Attempt to open a file

* ARGUMENTS:

* lpFilename - The name of the file to open *

* RETURNS:

* A file object handle or NULL if there was some error * in opening the
specified file.
*

*--------------------------------------------------------------*/

HDOSFH APIENTRY DosOpenFile( LPSTR lpFilename ) {

HDOSFH hDosFh=NULL; int fh=open(lpFilename,


_O_RDWR|_O_BINARY); if (fh!=-1) {

NEWOBJ(hDosFh); hDosFh->fh = fh; }

return (hDosFh);

} /* DosOpenFile */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Close File) JLJ

* Close a previously opened file

* ARGUMENTS:

* hDosFh - The file object or NULL

* RETURNS:
*

* NULL

*--------------------------------------------------------------*/

HDOSFH APIENTRY DosCloseFile( HDOSFH hDosFh ) {

VERIFYZ(hDosFh) {

int nResult=close(hDosFh->fh); WinAssert(!nResult); FREE(hDosFh); }

return (NULL);

} /* DosCloseFile */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Read File) JLJ

* Attempt to read a block of information from a file.

* ARGUMENTS:

*
* hDosFh - The file object

* lpMem - Pointer to memory buffer * wCount - Number of bytes to read


into the memory buffer *

* RETURNS:

* The number of bytes that were actually read *

*--------------------------------------------------------------*/

WORD APIENTRY DosRead( HDOSFH hDosFh, LPVOID lpMem,


WORD wCount ) {

WORD wNumRead=0;

VERIFY(hDosFh) {

wNumRead = (WORD)read(hDosFh->fh, lpMem, wCount); }

return (wNumRead);

} /* DosRead */

/*pf--------------------------------------------------------------

* DESCRIPTION: (Write File) JLJ

* Attempt to write a block of information to a file.


*

* ARGUMENTS:

* hDosFh - The file object

* lpMem - Pointer to memory buffer * wCount - Number of bytes to write


to the file *

* RETURNS:

* The number of bytes that were actually written *

*--------------------------------------------------------------*/

WORD APIENTRY DosWrite( HDOSFH hDosFh, LPVOID lpMem,


WORD wCount ) {

WORD wNumWritten=0;

VERIFY(hDosFh) {

wNumWritten = (WORD)write(hDosFh->fh, lpMem, wCount); }

return (wNumWritten);

} /* DosWrite */

<b>OutputDebugString() for MS-DOS programmers</b> /*pf----------------


----------------------------------------------

*
* DESCRIPTION: (Output String to Mono Screen) JLJ

* Scrolls the monochrome screen and places a new * string on the 25'th
line.

* ARGUMENTS:

* lpS - String to place on monochrome screen *

* RETURNS:

* (void)

*--------------------------------------------------------------*/

void APIENTRY OutputDebugString( LPSTR lpS ) {

LPSTR lpScreen=(LPSTR)0xB0000000; /* base of mono screen */

int nPos=0; /* for walking lpS string */

/*--- Scroll monochrome screen up one line ---*/

_fmemcpy( lpScreen, lpScreen+2*80, 2*80*24 ); /*--- Place new line


down in 25'th line ---*/

for (int loop=0; loop<80; ++loop) {


lpScreen[2*(80*24+loop)] = (lpS[nPos]?lpS[nPos++]:' '); }

} /* OutputDebugString */

<b>ReportWinAssert() for Windows</b> void APIENTRY


ReportWinAssert( LPSTR lpFilename, int nLine ) {

char buffer[100]; wsprintf( buffer, "WinAssert: %s %d", lpFilename,


nLine ); MessageBox( NULL, buffer, "WinAssert",
MB_OK|MB_SYSTEMMODAL );

} /* ReportWinAssert */

<b>ReportWinAssert() for C console apps</b> void APIENTRY


ReportWinAssert( LPSTR lpFilename, int nLine ) {

printf( "WinAssert: %s-%d (Press Enter) ", lpFilename, nLine ); while


('\n'!=getchar()) {

} /* ReportWinAssert */

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9
References Writing Bug-Free C Code

(1) Hummel, Robert L, Programmer's Technical Reference: The


Processor and Coprocessor. Ziff-Davis Press, Emeryville, CA. 1992. Has
an interesting chapter on bugs in the 80x86 chip family. A must for anyone
using 80x86 assembly in their programs.

(2) Kernighan, Brian W., and Dennis M. Ritchie.The C Programming


Language. Prentice Hall, Englewood Cliffs, NJ. Second Ed. 1988.

The bible for C programmers. It is written by the original designers of C


and it should be a part of every C programmer's library.

(3) Petzold, Charles. Programming Windows. Microsoft Press, Redmond,


WA.

Second Ed. 1990. A good introduction to Windows programming. If you


program in Windows, you need this book.

(4) Pietrek, Matt. Windows Internals. The Implementation of the


Windows Operating Environment. Addison-Wesley, Reading, MA. 1993.
A great book on the internals of Windows, giving insight into how
Windows works.

(5) Schulman, Andrew, David Maxey, and Matt Pietrek. Undocumented


Windows. A Programmer's Guide to Reserved Microsoft Windows API
Functions. Addison-Wesley, Reading, MA. 1992. A great book on the
undocumented internals of Windows, but it is not for the beginner or
fainthearted.

(6) Shaw, Richard Hale. "Based Pointers: Combining Far Pointer


Addressability and the Small Size of Near Pointers," Microsoft Systems
Journal, September 1990, p. 51. A useful article on the use of based
pointers.

(7) Stroustrup, Bjarne, and Margaret A. Ellis. The Annotated C++

Reference Manual. Addison-Wesley, Reading, MA. 1990. Known as the


ARM, this book is the ANSI base document for C++. A book for the
serious C/C++ programmer, it is filled with insightful commentary sections.

(8) Stroustrup, Bjarne. The Design and Evolution of C++.

Copyright © 1993-1995, 2002-2003 Jerry Jongerius


This book was previously published by Person Education, Inc.,
formerly known as Prentice Hall. ISBN: 0-13-183898-9

You might also like