Dec 092017
Advanced Heap Management for Turbo Pascal 5.5. | |||
---|---|---|---|
File Name | File Size | Zip Size | Zip Type |
BADPTR.ASM | 2438 | 739 | deflated |
BADPTR.OBJ | 326 | 296 | deflated |
BADPTR.PAS | 2850 | 1196 | deflated |
DEMO.PAS | 499 | 324 | deflated |
GRABHEAP.PAS | 2661 | 1013 | deflated |
HEAP.DOC | 17872 | 6503 | deflated |
HEAPLOG.PAS | 7961 | 2449 | deflated |
HPAT55.PAS | 5782 | 1703 | deflated |
Download File HEAP55.ZIP Here
Contents of the HEAP.DOC file
Advanced Heap Management for Turbo Pascal 5.5
Version 5.5
January 6, 1990
Overview
--------------------------------------------------------------------------
Turbo Pascal's heap is one of the most useful and powerful features of the
language. By using the heap, programs can access all 640K of DOS memory in a
completely dynamic fashion. With power comes responsibility, however. Managing
pointers to the heap is one of the trickiest subjects in Pascal. And as time
goes by, the 640K of DOS memory doesn't seem all that big -- accessing even
more memory is desirable.
This collection of Turbo Pascal units and utilities offers methods for
managing and extending the heap. It includes the following:
o a patch to TPC.EXE so that the compiler will generate an interrupt to a
user-supplied routine after dereferencing each pointer.
o a unit that checks for dereferencing an invalid pointer.
o a unit that transfers control of the New, Getmem, Dispose, and FreeMem
procedures to user-supplied routines.
o a unit that logs various information about the heap to disk at
convenient points in a program.
This collection of utilities is an updated version of those previously
written for Turbo Pascal 4.0 and 5.0.
Several people have developed virtual heap managers using earlier versions of
the compiler patch. This version of the patch should allow those utilities to
work with Turbo Pascal 5.5. Be careful to note the version 5.5 specific
comments in the documentation below, however. You can find these comments by
searching for the string Note!
Patching TPC.EXE
--------------------------------------------------------------------------
The patching program HPAT55 makes a small but important change to TPC.EXE
(version 5.5 only). Once the change is in place, the compiler is capable of
generating an interrupt each time a pointer is dereferenced. When the
interrupt occurs, a user-supplied routine takes control, thus allowing a
program to validate or modify the pointer value.
To apply the patch, compile the supplied program HPAT55.PAS, and run it while
a copy of TPC.EXE is in the current directory. HPAT55 modifies about 70
bytes of the command line compiler (most of which are required to relocate a
particular table so that it can hold two more bytes). Note that the patcher
works only on TPC, not on TURBO.EXE. While it would be possible to make the
same patch to TURBO.EXE, we rarely use the integrated compiler ourselves, and
it is linked differently enough to make finding the patch points a
time-consuming chore.
HPAT55 assures that the compiler version you have is Turbo Pascal 5.50. If
the file size is wrong, or if any of the locations to be patched contain
unexpected data, HPAT55 will halt with an error message. If this happens, be
sure to restore a clean copy of TPC.EXE from a backup, since a partial patch
may have occurred.
After the patch is successfully completed, the compiler's behavior is changed
in the following manner:
0. The compiler version will be reported as "5.5p" whenever TPC writes its
copyright message.
1. A new compiler switch directive will exist: {$P+} or {$P-}. The default
state of this switch is {$P-}. When the switch is turned ON, the compiler
will generate an interrupt $66 after each pointer dereference. The switch
may be turned on and off as desired in the source code only (not at the DOS
command line nor in a TPC.CFG file). Like other Turbo compiler directives,
the switch returns to its default state at the beginning of each unit.
It is your program's responsibility to install an interrupt handler prior to
the first occurrence of int $66 in the program. The supplied unit BADPTR
automatically installs such a handler for you.
Interrupt $66 is one of the user-definable interrupts described by IBM. If
this interrupt conflicts with your application, you may change HPAT55.PAS to
use a different interrupt. To do so, simply change the constant
DerefInterrupt in HPAT55.PAS, recompile the program and patch a fresh copy
of the compiler. User-definable interrupts range from $60 to $66. ($67 is
used for EMS, so it should be avoided here.)
If you change the interrupt, be sure to modify any related interrupt handler
(such as the one in BADPTR) as well.
2. Upon entry to your interrupt handler, ES:DI will hold the current value
of the pointer being dereferenced. The interrupt handler must preserve the
values of AX, BX, CX, DX, SI, DS, SS, BP, and SP, and it must return either
the same value of ES:DI or a value appropriately mapped by a virtual memory
manager.
While a Turbo Pascal Interrupt procedure will work correctly for the pointer
handler, the performance penalty may be unacceptable. For best performance,
the int $66 handler should be written primarily in assembly language. See
BADPTR.ASM for a simple example of such a handler.
The following fragment shows the code generated by the patched compiler when a
pointer P is a global variable, in this case a pointer to an integer.
P^ := 1;
C43E0000 LES DI,[P] ;load pointer into ES:DI
CD66 INT 66 ;call user routine
26C7050100 MOV WORD PTR ES:[DI],0001 ;assign value
An easy rule to remember is this: for each appearance of a dereferencing caret
(^) in your source code, an interrupt will be generated. Of course, this
occurs only when the $P+ directive is in effect for that statement.
Note!
In version 5.5 of Turbo Pascal, a dereferencing interrupt occurs in one
additional situation that is related to object-oriented programming. The
following code generates an interrupt:
dispose(anobjectptr, done);
This makes sense, since the statement is a kind of shorthand for the
following:
anobjectptr^.done;
dispose(anobjectptr);
The code that actually calls dispose in this case is located within the done
destructor, but the effect is the same.
Using BADPTR
--------------------------------------------------------------------------
BADPTR is a unit that works in conjunction with the HPAT55-patched compiler.
If you compile with the patched compiler, you must be sure that you USE the
unit BADPTR (or another unit with an int $66 handler), or your program may
crash unexpectedly. (Interrupt $66 often points to an IRET by default, but
that is not always the case.) You should USE BADPTR early in the USES
statement of the main program. If you USE BADPTR and then don't compile with
the patched compiler, no harm will result.
BADPTR automatically installs an interrupt handler that is invoked whenever a
pointer is dereferenced and the $P+ directive is active. This interrupt
handler checks that the pointer refers to the normal Turbo Pascal heap,
between HeapOrg and the top of the free list. If the pointer falls into this
range, the interrupt handler returns and the program proceeds. If the pointer
is outside of the normal heap, BADPTR calls an error routine, which writes the
value of the pointer as well as the relative code address where the error
occurred, and then halts. Note that NIL pointers will always fail BADPTR's
test. You can use Turbo's find runtime error facility to correlate the error
address to a source position.
If you are using pointers not allocated by New or GetMem, there are two ways
to keep BADPTR from reporting a false error. A common example of such a
pointer would be one pointing to the DOS command line, initialized with P :=
Ptr(PrefixSeg, $80). First, you could assure that the $P+ directive is not
active wherever you dereference such a pointer. Second, BADPTR interfaces two
WORD variables that allow you to determine the acceptable range for pointers.
HeapBot is the lowest acceptable pointer segment, and HeapTop is the highest.
BADPTR initializes these to the range of the normal Turbo heap. Suppose you
wanted to accept any pointer but the NIL pointer. In that case you could
assign:
HeapBot := $0001;
HeapTop := $FFFF;
BADPTR uses simple WriteLn statements to report an error. If this technique is
not appropriate for a particular application, modify the procedure BadPointer
in BADPTR.PAS. Upon entry to BadPointer, the system variable ErrorAddr
contains the PSP-relative address of the code causing the error, and BadP
contains the faulty pointer. Generally, BadPointer should halt without
returning. If it does not halt, execution will continue after the interrupt,
using the pointer originally supplied in ES:DI. The BadPointer procedure
cannot change the actual pointer value.
If you change HPAT55 to use a different interrupt, be sure to make the
corresponding change in BADPTR as well.
Using GRABHEAP
--------------------------------------------------------------------------
GRABHEAP is a unit that allows a program to take control of memory allocation
and deallocation functions normally handled by the system unit: NEW, GETMEM,
DISPOSE, and FREEMEM. To offer complete control, GRABHEAP interfaces two
procedures:
procedure CustomHeapControl(GetPtr : GetMemFunc; FreePtr : FreeMemProc);
{-Give control of GetMem, New, FreeMem, Dispose to specified procedures}
procedure SystemHeapControl;
{-Restore control to the system heap routines}
A program can call CustomHeapControl to transfer control of the system
routines to specified procedures. To do so, pass the addresses of two FAR,
global procedures that match the following declarations:
{$F+}
function CustomGetMem(Size : Word) : pointer;
begin
...
end;
procedure CustomFreeMem(P : Pointer; Size : Word);
begin
...
end;
{$F-}
To set up for this example, an appropriate call would be
CustomHeapControl(CustomGetMem, CustomFreeMem);
Note!
Due to changes in Turbo Pascal 5.5's heap manager, the declarations for the
custom routines are different than for previous versions of these utilities.
Thereafter, any calls that would normally go to New or GetMem are transferred
to CustomGetMem, and calls for Dispose or FreeMem are sent to CustomFreeMem.
The custom heap management routines can perform any needed actions, including
calls to the original system GetMem and FreeMem routines. To call the original
routines, the program must temporarily restore control to the system runtime
library by calling GRABHEAP's SystemHeapControl routine.
Here is an example of CustomGetMem and CustomFreeMem routines that do nothing
but keep a balance sheet of the memory allocated and deallocated in a program:
var
TotalAlloc : LongInt;
HeapMax : Pointer;
{$F+}
procedure MyFree(var P : Pointer; Size : Word); forward;
function MyGet(Size : Word) : pointer;
var
P : pointer;
begin
Inc(TotalAlloc, Size); {Update balance sheet}
SystemHeapControl; {Give back heap control temporarily}
GetMem(P, Size); {Use the system routine to allocate}
MyGet := P; {Assign it to function result}
if LongInt(HeapPtr) > LongInt(HeapMax) then
HeapMax := HeapPtr; {Keep track of heap high water mark}
CustomHeapControl(MyGet, MyFree); {Take over heap control again}
end;
procedure MyFree(P : Pointer; Size : Word);
begin
Dec(TotalAlloc, Size);
SystemHeapControl;
FreeMem(P, Size);
CustomHeapControl(MyGet, MyFree);
end;
{$F-}
begin
TotalAlloc := 0; {No memory allocated to start}
HeapMax := HeapOrg; {High water mark at base of heap}
CustomHeapControl(MyGet, MyFree); {Take over heap control}
.... {Normal program actions}
WriteLn('Maximum heap usage: ',
16*(LongInt(seg(HeapMax^))-seg(HeapOrg^)), ' bytes');
if TotalAlloc <> 0 then
WriteLn('Allocated memory not freed: ', TotalAlloc, ' bytes');
end.
GRABHEAP can be put to more powerful uses, of course. For example, based on an
installation flag, it could select among data storage in normal, EMS, or disk
memory. The pointer returned by the custom GetMem routine need not be a normal
pointer, but can instead be an EMS page and offset, or a disk page and offset,
combined into a four byte record. Then, based on the same installation flag, a
deref-interrupt handler can access the data on the appropriate media and
return a pointer to the actual data buffered in memory.
Note!
In Turbo Pascal 5.5, there is one form of call to New() that won't be
redirected to the custom routine you install. That's a call to allocate an
object and call its constructor at the same time:
new(AnObjP, Init);
or
AnObjP := new(AnObjPtrType, Init);
In both of these cases the allocation is performed by code within the
constructor itself. GRABHEAP doesn't redirect this code to the custom routines
since the code must perform special checks related to inheritance.
Using HEAPLOG
-------------------------------------------------------------------------
HEAPLOG is a unit that builds on top of the GRABHEAP facility to provide
diagnostic information for programs that make extensive use of the heap. It
keeps a log of all heap allocation and deallocation, and allows the program to
dump this log to disk at any time. By studying the log, you can detect
excessive heap fragmentation and memory that hasn't been deallocated.
You'll get a basic log just by using HEAPLOG in your program. HEAPLOG creates
a file named HEAP.LOG. When a program first starts, a report labeled "Initial"
is written to HEAP.LOG. When the program ends, a report labeled "Final" is
written to HEAP.LOG. The reports themselves will be described momentarily.
HEAPLOG interfaces two procedures that control the logging process:
procedure DumpHeapLog(Msg : string);
{-Write the current heap log to a file}
procedure ClearLog;
{-Clear all entries from the log}
DumpHeapLog adds another report to HEAP.LOG, giving it the label passed in
Msg. ClearLog clears HEAPLOG's internal data structures so that succeeding
reports will be relative to that point instead of relative to the beginning of
the program.
The following is an example of a HEAPLOG report.
Initial
MemAvail: 385568
MaxAvail: 385568
HeapPtr : 41DE:0000
HeapCnt : 0
FreeCnt : 0
Filled : FALSE
Intermediate
MemAvail: 382017
MaxAvail: 380742
HeapPtr : 4309:0002
HeapCnt : 15
FreeCnt : 5
Filled : FALSE
Pointer Size Allocated at
42CF:0008 115 0000:0AE3
42E9:000F 499 0000:0AE3
41F8:000F 387 0000:07F1
4229:0004 396 0000:07F1
4259:0007 125 0000:07F1
4261:0004 415 0000:07F1
427B:0003 116 0000:07F1
4282:0007 269 0000:07F1
4293:0004 223 0000:07F1
42A4:0009 158 0000:07F1
42AE:0007 42 0000:07F1
42B1:0001 112 0000:07F1
42B8:0001 74 0000:07F1
42BC:000B 301 0000:07F1
42D8:0008 279 0000:07F1
Free start Size
42D6:000B 29
42A1:0003 54
4242:0000 375
4211:0002 386
41DE:0000 431
Final
MemAvail: 385568
MaxAvail: 385568
HeapPtr : 41DE:0000
HeapCnt : 0
FreeCnt : 0
Filled : FALSE
HEAPLOG always generates the Initial and Final reports. For programs that
deallocate all their dynamic memory, the Final report should be the same as
the Initial. In the example, the Intermediate report is one generated by
calling DumpHeapLog directly.
Each report shows the values of MemAvail and MaxAvail in bytes, and the
current heap high water mark, HeapPtr. Each also shows the number of separate
blocks allocated on the heap (HeapCnt) and the number of blocks deallocated
and currently on the free list (FreeCnt). If HeapCnt is non-zero, HEAPLOG
shows the value of each allocated pointer, the size of the region it points
to, and the code address where the pointer was allocated. If FreeCnt is
non-zero, HEAPLOG shows the starting address of each free block and its size.
By default, HEAPLOG can track the allocation of up to 1000 pointers in one
run. If the program allocates more than this at one time, the Filled field
will report True. HEAPLOG's capacity can be adjusted by modifying the constant
MaxLog in HEAPLOG.PAS. Note that HEAPLOG itself uses 10 bytes of heap space
for each increment in MaxLog. (HEAPLOG doesn't report its own heap usage,
however.) The performance of calls to GetMem, FreeMem, New, and Dispose will
degrade for large numbers of pointers.
Note!
Because of the GRABHEAP limitation already described, HEAPLOG won't report
dynamic instances of objects that are allocated and initialized with a single
call to New(Obj, Init).
Legalities
--------------------------------------------------------------------------
Chris Franzen of O.K.Soft, West Germany, used earlier versions of HEAP.ARC to
help track down the compiler patch locations for Turbo Pascal 5.5. Thanks to
Chris for spending the time to do this.
Other programs and documentation in this package are copyright (C) TurboPower
Software, 1988, 1989, 1990. All rights reserved. TurboPower Software hereby
grants permission for free distribution of this software, and for use of these
units and techniques within commercial and non-commercial applications. The
units and utilities themselves may not be distributed commercially without
obtaining written permission from TurboPower Software.
We would appreciate hearing about enhancements made to these routines. Contact
Kim Kokkonen at Compuserve ID 76004,2611 or write to:
TurboPower Software
P.O. Box 66747
Scotts Valley, CA 95066
Version 5.5
January 6, 1990
Overview
--------------------------------------------------------------------------
Turbo Pascal's heap is one of the most useful and powerful features of the
language. By using the heap, programs can access all 640K of DOS memory in a
completely dynamic fashion. With power comes responsibility, however. Managing
pointers to the heap is one of the trickiest subjects in Pascal. And as time
goes by, the 640K of DOS memory doesn't seem all that big -- accessing even
more memory is desirable.
This collection of Turbo Pascal units and utilities offers methods for
managing and extending the heap. It includes the following:
o a patch to TPC.EXE so that the compiler will generate an interrupt to a
user-supplied routine after dereferencing each pointer.
o a unit that checks for dereferencing an invalid pointer.
o a unit that transfers control of the New, Getmem, Dispose, and FreeMem
procedures to user-supplied routines.
o a unit that logs various information about the heap to disk at
convenient points in a program.
This collection of utilities is an updated version of those previously
written for Turbo Pascal 4.0 and 5.0.
Several people have developed virtual heap managers using earlier versions of
the compiler patch. This version of the patch should allow those utilities to
work with Turbo Pascal 5.5. Be careful to note the version 5.5 specific
comments in the documentation below, however. You can find these comments by
searching for the string Note!
Patching TPC.EXE
--------------------------------------------------------------------------
The patching program HPAT55 makes a small but important change to TPC.EXE
(version 5.5 only). Once the change is in place, the compiler is capable of
generating an interrupt each time a pointer is dereferenced. When the
interrupt occurs, a user-supplied routine takes control, thus allowing a
program to validate or modify the pointer value.
To apply the patch, compile the supplied program HPAT55.PAS, and run it while
a copy of TPC.EXE is in the current directory. HPAT55 modifies about 70
bytes of the command line compiler (most of which are required to relocate a
particular table so that it can hold two more bytes). Note that the patcher
works only on TPC, not on TURBO.EXE. While it would be possible to make the
same patch to TURBO.EXE, we rarely use the integrated compiler ourselves, and
it is linked differently enough to make finding the patch points a
time-consuming chore.
HPAT55 assures that the compiler version you have is Turbo Pascal 5.50. If
the file size is wrong, or if any of the locations to be patched contain
unexpected data, HPAT55 will halt with an error message. If this happens, be
sure to restore a clean copy of TPC.EXE from a backup, since a partial patch
may have occurred.
After the patch is successfully completed, the compiler's behavior is changed
in the following manner:
0. The compiler version will be reported as "5.5p" whenever TPC writes its
copyright message.
1. A new compiler switch directive will exist: {$P+} or {$P-}. The default
state of this switch is {$P-}. When the switch is turned ON, the compiler
will generate an interrupt $66 after each pointer dereference. The switch
may be turned on and off as desired in the source code only (not at the DOS
command line nor in a TPC.CFG file). Like other Turbo compiler directives,
the switch returns to its default state at the beginning of each unit.
It is your program's responsibility to install an interrupt handler prior to
the first occurrence of int $66 in the program. The supplied unit BADPTR
automatically installs such a handler for you.
Interrupt $66 is one of the user-definable interrupts described by IBM. If
this interrupt conflicts with your application, you may change HPAT55.PAS to
use a different interrupt. To do so, simply change the constant
DerefInterrupt in HPAT55.PAS, recompile the program and patch a fresh copy
of the compiler. User-definable interrupts range from $60 to $66. ($67 is
used for EMS, so it should be avoided here.)
If you change the interrupt, be sure to modify any related interrupt handler
(such as the one in BADPTR) as well.
2. Upon entry to your interrupt handler, ES:DI will hold the current value
of the pointer being dereferenced. The interrupt handler must preserve the
values of AX, BX, CX, DX, SI, DS, SS, BP, and SP, and it must return either
the same value of ES:DI or a value appropriately mapped by a virtual memory
manager.
While a Turbo Pascal Interrupt procedure will work correctly for the pointer
handler, the performance penalty may be unacceptable. For best performance,
the int $66 handler should be written primarily in assembly language. See
BADPTR.ASM for a simple example of such a handler.
The following fragment shows the code generated by the patched compiler when a
pointer P is a global variable, in this case a pointer to an integer.
P^ := 1;
C43E0000 LES DI,[P] ;load pointer into ES:DI
CD66 INT 66 ;call user routine
26C7050100 MOV WORD PTR ES:[DI],0001 ;assign value
An easy rule to remember is this: for each appearance of a dereferencing caret
(^) in your source code, an interrupt will be generated. Of course, this
occurs only when the $P+ directive is in effect for that statement.
Note!
In version 5.5 of Turbo Pascal, a dereferencing interrupt occurs in one
additional situation that is related to object-oriented programming. The
following code generates an interrupt:
dispose(anobjectptr, done);
This makes sense, since the statement is a kind of shorthand for the
following:
anobjectptr^.done;
dispose(anobjectptr);
The code that actually calls dispose in this case is located within the done
destructor, but the effect is the same.
Using BADPTR
--------------------------------------------------------------------------
BADPTR is a unit that works in conjunction with the HPAT55-patched compiler.
If you compile with the patched compiler, you must be sure that you USE the
unit BADPTR (or another unit with an int $66 handler), or your program may
crash unexpectedly. (Interrupt $66 often points to an IRET by default, but
that is not always the case.) You should USE BADPTR early in the USES
statement of the main program. If you USE BADPTR and then don't compile with
the patched compiler, no harm will result.
BADPTR automatically installs an interrupt handler that is invoked whenever a
pointer is dereferenced and the $P+ directive is active. This interrupt
handler checks that the pointer refers to the normal Turbo Pascal heap,
between HeapOrg and the top of the free list. If the pointer falls into this
range, the interrupt handler returns and the program proceeds. If the pointer
is outside of the normal heap, BADPTR calls an error routine, which writes the
value of the pointer as well as the relative code address where the error
occurred, and then halts. Note that NIL pointers will always fail BADPTR's
test. You can use Turbo's find runtime error facility to correlate the error
address to a source position.
If you are using pointers not allocated by New or GetMem, there are two ways
to keep BADPTR from reporting a false error. A common example of such a
pointer would be one pointing to the DOS command line, initialized with P :=
Ptr(PrefixSeg, $80). First, you could assure that the $P+ directive is not
active wherever you dereference such a pointer. Second, BADPTR interfaces two
WORD variables that allow you to determine the acceptable range for pointers.
HeapBot is the lowest acceptable pointer segment, and HeapTop is the highest.
BADPTR initializes these to the range of the normal Turbo heap. Suppose you
wanted to accept any pointer but the NIL pointer. In that case you could
assign:
HeapBot := $0001;
HeapTop := $FFFF;
BADPTR uses simple WriteLn statements to report an error. If this technique is
not appropriate for a particular application, modify the procedure BadPointer
in BADPTR.PAS. Upon entry to BadPointer, the system variable ErrorAddr
contains the PSP-relative address of the code causing the error, and BadP
contains the faulty pointer. Generally, BadPointer should halt without
returning. If it does not halt, execution will continue after the interrupt,
using the pointer originally supplied in ES:DI. The BadPointer procedure
cannot change the actual pointer value.
If you change HPAT55 to use a different interrupt, be sure to make the
corresponding change in BADPTR as well.
Using GRABHEAP
--------------------------------------------------------------------------
GRABHEAP is a unit that allows a program to take control of memory allocation
and deallocation functions normally handled by the system unit: NEW, GETMEM,
DISPOSE, and FREEMEM. To offer complete control, GRABHEAP interfaces two
procedures:
procedure CustomHeapControl(GetPtr : GetMemFunc; FreePtr : FreeMemProc);
{-Give control of GetMem, New, FreeMem, Dispose to specified procedures}
procedure SystemHeapControl;
{-Restore control to the system heap routines}
A program can call CustomHeapControl to transfer control of the system
routines to specified procedures. To do so, pass the addresses of two FAR,
global procedures that match the following declarations:
{$F+}
function CustomGetMem(Size : Word) : pointer;
begin
...
end;
procedure CustomFreeMem(P : Pointer; Size : Word);
begin
...
end;
{$F-}
To set up for this example, an appropriate call would be
CustomHeapControl(CustomGetMem, CustomFreeMem);
Note!
Due to changes in Turbo Pascal 5.5's heap manager, the declarations for the
custom routines are different than for previous versions of these utilities.
Thereafter, any calls that would normally go to New or GetMem are transferred
to CustomGetMem, and calls for Dispose or FreeMem are sent to CustomFreeMem.
The custom heap management routines can perform any needed actions, including
calls to the original system GetMem and FreeMem routines. To call the original
routines, the program must temporarily restore control to the system runtime
library by calling GRABHEAP's SystemHeapControl routine.
Here is an example of CustomGetMem and CustomFreeMem routines that do nothing
but keep a balance sheet of the memory allocated and deallocated in a program:
var
TotalAlloc : LongInt;
HeapMax : Pointer;
{$F+}
procedure MyFree(var P : Pointer; Size : Word); forward;
function MyGet(Size : Word) : pointer;
var
P : pointer;
begin
Inc(TotalAlloc, Size); {Update balance sheet}
SystemHeapControl; {Give back heap control temporarily}
GetMem(P, Size); {Use the system routine to allocate}
MyGet := P; {Assign it to function result}
if LongInt(HeapPtr) > LongInt(HeapMax) then
HeapMax := HeapPtr; {Keep track of heap high water mark}
CustomHeapControl(MyGet, MyFree); {Take over heap control again}
end;
procedure MyFree(P : Pointer; Size : Word);
begin
Dec(TotalAlloc, Size);
SystemHeapControl;
FreeMem(P, Size);
CustomHeapControl(MyGet, MyFree);
end;
{$F-}
begin
TotalAlloc := 0; {No memory allocated to start}
HeapMax := HeapOrg; {High water mark at base of heap}
CustomHeapControl(MyGet, MyFree); {Take over heap control}
.... {Normal program actions}
WriteLn('Maximum heap usage: ',
16*(LongInt(seg(HeapMax^))-seg(HeapOrg^)), ' bytes');
if TotalAlloc <> 0 then
WriteLn('Allocated memory not freed: ', TotalAlloc, ' bytes');
end.
GRABHEAP can be put to more powerful uses, of course. For example, based on an
installation flag, it could select among data storage in normal, EMS, or disk
memory. The pointer returned by the custom GetMem routine need not be a normal
pointer, but can instead be an EMS page and offset, or a disk page and offset,
combined into a four byte record. Then, based on the same installation flag, a
deref-interrupt handler can access the data on the appropriate media and
return a pointer to the actual data buffered in memory.
Note!
In Turbo Pascal 5.5, there is one form of call to New() that won't be
redirected to the custom routine you install. That's a call to allocate an
object and call its constructor at the same time:
new(AnObjP, Init);
or
AnObjP := new(AnObjPtrType, Init);
In both of these cases the allocation is performed by code within the
constructor itself. GRABHEAP doesn't redirect this code to the custom routines
since the code must perform special checks related to inheritance.
Using HEAPLOG
-------------------------------------------------------------------------
HEAPLOG is a unit that builds on top of the GRABHEAP facility to provide
diagnostic information for programs that make extensive use of the heap. It
keeps a log of all heap allocation and deallocation, and allows the program to
dump this log to disk at any time. By studying the log, you can detect
excessive heap fragmentation and memory that hasn't been deallocated.
You'll get a basic log just by using HEAPLOG in your program. HEAPLOG creates
a file named HEAP.LOG. When a program first starts, a report labeled "Initial"
is written to HEAP.LOG. When the program ends, a report labeled "Final" is
written to HEAP.LOG. The reports themselves will be described momentarily.
HEAPLOG interfaces two procedures that control the logging process:
procedure DumpHeapLog(Msg : string);
{-Write the current heap log to a file}
procedure ClearLog;
{-Clear all entries from the log}
DumpHeapLog adds another report to HEAP.LOG, giving it the label passed in
Msg. ClearLog clears HEAPLOG's internal data structures so that succeeding
reports will be relative to that point instead of relative to the beginning of
the program.
The following is an example of a HEAPLOG report.
Initial
MemAvail: 385568
MaxAvail: 385568
HeapPtr : 41DE:0000
HeapCnt : 0
FreeCnt : 0
Filled : FALSE
Intermediate
MemAvail: 382017
MaxAvail: 380742
HeapPtr : 4309:0002
HeapCnt : 15
FreeCnt : 5
Filled : FALSE
Pointer Size Allocated at
42CF:0008 115 0000:0AE3
42E9:000F 499 0000:0AE3
41F8:000F 387 0000:07F1
4229:0004 396 0000:07F1
4259:0007 125 0000:07F1
4261:0004 415 0000:07F1
427B:0003 116 0000:07F1
4282:0007 269 0000:07F1
4293:0004 223 0000:07F1
42A4:0009 158 0000:07F1
42AE:0007 42 0000:07F1
42B1:0001 112 0000:07F1
42B8:0001 74 0000:07F1
42BC:000B 301 0000:07F1
42D8:0008 279 0000:07F1
Free start Size
42D6:000B 29
42A1:0003 54
4242:0000 375
4211:0002 386
41DE:0000 431
Final
MemAvail: 385568
MaxAvail: 385568
HeapPtr : 41DE:0000
HeapCnt : 0
FreeCnt : 0
Filled : FALSE
HEAPLOG always generates the Initial and Final reports. For programs that
deallocate all their dynamic memory, the Final report should be the same as
the Initial. In the example, the Intermediate report is one generated by
calling DumpHeapLog directly.
Each report shows the values of MemAvail and MaxAvail in bytes, and the
current heap high water mark, HeapPtr. Each also shows the number of separate
blocks allocated on the heap (HeapCnt) and the number of blocks deallocated
and currently on the free list (FreeCnt). If HeapCnt is non-zero, HEAPLOG
shows the value of each allocated pointer, the size of the region it points
to, and the code address where the pointer was allocated. If FreeCnt is
non-zero, HEAPLOG shows the starting address of each free block and its size.
By default, HEAPLOG can track the allocation of up to 1000 pointers in one
run. If the program allocates more than this at one time, the Filled field
will report True. HEAPLOG's capacity can be adjusted by modifying the constant
MaxLog in HEAPLOG.PAS. Note that HEAPLOG itself uses 10 bytes of heap space
for each increment in MaxLog. (HEAPLOG doesn't report its own heap usage,
however.) The performance of calls to GetMem, FreeMem, New, and Dispose will
degrade for large numbers of pointers.
Note!
Because of the GRABHEAP limitation already described, HEAPLOG won't report
dynamic instances of objects that are allocated and initialized with a single
call to New(Obj, Init).
Legalities
--------------------------------------------------------------------------
Chris Franzen of O.K.Soft, West Germany, used earlier versions of HEAP.ARC to
help track down the compiler patch locations for Turbo Pascal 5.5. Thanks to
Chris for spending the time to do this.
Other programs and documentation in this package are copyright (C) TurboPower
Software, 1988, 1989, 1990. All rights reserved. TurboPower Software hereby
grants permission for free distribution of this software, and for use of these
units and techniques within commercial and non-commercial applications. The
units and utilities themselves may not be distributed commercially without
obtaining written permission from TurboPower Software.
We would appreciate hearing about enhancements made to these routines. Contact
Kim Kokkonen at Compuserve ID 76004,2611 or write to:
TurboPower Software
P.O. Box 66747
Scotts Valley, CA 95066
December 9, 2017
Add comments