- BORLAND/: Borland C++ 4.52 (chosen over 4.5 by byte-match: CODE/RP/CW32.LIB
is identical to 4.52's install lib). BCC32/TLINK32/TLIB/MAKE run natively on
Win11; CODE/BT/OPT.MAK is the shipped BTL4OPT.EXE's exact flag recipe
(extender = Borland PowerPack DPMI32, not Phar Lap TNT).
- restoration/source410/: the literal 1995-form reconstruction of the missing
BT game source (never mixed into CODE/). Round 1-3 state:
* 6 of 10 surviving original TUs COMPILE CLEAN under the period toolchain
(BTMSSN, BTCNSL, BTSCNRL, BTTEAM, BTL4MODE, BTL4ARND) - first builds
since 1996.
* BT_L4/BTL4APP.CPP pilot reconstruction: 12/12 functions, Fail() lands on
its binary-recorded line 400 exactly.
* BT/BTCNSL.HPP: console wire IDs recovered from the binary's ctors
(Killed=9, Damaged=10, ScoreUpdate=13, DeathWithoutHonor=15 [T1];
TeamScore=12 flagged [T4]).
* MUNGA/: 8 engine-header backfills back-dated from the BT412 WinTesla tree
(VDATA numbering decomp-verified; AUDREND's OpenAL-era virtual removed -
the period compiler is the drift detector).
* Tooling: backdate.py (WinTesla->1995 header transform), compile410.sh
(per-TU verification sweep under authentic OPT.MAK flags).
* README: corrected roadmap - MECH.HPP is the capstone grown with the mech
TU reconstructions; BTREG.CPP green = the header-family milestone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1045 lines
41 KiB
Plaintext
1045 lines
41 KiB
Plaintext
Online document
|
|
___________________________________________________________________________
|
|
|
|
Windows memory management
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CONTENTS
|
|
___________________________________________________________________________
|
|
|
|
|
|
|
|
|
|
|
|
Windows memory Mixed-model programming: Addressing
|
|
management 1 modifiers . . . . . . . . . . . . 7
|
|
Running out of memory . . . . . . 1 Segment pointers . . . . . . . 8
|
|
Memory models . . . . . . . . . . 1 Declaring far objects . . . . . 9
|
|
The '86 registers . . . . . . . 2 Declaring functions to be near or
|
|
General-purpose registers . . 2 far . . . . . . . . . . . . . . 9
|
|
Segment registers . . . . . . 2 Declaring pointers to be near,
|
|
Special-purpose registers . . 2 far, or huge . . . . . . . . . 10
|
|
The flags register . . . . . . 3 Pointing to a given
|
|
Memory segmentation . . . . . . 3 segment:offset address . . . 11
|
|
Pointers . . . . . . . . . . . . 4 Using library files . . . . . . 12
|
|
Near pointers . . . . . . . . 4 Linking mixed modules . . . . . 12
|
|
Far pointers . . . . . . . . . 4
|
|
Huge pointers . . . . . . . . 5 Index 14
|
|
The four memory models . . . . . 5
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This document covers
|
|
|
|
See Chapter 8, o What to do when you receive "Out of memory" errors.
|
|
"Building a
|
|
Windows o What memory models are: how to choose one, and why
|
|
application," in you would (or wouldn't) want to use a particular
|
|
the User's Guide memory model.
|
|
for information on
|
|
choosing a memory
|
|
model for Windows
|
|
modules.
|
|
|
|
|
|
|
|
===========================================================================
|
|
Running out of memory
|
|
===========================================================================
|
|
|
|
Borland C++ does not generate any intermediate data
|
|
structures to disk when it is compiling (Borland C++
|
|
writes only .OBJ files to disk); instead it uses RAM
|
|
for intermediate data structures between passes.
|
|
Because of this, you might encounter the message "Out
|
|
of memory" if there is not enough memory available for
|
|
the compiler.
|
|
|
|
The solution to this problem is to make your functions
|
|
smaller, or to split up the file that has large
|
|
functions.
|
|
|
|
|
|
|
|
===========================================================================
|
|
Memory models
|
|
===========================================================================
|
|
|
|
Borland C++ gives you four memory models, each suited
|
|
for different program and code sizes. Each memory model
|
|
uses memory differently. What do you need to know to
|
|
use memory models? To answer that question, we have to
|
|
|
|
|
|
|
|
- 1 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
See page 5 for a take a look at the computer system you're working on.
|
|
summary of each Its central processing unit (CPU) is a microprocessor
|
|
memory model. belonging to the Intel iAPx86 family; an 80286, 80386,
|
|
or 80486. For now, we'll just refer to it as an '86.
|
|
|
|
|
|
The '86 registers =======================================================
|
|
|
|
These are some of the registers found in the '86
|
|
processor. There are other registers--but they can't be
|
|
accessed directly, so they're not shown here.
|
|
|
|
Figure not available online
|
|
'86 registers
|
|
|
|
|
|
------------------ The general-purpose registers are the ones used most
|
|
General-purpose often to hold and manipulate data. Each has some
|
|
registers special functions that only it can do. For example,
|
|
------------------
|
|
o Some math operations can only be done using AX.
|
|
|
|
o BX can be used as an index register.
|
|
|
|
o CX is used by LOOP and some string instructions.
|
|
|
|
o DX is implicitly used for some math operations.
|
|
|
|
But there are many operations that all these registers
|
|
can do; in many cases, you can freely exchange one for
|
|
another.
|
|
|
|
|
|
------------------ The segment registers hold selectors which reference
|
|
Segment registers segment descriptors. These descriptors provide
|
|
------------------ information about the starting address of the segment,
|
|
size, access control, and use.
|
|
|
|
------------------ The '86 also has some special-purpose registers:
|
|
Special-purpose
|
|
registers o The SI and DI registers can do many of the things the
|
|
------------------ general-purpose registers can, plus they are used as
|
|
index registers. They're also used by Borland C++ for
|
|
register variables.
|
|
|
|
o The SP register points to the current top-of-stack
|
|
and is an offset into the stack segment.
|
|
|
|
o The BP register is a secondary stack pointer, usually
|
|
used to index into the stack in order to retrieve
|
|
arguments or automatic variables.
|
|
|
|
Borland C++ functions use the base pointer (BP)
|
|
register as a base address for arguments and automatic
|
|
|
|
|
|
|
|
- 2 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
variables. Parameters have positive offsets from BP,
|
|
which vary depending on the memory model. BP points to
|
|
the saved previous BP value if there is a stack frame.
|
|
Functions that have no arguments will not use or save
|
|
BP if the Standard Stack Frame option is Off.
|
|
|
|
Automatic variables are given negative offsets from BP.
|
|
The offsets depend on how much space has already been
|
|
assigned to local variables.
|
|
|
|
|
|
------------------ The 16-bit flags register contains all pertinent
|
|
The flags register information about the state of the '86 and the results
|
|
------------------ of recent instructions.
|
|
|
|
For example, if you wanted to know whether a
|
|
subtraction produced a zero result, you would check the
|
|
zero flag (the Z bit in the flags register) immediately
|
|
after the instruction; if it were set, you would know
|
|
the result was zero. Other flags, such as the carry and
|
|
overflow flags, similarly report the results of
|
|
arithmetic and logical operations.
|
|
|
|
Figure not available online
|
|
Flags register
|
|
of the '86 Other flags control modes of operation of the '86. The
|
|
direction flag controls the direction in which the
|
|
string instructions move, and the interrupt flag
|
|
controls whether external hardware, such as a keyboard
|
|
or modem, is allowed to halt the current code temporar-
|
|
ily so that urgent needs can be serviced. The trap flag
|
|
is used only by software that debugs other software.
|
|
|
|
The flags register isn't usually modified or read
|
|
directly. Instead, the flags register is generally
|
|
controlled through special assembler instructions (such
|
|
as CLD, STI, and CMC) and through arithmetic and
|
|
logical instructions that modify certain flags.
|
|
Likewise, the contents of certain bits of the flags
|
|
register affect the operation of instructions such as
|
|
JZ, RCR, and MOVSB. The flags register is not really
|
|
used as a storage location, but rather holds the status
|
|
and control data for the '86.
|
|
|
|
|
|
Memory =======================================================
|
|
segmentation
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 3 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The Intel '86 microprocessor has a segmented memory
|
|
architecture. It has a total address space of 16MB, but
|
|
The 80386 and it is designed to directly address only 64K of memory
|
|
80486 processors at a time.
|
|
actually have a
|
|
total address o The '86 keeps track of four different segments: code,
|
|
space of four data, stack, and extra. The code segment is where the
|
|
gigabytes and machine instructions are; the data segment, where
|
|
their segments information is; the stack is, of course, the stack;
|
|
needn't be as and the extra segment is also used for extra data.
|
|
small as 64K, but
|
|
Windows 3.x o The '86 has four 16-bit segment registers (one for
|
|
doesn't change the each segment) named CS, DS, SS, and ES; these point
|
|
segment size. to the code, data, stack, and extra segments,
|
|
respectively.
|
|
|
|
|
|
Pointers =======================================================
|
|
|
|
Although you can declare a pointer or function to be a
|
|
specific type regardless of the model used, by default
|
|
the type of memory model you choose determines the
|
|
default type of pointers used for code and data.
|
|
Pointers come in four flavors: near (16 bits), far (32
|
|
bits), huge (also 32 bits), and segment (16 bits).
|
|
|
|
|
|
------------------ A near pointer (16 bits) relies on one of the segment
|
|
Near pointers registers to
|
|
------------------ specify the address. For example, a pointer to a
|
|
function would take on the segment of the code segment
|
|
(CS) register. In a similar fashion, a near data
|
|
pointer contains an offset to the data segment (DS)
|
|
register. Near pointers are easy to manipulate, since
|
|
any arithmetic (such as addition) can be done without
|
|
worrying about the segment.
|
|
|
|
|
|
------------------ A far pointer (32 bits) contains not only the offset
|
|
Far pointers within the segment, but also the segment address (as
|
|
------------------ another 16-bit value). By using far pointers, you can
|
|
have multiple code segments; that, in turn, allows you
|
|
to have programs larger than 64K. You can also address
|
|
more than 64K of data.
|
|
|
|
The equals (==) and not-equal (!=) operators use the
|
|
32-bit value as an unsigned long. The comparison
|
|
operators (<=, >=, <, and >) use just the offset.
|
|
|
|
The == and != operators need all 32 bits, so the
|
|
computer can compare to the NULL pointer (0000:0000).
|
|
If you used only the offset value for equality
|
|
|
|
|
|
|
|
|
|
|
|
- 4 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
checking, any pointer with 0000 offset would be equal
|
|
to the NULL pointer, which is not what you want.
|
|
|
|
Important! If you add values to a far pointer, only the offset is
|
|
changed. If you add enough to cause the offset to
|
|
exceed FFFF (its maximum possible value), the pointer
|
|
just wraps around back to the beginning of the segment.
|
|
For example, if you add 1 to 150D:FFFF, the result
|
|
would be 150D:0000. Likewise, if you subtract 1 from
|
|
150D:0000, you would get 150D:FFFF.
|
|
|
|
If you want to do pointer comparisons, it's safest to
|
|
use either near pointers--which all use the same
|
|
segment address--or huge pointers, described next.
|
|
|
|
|
|
------------------ Huge pointers are also 32 bits long. Like far pointers,
|
|
Huge pointers they contain both a segment address and an offset.
|
|
------------------ Unlike far pointers, when doing address calculations,
|
|
the segment is adjusted if necessary.
|
|
|
|
|
|
The four memory =======================================================
|
|
models
|
|
Borland C++ gives you four memory models: small,
|
|
medium, compact, and large. Your program requirements
|
|
--> determine which one you pick. (See Chapter 8, "Building
|
|
a Windows application," in the User's Guide for
|
|
information on choosing a memory model for Windows
|
|
modules.) Here's a brief summary of each:
|
|
|
|
This is a good Small. The code and data segments are different and
|
|
size for average don't overlap, so you have 64K of code and 64K of data
|
|
applications. and stack. Near pointers are always used.
|
|
|
|
Best for large Medium. Far pointers are used for code, but not for
|
|
programs without data. As a result, data plus stack are limited to 64K,
|
|
much data in but code is limited only to the amount of directly
|
|
memory. addressable memory.
|
|
|
|
Best if code is Compact. The inverse of medium: Far pointers are used
|
|
small but needs to for data, but not for code. Code is then limited to
|
|
address a lot of 64K, while data is limited only to the amount of
|
|
data. directly addressable memory. Static data and the stack
|
|
are still limited to 64K.
|
|
|
|
Large is needed Large. Far pointers are used for both code and data,
|
|
only for very giving both a range limited only by the amount of
|
|
large directly addressable memory. Static data and the stack
|
|
applications. are still limited to 64K.
|
|
|
|
Figures 1.3 through 1.6 show how memory in the '86 is
|
|
apportioned for the Borland C++ memory models. To
|
|
select these memory models, you can either use menu
|
|
|
|
|
|
|
|
- 5 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
selections from the IDE, or you can type options
|
|
invoking the command-line compiler version of Borland
|
|
C++.
|
|
|
|
|
|
Figure not available online
|
|
Small model
|
|
memory
|
|
segmentation Figure not available online
|
|
Medium model
|
|
memory
|
|
segmentation Figure not available online
|
|
Compact model
|
|
|
|
Figure not available online
|
|
CS points to only
|
|
one sfile at a Table 1.1 summarizes the different models and how they
|
|
time compare to one another. The models are often grouped
|
|
according to whether their code or data models are
|
|
small (64K) or large (16 MB); these groups correspond
|
|
to the rows and columns in Table 1.1.
|
|
CS points to only
|
|
one sfile at a
|
|
time -------------------------------------------------------
|
|
small code models Code size
|
|
because, by Data size --------------------------------------------
|
|
default, code 64K 1MB/4GB
|
|
pointers are near;
|
|
likewise, compact -------------------------------------------------------
|
|
and large are
|
|
large data models
|
|
because, by 64K Small (no overlap; Medium (small data,
|
|
default, data total size = 128K) large code)
|
|
pointers are far.
|
|
-------------------------------------------------------
|
|
|
|
|
|
1MB Compact (large data, Large (large data,
|
|
/4GB small code) large code)")
|
|
|
|
|
|
-------------------------------------------------------
|
|
|
|
Important! When you compile a module (a given source file with
|
|
some number of routines in it), the resulting code for
|
|
that module cannot be greater than 64K, since it must
|
|
all fit inside of one code segment. This is true even
|
|
if you're using one of the larger code models (medium
|
|
or large). If your module is too big to fit into one
|
|
(64K) code segment, you must break it up into different
|
|
source code files, compile each file separately, then
|
|
link them together.
|
|
|
|
|
|
|
|
|
|
|
|
- 6 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
===========================================================================
|
|
Mixed-model programming: Addressing modifiers
|
|
===========================================================================
|
|
|
|
Borland C++ introduces eight new keywords not found in
|
|
standard ANSI C (near, far, huge, _cs, _ds, _es, _ss,
|
|
and _seg) that can be used as modifiers to pointers
|
|
(and in some cases, to functions), with certain
|
|
limitations and warnings.
|
|
|
|
In Borland C++, you can modify the declarations of
|
|
pointers, objects, and functions with the keywords
|
|
near, far, or huge. We explained near, far, and huge
|
|
data pointers earlier in this chapter. You can declare
|
|
far objects using the far keyword. near functions are
|
|
invoked with near calls and exit with near returns.
|
|
Similarly, far functions are called far and do far
|
|
returns. huge functions are like far functions, except
|
|
that huge functions set DS to a new value, while far
|
|
functions do not.
|
|
|
|
There are also four special near data pointers: _cs,
|
|
_ds, _es, and _ss. These are 16-bit pointers that are
|
|
specifically associated with the corresponding segment
|
|
register. For example, if you were to declare a pointer
|
|
to be
|
|
|
|
char _ss *p;
|
|
|
|
then p would contain a 16-bit offset into the stack
|
|
segment.
|
|
|
|
Functions and pointers within a given program default
|
|
to near or far, depending on the memory model you
|
|
select. If the function or pointer is near, it is
|
|
automatically associated with either the CS or DS
|
|
register.
|
|
|
|
The next table shows just how this works. Note that the
|
|
size of the pointer corresponds to whether it is
|
|
working within a 64K memory limit (near, within a
|
|
segment) or inside the general 1 MB memory space (far,
|
|
has its own segment address).
|
|
|
|
Pointer results
|
|
-------------------------------------------------------
|
|
Memory model Function pointers Data pointers
|
|
-------------------------------------------------------
|
|
|
|
Small near, _cs near, _ds
|
|
Medium far near, _ds
|
|
Compact near, _cs far
|
|
Large far far
|
|
|
|
|
|
|
|
|
|
- 7 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
-------------------------------------------------------
|
|
|
|
|
|
Segment pointers =======================================================
|
|
|
|
Use _seg in segment pointer type declarators. The
|
|
resulting pointers are 16-bit segment pointers. The
|
|
syntax for _seg is:
|
|
|
|
datatype _seg *identifier;
|
|
|
|
For example,
|
|
|
|
int _seg *name;
|
|
|
|
Any indirection through identifier has an assumed
|
|
offset of 0. In arithmetic involving segment pointers
|
|
the following rules hold true:
|
|
|
|
1. You can't use the ++, - -, +=, or -= operators with
|
|
segment pointers.
|
|
|
|
2. You cannot subtract one segment pointer from
|
|
another.
|
|
|
|
3. When adding a near pointer to a segment pointer, the
|
|
result is a far pointer that is formed by using the
|
|
segment from the segment pointer and the offset from
|
|
the near pointer. Therefore, the two pointers must
|
|
either point to the same type, or one must be a
|
|
pointer to void. There is no multiplication of the
|
|
offset regardless of the type pointed to.
|
|
|
|
4. When a segment pointer is used in an indirection
|
|
expression, it is also implicitly converted to a far
|
|
pointer.
|
|
|
|
5. When adding or subtracting an integer operand to or
|
|
from a segment pointer, the result is a far pointer,
|
|
with the segment taken from the segment pointer and
|
|
the offset found by multiplying the size of the
|
|
object pointed to by the integer operand. The
|
|
arithmetic is performed as if the integer were added
|
|
to or subtracted from the far pointer.
|
|
|
|
6. Segment pointers can be assigned, initialized,
|
|
passed into and out of functions, compared and so
|
|
forth. (Segment pointers are compared as if their
|
|
values were unsigned integers.) In other words,
|
|
other than the above restrictions, they are treated
|
|
exactly like any other pointer.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 8 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Declaring far =======================================================
|
|
objects
|
|
You can declare far objects in Borland C++. For
|
|
example,
|
|
|
|
int far x = 5;
|
|
int far z;
|
|
extern int far y = 4;
|
|
static long j;
|
|
|
|
The command-line compiler options -zE, -zF, and -zH
|
|
(which can also be set using #pragma option) affect the
|
|
far segment name, class, and group, respectively. When
|
|
you change them with #pragma option, you can change
|
|
them at any time and they apply to any ensuing far
|
|
object declarations. Thus you could use the following
|
|
sequence to create a far object in a specific segment:
|
|
|
|
#pragma option -zEmysegment -zHmygroup -zFmyclass
|
|
int far x;
|
|
#pragma option -zE* -zH* -zF*
|
|
|
|
This will put x in segment MYSEGMENT 'MYCLASS' in the
|
|
group 'MYGROUP', then reset all of the far object items
|
|
to the default values.
|
|
|
|
|
|
Declaring =======================================================
|
|
functions to be
|
|
near or far On occasion, you'll want (or need) to override the
|
|
default function type of your memory model shown in
|
|
Table 1.1 (page 6).
|
|
|
|
For example, suppose you're using the large memory
|
|
model, but you have a recursive (self-calling) function
|
|
in your program, like this:
|
|
|
|
double power(double x,int exp)
|
|
{
|
|
if (exp <= 0)
|
|
return(1);
|
|
else
|
|
return(x * power(x, exp-1));
|
|
}
|
|
|
|
Every time power calls itself, it has to do a far call,
|
|
which uses more stack space and clock cycles. By
|
|
declaring power as near, you eliminate some of the
|
|
overhead by forcing all calls to that function to be
|
|
near:
|
|
|
|
double near power(double x,int exp)
|
|
|
|
|
|
|
|
|
|
|
|
- 9 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This guarantees that power is callable only within the
|
|
code segment in which it was compiled, and that all
|
|
calls to it are near calls.
|
|
|
|
This means that if you are using a large code model
|
|
(medium or large), you can only call power from within
|
|
the module where it is defined. Other modules have
|
|
their own code segment and thus cannot call near
|
|
functions in different modules. Furthermore, a near
|
|
function must be either defined or declared before the
|
|
first time it is used, or the compiler won't know it
|
|
needs to generate a near call.
|
|
|
|
Conversely, declaring a function to be far means that a
|
|
far return is generated. In the small code models, the
|
|
far function must be declared or defined before its
|
|
first use to ensure it is invoked with a far call.
|
|
|
|
Look back at the power example. It is wise to also
|
|
declare power as static, since it should only be called
|
|
from within the current module. That way, being a
|
|
static, its name will not be available to any functions
|
|
outside the module.
|
|
|
|
|
|
Declaring pointers =======================================================
|
|
to be near, far,
|
|
or huge You've seen why you might want to declare functions to
|
|
be of a different model than the rest of the program.
|
|
Why might you want to do the same thing for pointers?
|
|
For the same reasons given in the preceding section:
|
|
either to avoid unnecessary overhead (declaring near
|
|
when the default would be far) or to reference
|
|
something outside of the default segment (declaring far
|
|
or huge when the default would be near).
|
|
|
|
There are, of course, potential pitfalls in declaring
|
|
functions and pointers to be of nondefault types. For
|
|
example, say you have the following small model
|
|
program:
|
|
|
|
Note that this void myputs(s)
|
|
program uses the char *s;
|
|
EasyWin library. {
|
|
}
|
|
|
|
main()
|
|
{
|
|
char near *mystr;
|
|
|
|
mystr = "Hello, world\n";
|
|
myputs(mystr);
|
|
}
|
|
|
|
|
|
|
|
|
|
- 10 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This program works fine. In fact, the near declaration
|
|
on mystr is redundant, since all pointers, both code
|
|
and data, will be near.
|
|
|
|
But what if you recompile this program using the
|
|
compact (or large) memory model? The pointer mystr in
|
|
main is still near (it's still a 16-bit pointer).
|
|
However, the pointer s in myputs is now far, since
|
|
that's the default. This means that myputs will pull
|
|
two words out of the stack in an effort to create a far
|
|
pointer, and the address it ends up with will certainly
|
|
not be that of mystr.
|
|
|
|
How do you avoid this problem? The solution is to
|
|
define myputs in modern C style, like this:
|
|
|
|
void myputs(char *s)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; s[i] != 0; i++)
|
|
putc(s[i]);
|
|
}
|
|
|
|
If you're going to Now when Borland C++ compiles your program, it knows
|
|
explicitly declare that myputs expects a pointer to char; and since you're
|
|
pointers to be of compiling under the large model, it knows that the
|
|
type far or near, pointer must be far. Because of that, Borland C++ will
|
|
be sure to use push the data segment (DS) register onto the stack
|
|
function along with the 16-bit value of mystr, forming a far
|
|
prototypes for any pointer.
|
|
functions that
|
|
might use them. How about the reverse case: Arguments to myputs
|
|
declared as far and compiling with a small data model?
|
|
Again, without the function prototype, you will have
|
|
problems, since main will push both the offset and the
|
|
segment address onto the stack, but myputs will only
|
|
expect the offset. With the prototype-style function
|
|
definitions, though, main will only push the offset
|
|
onto the stack.
|
|
|
|
|
|
------------------ How do you make a far pointer point to a given memory
|
|
Pointing to a location (a specific segment:offset address)? You can
|
|
given use the macro MK_FP, which takes a segment and an
|
|
segment:offset offset and returns a far pointer. For example,
|
|
address
|
|
------------------ MK_FP(segment_value, offset_value)
|
|
|
|
Given a far pointer, fp, you can get the segment
|
|
component with FP_SEG(fp) and the offset component with
|
|
FP_OFF(fp). For more information about these three
|
|
Borland C++ library routines, refer to the Library
|
|
Reference.
|
|
|
|
|
|
|
|
- 11 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Using library =======================================================
|
|
files
|
|
Borland C++ offers a version of the standard library
|
|
routines for each of the six memory models. Borland C++
|
|
is smart enough to link in the appropriate libraries in
|
|
the proper order, depending on which model you've
|
|
selected. However, if you're using the Borland C++
|
|
linker, TLINK, directly (as a standalone linker), you
|
|
need to specify which libraries to use. See Chapter 4,
|
|
"TLINK: The Turbo linker" in the Tools and Utilities
|
|
Guide for details on how to do so.
|
|
|
|
|
|
Linking mixed =======================================================
|
|
modules
|
|
What if you compiled one module using the small memory
|
|
model, and another module using the large model, then
|
|
wanted to link them together? What would happen?
|
|
|
|
The files would in some cases link together fine, but
|
|
the problems you would encounter would be similar to
|
|
those described in the earlier section, "Declaring
|
|
functions to be near or far." If a function in the
|
|
small module called a function in the large module, it
|
|
would do so with a near call, which would probably be
|
|
disastrous. Furthermore, you could face the same
|
|
problems with pointers as described in the earlier
|
|
section, "Declaring pointers to be near, far, or huge,"
|
|
since a function in the small module would expect to
|
|
pass and receive near pointers, while a function in the
|
|
large module would expect far pointers.
|
|
|
|
The solution, again, is to use function prototypes.
|
|
Suppose that you put myputs into its own module and
|
|
compile it with the large memory model. Then create a
|
|
header file called myputs.h (or some other name with a
|
|
.h extension), which would have the following function
|
|
prototype in it:
|
|
|
|
void far myputs(char far *s);
|
|
|
|
Now, if you put main into its own module (called
|
|
MYMAIN.C), set things up like this:
|
|
|
|
Note that this #include <stdio.h>
|
|
program uses the #include "myputs.h"
|
|
EasyWin library.
|
|
main()
|
|
{
|
|
char near *mystr;
|
|
|
|
mystr = "Hello, world\n";
|
|
|
|
|
|
|
|
|
|
|
|
- 12 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
myputs(mystr);
|
|
}
|
|
|
|
When you compile this program, Borland C++ reads in the
|
|
function prototype from myputs.h and sees that it is a
|
|
far function that expects a far pointer. Because of
|
|
that, it will generate the proper calling code, even if
|
|
it's compiled using the small memory model.
|
|
|
|
What if, on top of all this, you need to link in
|
|
library routines? Your best bet is to use one of the
|
|
large model libraries and declare everything to be far.
|
|
To do this, make a copy of each header file you would
|
|
normally include (such as stdio.h), and rename the copy
|
|
to something appropriate (such as fstdio.h).
|
|
|
|
Then edit each function prototype in the copy so that
|
|
it is explicitly far, like this:
|
|
|
|
int far cdecl printf(char far * format, ...);
|
|
|
|
That way, not only will far calls be made to the
|
|
routines, but the pointers passed will be far pointers
|
|
as well. Modify your program so that it includes the
|
|
new header file:
|
|
|
|
Note that this #include <fstdio.h>
|
|
program uses the
|
|
EasyWin library. main()
|
|
{
|
|
char near *mystr;
|
|
mystr = "Hello, world\n";
|
|
printf(mystr);
|
|
}
|
|
|
|
Compile your program with the command-line compiler BCC
|
|
then link it with TLINK, specifying a large model
|
|
library, such as CWL.LIB. Mixing models is tricky, but
|
|
it can be done; just be prepared for some difficult
|
|
bugs if you do things wrong.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 13 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
INDEX
|
|
___________________________________________________________________________
|
|
|
|
|
|
|
|
|
|
|
|
'86 processors _es (keyword) 7
|
|
registers 2-3 ES register 4
|
|
extra segment 4
|
|
|
|
A
|
|
auxiliary carry flag 3 F
|
|
AX register 2 far (keyword) 4, 7, 13
|
|
flags
|
|
register 2, 3
|
|
B FP_OFF 11
|
|
base address register 2 FP_SEG 11
|
|
BP register 2 functions
|
|
BX register 2 declaring
|
|
as near or far 9
|
|
far
|
|
C declaring 10
|
|
carry flag 3 memory model size and 9
|
|
code segment 4 near
|
|
command-line compiler declaring 10
|
|
options memory models and 9
|
|
data segment recursive
|
|
name 9 memory models and 9
|
|
far objects (-zE
|
|
-zF
|
|
and -zH) 9 H
|
|
-zX (code and data segments) 9 huge (keyword) 4, 7
|
|
_cs (keyword) 7
|
|
CS register 4
|
|
CX register 2 I
|
|
interrupts
|
|
flag 3
|
|
D IP (instruction pointer) register 2
|
|
data segment
|
|
naming and renaming 9
|
|
data segments 4 L
|
|
descriptors linker
|
|
segment 2 mixed modules and 12
|
|
DI register 2 using directly 12
|
|
direction flag 3
|
|
_ds (keyword) 7
|
|
DS register 4 M
|
|
DX register 2 macros
|
|
far pointer creation 11
|
|
MK_FP 11
|
|
E memory
|
|
errors Borland C++'s usage of 1
|
|
out of memory 1 memory models and 6
|
|
|
|
|
|
|
|
Index 14
|
|
|
|
|
|
|
|
|
|
|
|
|
|
memory addresses comparing 5
|
|
calculating 2 default data 6
|
|
far pointers and 4 far
|
|
near pointers and 4 adding values to 5
|
|
pointing to 11 declaring 10-11
|
|
memory models 6, 1-13 function prototypes and 11
|
|
changing 11 memory model size and 10
|
|
compact 5 registers and 4
|
|
comparison 6 far memory model and 4
|
|
defined 5 huge 5
|
|
illustrations 6 declaring 10-11
|
|
large 5 huge memory model and 4
|
|
medium 5 memory models and 4, 7
|
|
memory apportionment and 6 to memory addresses 11
|
|
mixing 12 modifiers 7
|
|
function prototypes and 12 near
|
|
pointers and 4, 7 declaring 10-11
|
|
small 5 function prototypes and 11
|
|
Windows and 5 memory model size and 10
|
|
mixed modules registers and 4
|
|
linking 12 near memory model and 4
|
|
MK_FP (run-time library macro) 11 normalized 5
|
|
modifiers segment 7, 8
|
|
pointers 8 stack 2
|
|
modules positive offsets 3
|
|
linking mixed 12 #pragma directives
|
|
size limit 6 option pragma
|
|
far objects and 9
|
|
prototypes
|
|
N far and near pointers and 11
|
|
near (keyword) 4, 7 mixing modules and 12
|
|
negative offsets 3
|
|
|
|
R
|
|
O RAM
|
|
objects Borland C++'s use of 1
|
|
far recursive functions
|
|
class names 9 memory models and 9
|
|
declaring 9 registers
|
|
option pragma and 9 '86 2-3
|
|
offsets AX 2
|
|
component of a pointer 11 base point 2
|
|
option pragma BP 2
|
|
far objects and 9 BX 2
|
|
out of memory error 1 CS 4
|
|
overflows CX 2
|
|
flag 3 DI 2
|
|
DS 4
|
|
DX 2
|
|
P ES 4
|
|
parity flag 3 flags 2, 3
|
|
pointers index 2
|
|
arithmetic 5 IP (instruction pointer) 2
|
|
changing memory models and 11 LOOP and string instruction 2
|
|
|
|
|
|
|
|
- 15 -
|
|
|
|
|
|
|
|
|
|
|
|
|
|
math operations 2 SP register 2
|
|
segment 2, 4 special-purpose registers ('86) 2
|
|
SI 2 SS register 4
|
|
SP 2 _ss (keyword) 7
|
|
special-purpose 2 stack
|
|
SS 4 pointers 2
|
|
segment 4
|
|
strings
|
|
S instructions
|
|
_seg (keyword) 7, 8 registers 2
|
|
segment descriptors 2
|
|
segment:offset address notation
|
|
making far pointers from 11 T
|
|
segmented memory architecture 4 TLINK (linker)
|
|
segments 5 using directly 12
|
|
component of a pointer 11 trap flag 3
|
|
memory 4
|
|
pointers 7, 8
|
|
registers 2, 4 Z
|
|
selectors 2 zero flag 3
|
|
SI register 2 -zX options (code and data
|
|
sign segments) 9
|
|
flag 3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Index 16
|
|
|