PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 1/15 TITLE : Memory Corruption POINTERS: A pointer is a memory location that holds a memory address as its contents. When you declare a pointer (e.g. int *foo) the compiler will allocate the necessary space to hold the appropriate memory address. If the pointer is declared globally the value of its address will be 0000 in the case of a near pointer and 0000:0000 in the case of a far pointer. If the pointer is declared inside a function definition as an auto variable (default), then it is created on the stack and will have a default address of whatever value happened to be at that location on the stack when it is created. In either case these default memory addresses are invalid. This is referred to as an uninitialized pointer and should never be dereferenced. To initialize a pointer you must either dynamically allocate memory using malloc, farmalloc, calloc, farcalloc, realloc, farrealloc, allocmem or new (C++ only) or you can associate the pointer with a variable who's memory is allocated by the compiler at compile time (e.g. int foo[100]). Once the pointer has been initialized it is then safe to dereference the pointer using the * operator. int *p; OK Uninitialized pointer *p = 4; WRONG This puts a value of 4 at whatever address happened to be stored where *p was created. int i; OK Statically declared variable. Static meaning the memory is allocated by the compiler at compile time. p = &i; OK Associating p with a statically declared variable. p = (int *) malloc(2); OK Associating p with dynamically allocated memory. The parameter could also have been sizeof(int). Modifying the variable p (e.g. p = 0;) will make the memory address that p holds equal to 0. When modifying dereferenced p, denoted by *p, the value stored at the memory address p holds as its value is modified. *p = 4; OK p still holds the address that malloc returned above, but that address in memory now holds a value of 4. PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 2/15 TITLE : Memory Corruption p = 4; OK *p now points to offset 4 from DS. It is important to note that this type of assignment should only be made if the address (4 in this case) is valid. Otherwise memory corruption will occur. Also note that if a far memory model (see Memory Models) is used the segment may not be DS, but will be whatever its initial value was. There are 3 types of pointers. They are near, far, and huge. Near pointers have a size of 2 bytes. They only store the offset of the address the pointer is referencing. An address consisting of only an offset has a range of 0 - 64K bytes starting from the beginning of DGROUP. A near pointer can be incremented and decremented using arithmetic operators (+, -, ++, and --) through the entire address range. Any attempt to increment a near pointer that has a value of 64K (0xffff) will result in a value of 0. This is referred to as wrapping the pointer. A corresponding result can be expected when attempting to decrement a pointer that contains an address of 0, except the result will be 64K instead of 0. In addition to being incremented and decremented, near pointers can be compared to one another using relational operators ( <, >, ==, >= and <= ). Far pointers have a size of 4 bytes. They store both the segment and the offset of the address the pointer is referencing. A far pointer has an address range of 0 - 1M bytes. It is important to understand that an addressing range of 1M does not remove the 640K barrier from the program. It means that the pointer can address the upper memory area (641 - 1M) which typically contains video memory, ROM and anything else that may be loaded high. A far pointer can be incremented and decremented using arithmetic operators. When a far pointer is incremented or decremented ONLY the offset of the pointer is actually incremented or decremented. The segment is never incremented by the arithmetic operators. This means that although a far pointer can address up to 1Mb of memory, it can only be incremented through 64Kb and the offset will start at zero again without changing the value of the segment. This is referred to as "wrapping" the pointer (e.g. 0F3E:FFFF + 1 = 0F3E:0000). When a far pointer is decremented from zero it will wrap the other way and become 64K. Far pointers are not unique. It is possible to have two far memory addresses PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 3/15 TITLE : Memory Corruption that have different segment values and different offset values that address the same memory location e.g. 0777:2222 has an absolute address of 07770 + 2222 = 09992 and 0999:0002 has an absolute address of 09990 + 0002 = 09992. When relational operators are used on far pointers only the offsets are compared. For example: if we let a = 0777:2222 and let b = 0999:0002 then a == b would return false because this is equivalent to 2222 == 0002 which is in fact false. In other words relational operators will only work on far pointers if the segment values of the pointers being compared are the same. Huge pointers have a size of 4 bytes. They store both the segment and the offset of the address the pointer is referencing. A huge pointer has an address range of 0 - 1M bytes. A huge pointer can be incremented and decremented using arithmetic operators. The only difference between a far pointer and a huge pointer is that a huge pointer is normalized by the compiler. A normalized pointer is one that has as much of the address as possible in the segment, meaning that the offset is never larger than 15. A huge pointer is normalized only when pointer arithmetic is performed on it. It is not normalized when an assignment is made. You can cause it to be normalized without changing the value by incrementing and then decrementing it. The offset must be less than 16 because the segment can represent any value greater than or equal to 16 (e.g. Absolute address 0x17 in a normalized form would be 0001:0001. While a far pointer could address the absolute address 0x17 with 0000:0017, this is not a valid huge (normalized) pointer because the offset is greater than 0000F.). Huge pointers can also be incremented and decremented using arithmetic operators, but since they are normalized they will not wrap like far pointers. Huge pointers can be reliably used with relational operators because they are normalized. This works because normalization of huge pointers insures that every huge pointer is unique. It is important to understand that huge pointers are never the default pointer, even in the huge memory model. MEMORY MODELS: The important difference between the memory models are the size of the data and code pointers, number of data and code segments and the number and type of heaps available. For our purposes we will refer to the tiny, small and medium memory models as near PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 4/15 TITLE : Memory Corruption memory models and the compact, large and huge as far memory models. We use this notation because the near memory models have both a near and far heap while the far memory models have only a far heap. The near memory models do not have a separate stack segment like the far memory models do. This is because the data segment, the near heap and the stack are all part of DGROUP meaning that the total size of these things must be less than or equal to 64K bytes. The tiny model is an exception to this in that it also includes the code segment and psp (256 bytes) in DGROUP also. TINY SML MED CMP LRG HUGE Near Heap : Yes Yes Yes No No No Far Heap : Yes Yes Yes Yes Yes Yes Code pointers : near near far near far far Data pointers : near near near far far far Separate stack segment: No No No Yes Yes Yes Multiple code segments: No No Yes No Yes Yes Multiple data segments: No No No No No Yes The near and far heap fields above indicate whether or not those heaps exist in the specified memory model. If a memory model is used that doesn't contain a near heap and one or more of the near heap functions i.e. malloc, free, heapwalk etc. are used, they are mapped by the compiler into a special far version of the call that will allocate the memory off the correct heap (far). All of the parameters of these special near memory allocation functions are the same, but the pointer returned by this special version of the near memory allocation function will be a far pointer rather than a near pointer. The code and data pointer fields show the default pointer size for each memory model. It is important to note that none of the memory models use huge pointers by default. These defaults can all be overridden using the modifiers near, far or huge (e.g. int far *foo; or void near goo();). The separate stack segment field indicates whether the stack is part of DGROUP or in its own segment. Multiple code and data segments indicate if the specified memory model can have more than one of each type. If more than one of either type is allowed the segments are divided based on their corresponding source files e.g. a 3 source file program in the huge memory model would have 3 code segments and 3 data segments where the code and global PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 5/15 TITLE : Memory Corruption data declared in each source file would go into the corresponding segment. HEAP: The global variable _heaplen only applies to the near heap. It is used to specify the size of the heap. If no value is specified for _heaplen then DGROUP will default to 64K bytes. If a value is specified then the size of DGROUP will be computed by summing the size of everything in DGROUP and then adding the size of _heaplen and _stklen. The global variable _stklen is used to specify how much memory to set aside for the stack. The near heap, when present, begins just after the global data and grows up toward the stack (higher memory) and the stack is situated at the end of DGROUP and grows down toward the heap (lower memory). The empty space separating the near heap and the stack can be used by either the heap or the stack regardless of whether it was reserved using _heaplen or _stklen variables. It is possible to dynamically allocate memory that is actually being used by the stack without generating a warning or an error. This is a common cause of memory corruption and can be avoided by using coreleft to keep track of the available memory to insure it is available prior to allocating it. The far heap exists just above the stack in memory. The far heap's initial size is zero. When the program processes a request for memory from the far heap it requests DOS to reallocate the current program size (minimum of 16 byte chunks) to include the requested size. DOS maintains its own heap very similar to the heap used by a program. Each block of memory that DOS allocates has a header called a memory control block (MCB) which is used to manage the heap used by DOS. When there is 1K bytes of free memory at the top of the far heap the program again calls DOS to reallocate the program space giving the memory back to DOS. All pointers used to reference the far heap should be either far or huge since the far heap is in a separate segment. It is important to note that if another process requests memory from DOS and the memory is available then that other program will get the next free block DOS owns which is the one immediately following the far heap in memory. This means that the next time your program attempts to reallocate the current program size the DOS allocate function will fail because your program has now been blocked in PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 6/15 TITLE : Memory Corruption by a DOS memory control block (MCB) other than your program MCB. This is also the case if allocmem is called from within your program. STACK: Stack checking is an option that can be turned on or off that will check to see if the stack has overflowed. When this option is turned on the compiler will generate code that will check, upon entering a new function, to insure there is adequate room on the stack to make the call. If there isn't room the appropriate error message will be generated and the program will exit. The checking is done in the near memory models by comparing __brklvl that marks the end of the near heap and SP which marks the actual top of the stack. In the far memory models this checking is done by comparing SP which marks the actual top of the stack and _stklen which is the size of the stack segment. Stack checking is not fool proof. The Run Time Library (RTL) of functions found in the Library Reference Manual were not compiled with stack checking turned on. This means there is a possibility of overflowing the stack when making a call to an RTL function even though stack checking is turned on. Functions that use a stack other than the program stack should not use the stack checking option (e.g asynchronous interrupt service routines). When floating point is turned on in any memory model the floating point emulator information is located in the first 416 bytes of the physical stack (SS:0000 - SS::415). This information must be transferred if you are trying to switch stacks. Receiving false floating point error messages is a good indication that something may be corrupting this portion of the stack. The size of the stack is determined by the startup code at run time. If a link map is generated you will see a size specified for the stack that may be different than the size you specified. This value you see in the link map is a value that DOS is told, but the actual size requirement is met at runtime. In the near memory models the stack exists within the 64K byte limit defined by DGROUP which also contains global data and the near heap. In the tiny memory model it also contains the code segment. In the near memory when the stack overflows the first thing corrupted is the near heap followed by the global data. PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 7/15 TITLE : Memory Corruption In the far memory models the stack exists immediately following the data segment and is just before the far heap. In the far memory models the first thing that is corrupted when the stack overflows is the floating point emulator followed by the far heap. In order to avoid stack overflow it is important to understand what makes up the stack. In the far memory model only, the emulator occupies the 416 bytes from SS:0000 to SS:0415 located at the top of the logical stack. As each function is entered space is allocated for all its parameters. They are created on the stack from right to left followed by the return address. Space is then allocated for all auto (default for local variables) variables in the order of declaration. All this space remains allocated until the function returns to the calling function. In other words if you then called another function, the space allocated for the first function would remain allocated until the first function regained control and then returned control to its calling function. In the case of C++ programs, all class copies used by the compiler are implicitly created on the stack unless a copy constructor is provided that uses the alternative of dynamic memory allocation. COMMON PROBLEMS LEADING TO MEMORY CORRUPTION: Using an uninitialized pointer is probably the most common cause of memory corruption. It is possible to use an uninitialized pointer and have a program work. It just depends on what the default address is when the pointer is created and if anything else is attempting to use that address. Failure to include ALLOC.H is a common mistake. When ALLOC.H is left out the type checking is not performed. Since there is no prototype the memory allocation functions are all thought by the compiler to be returning integer types. A program will usually continue to work without ALLOC.H in the near memory models, since an integer is the same size as the near pointers used in these memory models. However in the far memory models the segment half of the pointer is lost when the memory allocation function attempts to return a far pointer. These programs may or may not work. You should always include ALLOC.H. PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 8/15 TITLE : Memory Corruption Failure to check the return value from a memory allocation call can result in a NULL pointer. Technically a NULL pointer is defined as an invalid pointer. In more practical use, however, a NULL pointer is interpreted as meaning a pointer that holds 0 as its address (e.g. p = 0;). NULL for our purposes will mean a pointer that holds an address of 0. A memory allocation function can fail and return NULL if there is no memory left, if the program was unable to resize its MCB or if there is some sort of heap corruption (whether it be in the DOS heap or your program heap). Your code should always check to insure the pointer returned by a memory allocation function is not NULL. Stack overflow is a common problem. When making a function call the stack can overflow and corrupt some other variables and return without the user being the wiser. A later attempt to use the corrupted variable can result in incorrect program results or hanging the system. Indexing out of bounds occurs with both statically declared arrays and dynamically declared arrays. An array declared as char foo[100] has a valid index range of 0 - 99. Making an assignment to foo[100] is memory corruption since foo[100] does not belong to the array foo. A non-huge array will wrap if a value larger than unsigned (0xffff) is used to index it. The largest dynamically allocated array that can be addressed using a far pointer is 65531 bytes because the offset returned by farmalloc will always have an offset of 0004. This offset is guaranteed because the far heap is paragraph aligned. The missing 4 bytes are used by the program heap manager as a block header to manage the heap. Of course a far pointer has the potential to rival the power available with the huge pointers if the user does the necessary normalizing of the pointer. In a far memory model, a call to a near memory function will be mapped into the corresponding special function call described above in the heap section. In the near memory models all pointers declared are near by default and should not be used with any of the far memory allocation functions i.e. farmalloc and farcalloc. The compiler will generate a warning about a suspicious pointer conversion but it won't prevent it from happening. The first time such a pointer is used it will address an offset of 0004 in the DGROUP. Also be aware of the memory model that the program is PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 9/15 TITLE : Memory Corruption compiled in. If it is one of the far memory models then all calls to the near memory allocation functions will be mapped into the corresponding far functions behind the scenes by the compiler. In some cases this also changes the parameter and return types. It is strongly suggested that if you are using a far memory model you use only the far memory allocation functions to avoid confusion at a later date and make debugging easier. The duration of a variable is very important. A global pointer should never reference a local auto variable, because when that local variable goes out of scope its memory is released to be reused by the stack for something else. If the global pointer is later used it may be erroneously referencing something else. Receiving the message "Null pointer assignment" after your program has completed its run is usually caused by the use of an uninitialized pointer. You can track the cause of this message down by placing the two watches ( (char *)4,s and *(char *) 0,4m ) on your program. The first 47 bytes of the data segment are not valid addresses for any variables in your program. When your program exits these 47 bytes are checked to see if they have been modified. If they have the warning message is printed on your screen. These two watches will allow you to monitor the beginning of the data segment to identify the offending line of code. The memory allocation functions used to free dynamically allocated memory (free, farfree, delete) should only be used on pointers that hold an address returned by a dynamic memory allocation function i.e. farmalloc, malloc, new etc.. Any attempt to free an array declared as int foo[100] will result in undefined results possibly corrupting memory. The memory freeing functions should never be called twice with the same address as a parameter unless that same address has been reallocated since the last memory free function call. The results are undefined and can produce memory corruption. One very common problem is character arrays that are not NULL terminated. Every character array must be terminated by a NULL or 0 i.e. char foo[100]; foo[0] = 0; This is a NULL terminated "empty" string PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 10/15 TITLE : Memory Corruption foo[10] = '\0';This is a NULL terminated string with 10 undefined characters preceding the NULL. Remember indexing starts at 0 not 1. foo[0] = '0'; Incorrect this is not a NULL terminated string. All of the functions that are listed in string.h found in the include directory rely on this NULL termination. The NULL is what they use to tell them they have reached the end of the string and should stop processing the string. If the NULL is missing then these functions will continue on processing possibly corrupting memory as they go. For most of the string family of functions list in string.h you will see a corresponding n family of functions i.e. strcpy and strncpy, stricmp and strnicmp, strcat and strncat etc.. The corresponding n functions perform the same tasks as their corresponding counter parts with the exception that they take an extra parameter n which specifies how many characters in the string to operate on. These n functions will be finished when they have processed the string up to the NULL character or until they have processed n characters in the string. It is strongly recommended that these n family of functions be used rather than their counterparts because if they do receive a string that is not NULL terminated they will stop after they have processed n characters rather than continuing on and corrupting memory. You will also see an _f family of functions in string.h that have corresponding standard and n family of functions i.e. strcat and _fstrcat; strchr and _fstrchr; strncmp and _fstrncmp etc.. The _f family of functions is not limited to the string family of functions and will be addressed later. In general most functions that work with pointers have a corresponding _f family of functions. The _f family of functions were designed for use exclusively in the small and medium memory models because they are the only memory models that have both a near and a far heap. The _f family of functions are designed for use with far pointers in the small and medium memory models. They are not necessary in the far memory models because those RTL functions have already been compiled using the default far pointers as parameter and return types. Near pointers present no special problems in the far memory models because they can be implicitly converted by the compiler. PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 11/15 TITLE : Memory Corruption The C++ language presents some special problems due to constructors and destructors which are called by the compiler automatically. The first rule is that for every constructor call there must be a corresponding destructor call. If no dynamic memory allocation and deallocation is taking place in these constructors and destructors one may not even notice if the destructor gets called twice, but if it is, the heap will most likely become corrupted. One can check for violations of this property by placing simple print statements or breakpoints (if using a debugger) in each of the constructors and destructors and check for compliance. The stack size variable should never be set larger than 0xffe0. The startup code will increase this value to 0xfff0 as part of the process to make the stack paragraph aligned. If _stklen is set to a value greater than 0xffef then the value of stklen will become 0 in the startup code causing all stack checking to fail. If dynamic memory allocation is used in a constructor in C++ then a copy constructor is almost always required. A copy constructor is a constructor that will dynamically allocate the needed memory and then copy what is stored in the corresponding memory in the class instance being copied. The compiler will often times need to generate temporaries of the class instances e.g. when an instance is being passed as a parameter. If a copy constructor is not defined it will use the default memberwise copy resulting in two different pointers in two different class instances pointing to the same dynamically allocated memory. This means that when the first instance is destructed the memory addressed by its pointer is freed leaving the remaining instance of the class with an invalid pointer that, if used, will cause memory corruption. If a copy constructor is defined the compiler will use it versus the default copy constructor. DEBUGGING TIPS AND TECHNIQUES: Once you are sure that the problem is not one of the common mistakes listed above you roll up your sleeves and prepare for a real debugging session. The real trick to debugging is to understand how memory works and how a program is structured in that memory. If you understand these things then you can use things like different size pointers and different segment organization to debug your program. Switching memory models makes PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 12/15 TITLE : Memory Corruption all these things happen. The hard part is knowing which one to switch to and what to look for when you get there. In some cases you may not be able switch memory models due to program constraints and may be forced to use other techniques. When none of the simpler tricks such as changing memory models works, then the divide and conquer technique should be used. The divide and conquer method is the most reliable method, but it is often the most difficult to implement. A good technique to employ in your programming is to initialize all pointers to NULL when they are declared and again after they have been freed. This way all invalid pointers will contain the same address and can be tested prior to use and prior to being freed. All compiler warnings should be turned on. It is important to note that the compiler isn't shipped with all the warnings turned on. Inside the IDE you have to turn each warning on explicitly and on the command line you should give the -w+ option. You can find more information about how to turn these on in the User's Guide. In a near memory model the stack and the near heap grow towards each other in memory. It is possible for either one to overstep its bounds and corrupt the other. This condition is easy to test for by moving to a far memory model and then increase the stack size to 0xffe0. If the program then behaves differently this is a strong indication that one or the other of these conditions existed. If this is the case, there are two possible solutions. The first is to use the far memory allocation functions and the second is to stay in the far memory model and adjust the stack size as necessary. In the far memory models the stack exists directly below the far heap in memory. As the stack grows the stack pointer SP approaches zero. When the stack overflows SP jumps from 0 to 0xffffh. If the stack has been declared to be 0xffe0 then the beginning of the logical stack will be corrupted else the far heap at a point exactly 64k higher in memory from SS will be corrupted. Note if you are using floating point the emulator located on the top of the logical stack will become corrupted before SP even wraps. This could result in a program running OK, but giving incorrect results from computations or aborting the PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 13/15 TITLE : Memory Corruption program. The stack size can be increased to oxffe0 by the line "extern unsigned _stklen = 0xffe0u;" at file scope (outside any function definition) inside any source file being linked into your program. A common mistake in writing an overloaded operator is to allocate a new instance of a class, calculate the new value of this instance, and return the pointer to this new instance. This will work, but the compiler has no way of knowing that it should free up the new data after it has finished with it. A program may work "perfectly" on small amounts of data, but crash after it has exhausted most of the available memory. This is called a "memory leak." Overloading NEW and DELETE to keep a log of used/freed memory will assist in tracking these leaks. When debugging a live program, it is easy to display and verify an integer, string or other simple data structure. Verification of more complex structures may involve several layers of pointer indirection. It may be tedious to examine all elements in a linked list for example. If you write functions to check your structures/classes, these can be called within your program at debug time. For instances of classes, these should be virtual member functions, so that the correct routine is called for instances of derived classes accessed through base class pointers. Call these functions at the start and end of all other member functions to flag corrupted data and assist in isolating problems. The divide and conquer method is implemented by manipulating the different components composing the program. These components will most likely be single functions or groups of functions that lend themselves to being individually tested by a driver program. A driver program is a small program used to test a particular module or group of modules. It should set up the environment and pass the parameters required by the function being tested. Sometimes it may be more desirable to test an entire program without a particular function. This can be done by using a stub. A stub is a testing function designed to take the same parameters and return the same type as the function it is replacing. Once the offending function has PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 14/15 TITLE : Memory Corruption been located the offending line of code must be located by commenting out and changing various lines within the function. When you have identified what you suspect as being the offending line of code you need to confirm this. This is done by writing a separated test program to test the line of code all by itself. If it still doesn't work correctly then you have found the problem. If it works in the example program then you have most likely found a side effect caused by something else that must be identified. For instance something else may modify the value of a pointer that, when used by the suspect code, causes the machine to hang. Enough information should be obtained from the code producing the side effect to give you a new place to look. In our example we would watch the address of the pointer that got corrupted by using Turbo Debugger and setting a hardware breakpoint on the address of the pointer that would stop program execution whenever the pointer value is modified. It is rare that an identified problem needs more than 20 lines of code to reproduce. Once you feel that you have isolated it you should ask yourself whether every piece of code in your example is necessary to reproduce the problem. For instance if the problem requires a structure to manifest itself then what the structure contains is probably not important. The divide portion of the algorithm is usually the most difficult and the most time consuming. Now the you have located the problem you can usually determine what the cause was. If you know what the cause of the problem was you should be able to summarize the problem in a few sentences. If you cannot do this you probably don't have a complete grasp of the problem. Borland Technical Support will be able to provide you with the best support when you have the problem isolated and have reproduced it in an small example, whether you are just letting us know the problem is there or you're not sure what is causing the problem. A function familiar to C programmers is the assert() macro. This function is well suited for C++ programming. Assert() takes an expression as a parameter, and if the expression is false, usually halts the program and prints a message giving the line number and file where the problem occurred. At any point where an important assumption is made about the correctness of calculated PRODUCT : Borland C++ NUMBER : 738 VERSION : All OS : DOS DATE : October 19, 1993 PAGE : 15/15 TITLE : Memory Corruption data, insert an assert() call, which will display a message if the assumption is invalid. For example, the default for a switch statement is an ideal place to insert an assert() call. Insure that you include ASSERT.H. Defining the symbol "NDEBUG" will cause the compiler to ignore (i.e. not generate code for) all assert() calls. This way the source code will not have to be modified to remove the excess debugging code. DISCLAIMER: You have the right to use this technical information subject to the terms of the No-Nonsense License Statement that you received with the Borland product to which this information pertains.