Writing Bug-Free C Code
Writing Bug-Free C Code
} /* EmLimitText */
#ifdef USE_HOBJ
/*--------------------------------------------------------------
*-------------------------------------------------------------*/
/* (project name) */
/* */
/* */
/* */
/************************************************************
****/
* OUTLINE:
* IMPLEMENTATION:
* NOTES:
*
*--------------------------------------------------------------*/
#define USE_HDOSFH
#include "app.h"
USEWINASSERT
<b>Random number generator class declaration</b> CLASS(hRand,
HRAND) {
long lRand; };
* ARGUMENTS:
* ...
* RETURNS:
* NOTES:
*
* (optional notes section)
*--------------------------------------------------------------*/
} /* FunctionName */
} /* FunctionName */
#ifdef USE_LOWIO
/*--------------------------------------------------------------
*-------------------------------------------------------------*/
#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#endif
#ifdef USE_HDOSFH
/*--------------------------------------------------------------
*-------------------------------------------------------------*/
<b>HDOSFH module</b>
/************************************************************
****/
/* */
/* (project name) */
/* */
/* */
/* */
/************************************************************
****/
/*pm--------------------------------------------------------------
* OUTLINE:
* IMPLEMENTATION:
* NOTES:
*--------------------------------------------------------------*/
#define USE_LOWIO
#define USE_HDOSFH
#include "app.h"
USEWINASSERT
CLASS(hDosFh, HDOSFH) {
int fh; };
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* A file object handle or NULL if there was some error * in opening the
specified file.
*
*--------------------------------------------------------------*/
return (hDosFh);
} /* DosOpenFile */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* NULL
*
*--------------------------------------------------------------*/
VERIFYZ(hDosFh) {
return (NULL);
} /* DosCloseFile */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*
*--------------------------------------------------------------*/
return (wNumRead);
} /* DosRead */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
return (wNumWritten);
} /* DosWrite */
6.7.3 Commentary
Completeness. This module is not complete. There are other low-level I/O
functions that should be implemented.
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.
Book C Source Code (9k): source.zip (for DOS, Windows, UNIX, etc)
} NODE, *PNODE;
} NODEA, *PNODEA;
} NODEB, *PNODEB;
} NODEA;
} NODEB;
<b>The NEWHANDLE() macro</b> #define NEWHANDLE(Handle)
typedef struct tag##Handle *Handle
<br/>
} /* Testing */
long lRand; };
HRAND RandCreate( int nSeed )
} /* RandCreate */
} /* RandDestroy */
hRand->lRand = NEXTRAND(hRand->lRand);
return(FINALRAND(hRand->lRand));
} /* RandNext */
.
HRAND RandCreate( int nSeed )
<b>The _CD() macro, for use only in other macros</b> #define _CD(hObj)
hObj##_ClassDesc
<br/>
long lRand; };
<i>or</i>
VERIFY(hObject) {
(block of code) }
<br/>
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-4], ax ;; test class desc offset jne done
hObj = FmNew(sizeof(*hObj),&_CD(hObj),__FILE__,__LINE__))
#define FREE(hObj) hObj = FmFree(hObj)
<b>_LPV() macro</b>
(_LPV(hObj)=FmNew(sizeof(*hObj),&_CD(hObj),szSRCFILE,__LINE
__)) #define FREE(hObj) (_LPV(hObj)=FmFree(hObj))
VERIFY(hObject) {
(body of code) }
} /* Method */
return (var)
} /* Method */
<b>Fault-tolerant RandNext() method function</b> int APIENTRY
RandNext( HRAND hRand ) {
return(nRand);
} /* RandNext */
CLASS(hRand, HRAND) {
long lRand; };
} /* RandCreate */
VERIFYZ(hRand) {
FREE(hRand); }
return (NULL);
} /* RandDestroy */
} /* RandNext */
The CLASS() macro is used in the source file that implements the HRAND
class object, not in an include file.
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.
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.
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.
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.
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.
LOOP(3) {
/* Testing */
<br/>
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
} ENDLOOP
NewScope {
} /* Function */
#include <stdio.h>
#include <string.h>
int main(void)
TranslateMessage((LPMSG)&msg); DispatchMessage((LPMSG)&msg);
}
return (nSpeed);
} /* GetCpuSpeed */
int nSpeed1=GetCpuSpeed(NULL);
int nSpeed2=GetCpuSpeed(pBuffer);
...
...
if (test1) {
(more code) if (test2) {
(more code) }
} /* DeepNestFunction */
if (bVar) {
...
} /* UnrollingDeepNesting */
CSCHAR TextOfMonths[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"
};
return (TextOfMonths[nMonth]);
} /* GetTextOfMonth */
CSCHAR TextOfMonths[12][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct",
"Nov", "Dec"
};
lpMonth = TextOfMonths[nMonth]; }
return (lpMonth);
} /* GetTextOfMonth */
} /* OutputDebugString */
*(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.
#include <stdio.h>
int main(void)
} /* main */
if (fh!=-1) {
close(fh); }
#define FASTCALL
#define PASCAL
#define EXPORT
#define BASEDIN(seg)
#ifdef __cplusplus
#else
#define EXTERNC
#endif
<br/>
(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); \ }
WinAssert(nValue>0) {
...
} /* TestingWinAssert */
<br/>
WinAssert(x>0);
<br/>
<br/>
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.
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).
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.
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.
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().
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.
char buffer[BUFFER_SIZE];
...
CompilerAssert(ISPOWER2(sizeof(buffer))); ...
int main(void)
long lVal;
CompilerAssert(sizeof(lVal)==0); return 0;
} /* main */
<br/><br/>
^^^^
4213
^^^^^^
642135
86125347
int main(void)
x x;
return 0;
} /* main */
<b>CSCHAR define</b>
<b>Using CSCHAR</b>
CSCHAR szFile[]=__FILE__;
...
OutputName(szFile);
<b>Sample code</b>
#include <stdio.h>
int main(void)
} /* main */
<br/>
int main(void)
{
printf( "This is an example of using C's ability to\n"
} /* main */
#define PRINTD(var) printf( "Variable " #var "=%d\n", var ) int main(void)
} /* main */
<br/>
<b>Printf example</b>
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
if (test2) {
...
else {
...
if (test2) {
...
else {
...
}
<b>strncpy() problem, but code may still work</b> #include <stdio.h>
#include <string.h>
int main(void)
} /* main */
(code body) }
<b>De Morgans laws</b> 1. !(a && b) == (!a || !b) 2. !(a || b) == (!a &&
!b)
<b>A macro that needs a scope, has problems</b> #define POW3(x,y) int
i; for(i=0; i<y; ++i) {x*=3;}
WinAssert(expression);
WinAssert(expression) {
(block of code) }
LLOOP(10) {
printf( "%ld\n", lLoop ); } ENDLOOP
<b>C++ loop code, with variable declaration problems</b> for (int loop=0;
loop<10; ++loop) {
...
...
...
} /* main */
<br/>
<b>Desired output</b>
array size=100
CSCHAR szPE7060[]="coords";
...
static struct {
cl -Tctest.i
<br/>
cl -Tptest.i
3. x ^= y;
Note to this online book: On April 29, 2002, I reacquired the publishing
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.
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.
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?
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.
"A New Heap Manager." The heap manager that comes with C does not
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.
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.
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.
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.
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.
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.
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++.
If you have any comments on this book, I would like to hear them. I can be
reached through the Internet at
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 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.
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.
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 );
int main(void)
free(pMem);
return 0;
} /* main */
} PREFIX;
CompilerAssert(ISPOWER2(ALIGNMENT));
CompilerAssert(!(sizeof(PREFIX)%ALIGNMENT));
else {
} /* FmNew */
if (lpHeapHead) {
else {
lpHeapHead = lpAdd;
} /* AddToLinkedList */
<b>FmFree() function</b>
if (VerifyHeapPointer(lpMem)) {
return (NULL);
} /* FmFree */
if (lpRemove==lpHeapHead) {
} /* RemoveFromLinkedList */
BOOL bOk=FALSE;
if (lpMem) {
WinAssert(FmIsPtrOk(lpMem)) {
WinAssert(lpPrefix->lpPostfix->lpPrefix==lpPrefix) {
bOk = TRUE;
return (bOk);
} /* VerifyHeapPointer */
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 */
} /* FmIsPtrOk */
<b>FmWalkHeap() function</b>
if (lpHeapHead) {
break;
} /* FmWalkHeap */
<b>RenderDesc() function</b>
if (lpPrefix->lpMem==&lpPrefix[1]) {
if (lpPrefix->lpClassDesc) {
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>
return(lpReturn);
} /* FmStrDup */
<b>NEWARRAY macro</b>
<b>SIZEARRAY Macro</b>
(_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).
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.
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!
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.
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.
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.
int x; // the x coordinate of the point int y; // the y coordinate of the point
} POINT;
int main(void)
} POINT;
int main(void)
#include <stdio.h>
int main(void)
while (safegets(buffer,sizeof(buffer))) {
++lLineCount; }
} /* main */
* ARGUMENTS:
* RETURNS:
* NOTES:
*
* (optional notes section) *
*--------------------------------------------------------------*/
} /* FunctionName */
if (expression) {
statement; ...
if (expression) {
statement; ...
statement; ...
while (expression) {
statement; ...
do {
statement; ...
} while (expression);
statement; ...
switch (expression) {
statement; ...
break; }
break; }
...
} /* Testing */
<br/>
...
} /* Testing */
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.
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().
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.
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.
correctly, the what can stay the same and the how can change drastically.
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.
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.
#include "book.h"
NEWHANDLE(HRAND);
NEWHANDLE(HDOSFH);
#ifdef USE_HRAND
/*----------------------------------------------------------------
*
*--------------------------------------------------------------*/
#ifdef USE_LOWIO
/*--------------------------------------------------------------
*-------------------------------------------------------------*/
#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
*-------------------------------------------------------------*/
<b>BOOK.H</b>
#else
#define FAR
#define NEAR
#define FASTCALL
#define PASCAL
#define EXPORT
#define BASEDIN(seg)
#endif
#ifndef _WINDOWS
#endif
#ifdef __cplusplus
#define TYPEDEF
#else
#endif
#ifdef __cplusplus
#else
#define EXTERNC
#endif
#ifdef _WINDLL
#else
typedef struct {
#define CLASS(hObj,Handle) \
(_LPV(hObj)=FmNew(sizeof(*hObj),&_CD(hObj),szSRCFILE,__LINE
__)) #define FREE(hObj) (_LPV(hObj)=FmFree(hObj))
#define NEWSTRING(lpDst,wSize) \
(_LPV(lpDst)=FmNew((SIZET)(wSize),NULL,szSRCFILE,__LINE__))
#define MYLSTRDUP(lpDst,lpSrc) \
(_LPV(lpDst)=FmStrDup(lpSrc,szSRCFILE,__LINE__))
(_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:
* 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:
*--------------------------------------------------------------*/
#include "app.h"
USEWINASSERT
} PREFIX;
/*--- Postfix structure after every heap object ---*/
CompilerAssert(!(sizeof(PREFIX)%ALIGNMENT));
/*pf--------------------------------------------------------------
* 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:
*--------------------------------------------------------------*/
else {
} /* FmNew */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* NULL
*--------------------------------------------------------------*/
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--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
LPVOID APIENTRY FmStrDup( LPSTR lpS, LPSTR lpFile, int nLine ) {
LPVOID lpReturn=NULL;
if (lpS) {
return(lpReturn);
} /* FmStrDup */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
LPVOID lpNew=NULL;
if (lpOld) {
if (VerifyHeapPointer(lpOld)) {
AssertError; }
else {
return(lpNew);
} /* FmRealloc */
/*pf--------------------------------------------------------------
*
* Display a symbolic dump of the heap by walking the * heap and
displaying all objects in the heap.
* ARGUMENTS:
* (void)
* RETURNS:
* (void)
*--------------------------------------------------------------*/
if (lpHeapHead) {
char buffer[100]; RenderDesc( lpCur, buffer ); /*--- print out buffer ---*/
break;
}
} /* FmWalkHeap */
/*p---------------------------------------------------------------
* Add the given heap object into the doubly linked list * of heap objects.
* ARGUMENTS:
* RETURNS:
* (void)
*--------------------------------------------------------------*/
void LOCAL AddToLinkedList( LPPREFIX lpAdd ) {
if (lpHeapHead) {
else {
lpHeapHead = lpAdd;
} /* AddToLinkedList */
/*p---------------------------------------------------------------
*
* Remove the given heap object from the doubly linked list * of heap
objects.
* ARGUMENTS:
* RETURNS:
* (void)
*--------------------------------------------------------------*/
if (lpRemove==lpHeapHead) {
/*p---------------------------------------------------------------
* Verify that a pointer points into that heap to a valid * object in the heap.
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
BOOL bOk=FALSE;
if (lpMem) {
WinAssert(FmIsPtrOk(lpMem)) {
WinAssert(lpPrefix->lpPostfix->lpPrefix==lpPrefix) {
bOk = TRUE;
return (bOk);
} /* VerifyHeapPointer */
/*pf--------------------------------------------------------------
* Does the given memory pointer point anywhere into * the heap.
* ARGUMENTS:
*
* lpMem - Heap pointer to check
* RETURNS:
*--------------------------------------------------------------*/
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
} /* FmIsPtrOk */
#endif
/*p---------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* (void)
*--------------------------------------------------------------*/
if (lpPrefix->lpMem==&lpPrefix[1]) {
else {
} /* RenderDesc */
* OUTLINE:
* IMPLEMENTATION:
* The random numbers are implemented just like they are * in the
Microsoft C8 RTL.
* NOTES:
*
*--------------------------------------------------------------*/
#define USE_HRAND
#include "app.h"
USEWINASSERT
CLASS(hRand, HRAND) {
long lRand;
};
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*
* A new random number generator object *
*--------------------------------------------------------------*/
} /* RandCreate */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* NULL
*
*--------------------------------------------------------------*/
VERIFYZ(hRand) {
FREE(hRand); }
return (NULL);
} /* RandDestroy */
/*pf--------------------------------------------------------------
* Generate the next random number for the given random * number
generator object.
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
return(nRand);
} /* RandNext */
/************************************************************
****/
/* */
/* (project name) */
/* */
/* */
/* */
/************************************************************
****/
/*pm--------------------------------------------------------------
* OUTLINE:
* IMPLEMENTATION:
* NOTES:
*--------------------------------------------------------------*/
#define USE_LOWIO
#define USE_HDOSFH
#include "app.h"
USEWINASSERT
CLASS(hDosFh, HDOSFH) {
int fh;
};
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
* A file object handle or NULL if there was some error * in opening the
specified file.
*
*--------------------------------------------------------------*/
return (hDosFh);
} /* DosOpenFile */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*
* NULL
*--------------------------------------------------------------*/
VERIFYZ(hDosFh) {
return (NULL);
} /* DosCloseFile */
/*pf--------------------------------------------------------------
* ARGUMENTS:
*
* hDosFh - The file object
* RETURNS:
*--------------------------------------------------------------*/
WORD wNumRead=0;
VERIFY(hDosFh) {
return (wNumRead);
} /* DosRead */
/*pf--------------------------------------------------------------
* ARGUMENTS:
* RETURNS:
*--------------------------------------------------------------*/
WORD wNumWritten=0;
VERIFY(hDosFh) {
return (wNumWritten);
} /* DosWrite */
*
* DESCRIPTION: (Output String to Mono Screen) JLJ
* Scrolls the monochrome screen and places a new * string on the 25'th
line.
* ARGUMENTS:
* RETURNS:
* (void)
*--------------------------------------------------------------*/
} /* OutputDebugString */
} /* ReportWinAssert */
} /* ReportWinAssert */