Category : C++ Source Code
Archive   : MEMCORRP.ZIP
Filename : TI738.ASC
Output of file : TI738.ASC contained in archive : MEMCORRP.ZIP
PRODUCT : Borland C++ NUMBER : 738
VERSION : All
OS : DOS
DATE : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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 : February 25, 1992 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.
Very nice! Thank you for this wonderful archive. I wonder why I found it only now. Long live the BBS file archives!
This is so awesome! 😀 I’d be cool if you could download an entire archive of this at once, though.
But one thing that puzzles me is the “mtswslnkmcjklsdlsbdmMICROSOFT” string. There is an article about it here. It is definitely worth a read: http://www.os2museum.com/wp/mtswslnk/