Based addressing is useful when you need to:
Exercise precise control over the segment in which objects are allocated (static and dynamic based data).
Reference far objects using a 16- rather than a 32-bit address. This can decrease the size of an executable file while increasing its speed.
Store pointers to memory not allocated by your program—for example, ROM data.
The only form of based addressing acceptable in 32-bit compilations is “based on a pointer” which defines a type that contains a 32-bit displacement to a 32-bit base.
based-range-modifier:
__based ( base-expression)
base-expression:
base-constant
based-variable
based-abstract-declarator
__self
base-constant:
segment-name
segment-cast
based-variable:
identifier
based-abstract-declarator:
abstract-declarator
base-type:
type-name
segment-name:
__segment ( string-literal )
segment-cast:
(__segment ) pointer-id
(__segment )&identifier
The keywords and operators in the following list have been added to the language to support based addressing:
Keyword or Operator | Use |
__segment | A type that contains a segment value. |
__segname | A built-in function that returns type __segment. The __segname function can be used in declarations to initialize variables of type __segment, or it can be used in declarations of based objects or pointers. |
__self | The result of the __self keyword is a far pointer to the segment where a pointer is stored. Basing a pointer on __self avoids the segment-register reloads implicit in storing the pointer and object in different segments. |
:> | Base operator. The base operator combines a segment and an address that can be dereferenced using the * operator. |
Based pointers are short (16-bit) offsets from a segment base. The effective address is calculated using the formula:
effective address = base + pointer value
In 32-bit target compilations, based pointers are 32-bit offsets from a 32-bit base.¨
When dereferencing a based pointer, the base must either be explicitly specified or implicitly known through the declaration. The following code shows some example declarations of based pointers:
// Binary tree
struct BTree
{
char *szSymbolName;
BTree *btLeft;
BTree *btRight;
};
// Declare a pointer to data that resides in segment SYM_DATA.
BTree __based( __segname( "SYM_DATA" ) ) *btSymTable1;
// Declare a pointer to data that resides in segment 0x7000.
__segment SegVar = 0x7000;
BTree __based( SegVar ) *btSymTable2;
// Declare a pointer to data that resides in the same segment
// as the pointer btSymTable3.
BTree __based( (__segment)__self ) *btSymTable3;
A based pointer can be based on the following:
A constant. A pointer can be based on an expression that evaluates to a constant. These constants can be supplied as segment names using the __segname operator.
A variable segment. A pointer based on a variable segment is specified using an expression that evaluates type to __segment but that does not evaluate to a constant.
Self. A pointer based on self is specified using the __self function. It causes the pointer to refer only to objects in its own segment.
Void. A pointer based on void has no implicit base; the base must be supplied at the point of dereference.
A pointer. A pointer based on a pointer allows the base to be manipulated at run time.
Pointers based on a constant are specified in several ways:
In the following example, sgConst is explicitly declared as a constant. The pointer bp1, therefore, is based on a constant.
const __segment sgConst = 0x3000;
char __based( sgConst ) *bp1;
In the following example, the __segname function returns a constant value of type __segment. The pointer bp3, therefore, is based on a constant.
char __based( __segname("INFO_STRINGS") ) *bp3;
Pointers Based on a Segment Variable
Pointers based on a segment variable require that a variable of type __segment be declared. This variable determines what segment the based pointer refers to and can be changed at run time. The following uses a pointer, pbv, based on a segment variable, sgVar:
#include <iostream.h>
__segment sgVar;
void __based( sgVar ) *pbv;
int main()
{
static __segment sgList[] = // Initialize a static array
{ // with the CODE, DATA, and
__segname( "_CODE" ), // STACK segment values.
__segname( "_DATA" ),
__segname( "_STACK" )
};
static char *aszSegNames[] = // Initialize an array of
{ // strings with the names
"_CODE", // of the above segments.
"_DATA",
"_STACK"
};
// Calculate the size of the array.
int cSegments = sizeof( sgList ) / sizeof ( __segment );
// Dump the first 256 bytes of each segment.
for( int i = 0; i < cSegments; ++i )
{
cout << "Segment: " << aszSegNames[i] << "\n\n";
// Set the variable on which pbv is based to point to
// the ith segment in the list.
sgVar = sgList[i];
// Initialize pbv to point to the
// initial byte of the current
// segment (sgList[i]:0000).
for( int row = 0, pbv = 0; row < 16; ++row )
{
for( int col = 0; col < 16; ++col, pbv++ )
cout << hex << *(unsigned*)pbv << " ";
for( col = 0, pbv -= 16; col < 16; ++col, pbv++ )
cout << isprint(*(char*)pbv) ? *(char*)pbv : '.';
cout << "\n";
}
}
return 0;
}
Pointers based on self can access data anywhere in the segment in which the pointer resides. They are declared using the __self function cast to the __segment type as the base-expression. You can base a pointer only on (__segment)__self. You cannot base a pointer on __self alone. Basing a pointer on self can improve program performance by requiring that the segment register be the same for addressing both the pointer and the data it addresses.
Note:
While both the pointer and the data are in the same segment, some segment-register reloads can be forced by such operations as conversions, references to other data, and function calls.
Pointers based on self commonly refer to objects allocated using the new operator. For example:
// Declare the pointer; the actual segment is unimportant.
char __based( (__segment)__self ) *pbch;
// Allocate storage for objects.
pbch = new char __based( (__segment)__self )[1024];
Pointers based on self are particularly useful for optimizing access in self-referential data structures such as linked lists and trees.
Functions cannot return pointers based on self. Also, register variables cannot be based on self. Only addressable l-values can be converted to pointers based on self.
Since only l-values can be converted to pointer types based on self, you cannot assign a constant to a pointer based on self. Consider the following code:
int __based((__segment)__self) *piself;
piself = 1;
Before performing the assignment, the compiler attempts to convert 1, which is of type const int, to type int __based(void) *, which is an illegal conversion. To put the value 1 in the pointer piself, you first must cast 1 to a pointer based on void, as follows:
piself = (int __based(void)*)1;
Pointers based on self are not supported in 32-bit target compilations.¨
Pointers based on void defer address calculation until the pointer is dereferenced. Unlike most other forms of based pointers, a pointer that is based on void has no implied segment as its base.
The segment specified at the point of dereference can be a constant or a segment variable. It is combined with the offset using the base operator (:>) to form an address that can be dereferenced using the indirection (*) operator.
The following example shows how to declare and dereference a pointer based on void.
struct BiosEquipList // Structure for the BIOS Equipment List
{ // that starts at 0000:0410 (hex).
...
};
// Declare ROM data as const and supply the offset, hex 410.
const BiosEquipList __based( void ) *bpelROM = 0x410;
int main()
{
BiosEquipList elLocal; // Local copy of equipment list.
// Make a local "shadow" copy of the BIOS equipment list.
elLocal = *((__segment)0x0000 :> bpelROM);
return 0;
}
The “based on pointer” variant of based addressing enables specification of a pointer as a base-expression. The based pointer, then, is an offset into the segment starting at the beginning of the pointer on which it is based.
One use for pointers based on pointers is for persistent objects that contain pointers. A linked list that consists of pointers based on pointers can be saved to disk and reloaded to another place in memory, and the pointers are still valid. The following example declares such a linked list:
void *vpBuffer;
struct llist_t
{
void __based( vpBuffer ) *vpData;
llist_t __based( vpBuffer ) *llNext;
};
The pointer, vpBuffer, is assigned the address of memory allocated at some later point in the program; the linked list is then relocated relative to the value of vpBuffer.
Pointers based on pointers are the only form of __based valid in 32-bit compilations. In such compilations, they are 32-bit displacements from a 32-bit base.¨
Static and external objects can be declared using the __based keyword. In this context, the __based specification causes the object to be allocated in the specified segment. The following example shows how to declare a based object:
Class Symbol
{
...
};
Symbol __based( __segname( "SYM_DATA" ) ) Symbol;
The preceding declaration allocates one object of type Symbol in segment SYM_DATA. Similarly, arrays of objects can be declared as based:
Symbol __based( __segname( "SYM_DATA" ) ) Symbol[3000];
If a function is to be allocated in a given segment, it can be declared as based on a segment constant. No other forms of based declarations are accepted for functions. Consider the following example:
char __based(__segname("STRING_TEXT"))
__near *StringCompare( char __near *String1, char __near *String2 );
char __based(__segname("STRING_TEXT"))
__far *FStringCompare( char __far *String1, char __far *String2 );
In the preceding declaration, both StringCompare and FStringCompare are allocated in the segment STRING_TEXT. However, the segment in which the functions are allocated does not affect the calling protocol specified by the __near and __far keywords. In the preceding example, StringCompare can be called only by other functions in the same segment, and it is accessed using a near (intrasegment) call. The FStringCompare function, however, is declared as __far, and can be accessed from any function in the program using a far call.
Note:
Declaring functions as __based replaces the alloc_text pragma. However, the alloc_text pragma is retained for backward compatibility.
This application of __based is not used for disambiguation of overloaded functions. Therefore, the following two function declarations conflict:
char __based(__segname("FAR_TEXT")) Print( int iValue );
char __based(__segname("NEAR_TEXT")) Print( int iValue );
The compiler recognizes the suffix _TEXT as a code segment designation. Therefore, any function based on a segment that has the _TEXT suffix is placed in a code segment. If you choose a segment name that does not have the _TEXT suffix, however, the segment chosen is a data segment.
The __segment type is used either at the point of declaration of a based type or at the point of a based dereference to specify the segment in which the based object resides. If specified at the point of declaration, the segment value is implicit and need not be respecified at the point of dereference (based pointers). Otherwise, the segment must be explicitly specified.
The following example shows specification of a segment in a declaration and at the point of dereference:
// Dump the first 256 bytes of the data segment (DS)
// using based pointers.
#include <iostream.h>
int main()
{
// Segment specified at point of declaration.
unsigned char __based( __segname( "_DATA" ) ) *puc = 0;
// Segment specification deferred until point of dereference.
// pv is a generic pointer to type unsigned char.
unsigned char __based( void ) *pv = 0;
int i, j;
for( i = 0; i < 16; ++i )
{
// Specify the segment at point of dereference.
for( j = 0; j < 16; ++j )
cout << hex << setw( 2 )
<< (unsigned)*(__segname("_DATA"):>pv++) << " ";
// Because the segment for puc was specified at the point
// of declaration, it can be used like a normal pointer.
for( j = 0, pv -= 16; j < 16; ++j )
cout << *puc++;
cout << "\n";
}
return 0;
}
Type __segment behaves like any other C++ type—derivative types can be formed using the pointer-to, array-of, and function-returning operators (*, [], and (), respectively).
An object of type __segment can be initialized with the following four items:
An expression that evaluates to an integral constant value.
The result of the built-in function __segname. This allows specification of a segment by name, causing the linker to insert segment fixups in the executable file, to be resolved at load time.
Another expression of type __segment. For example:
__segment sgCustomerData = 0x7000;
__segment sgCurrent = sgCustomerData;
Another expression explicitly cast to type __segment. The following list shows how various expressions are converted in explicit casts to type __segment:
Expression | Base |
Cast from pointer not explicitly declared with a memory-model modifier, Segment value for that pointer | ||
Cast from near pointer | Segment value of the default data segment | |
Cast from far pointer | Segment portion of the far pointer | |
Cast from based pointer | Base segment of based pointer | |
Cast from address value obtained using the address-of operator (&) on an object | Segment in which the object is located |
The __segment type is not supported in 32-bit compilations.¨
The built-in function __segname accepts a quoted string and returns a value of type __segment. A __segname declaration is not an l-value and its address cannot be taken.
The primary use for the __segname function is in initializing objects of type __segment or in declarations of pointers or objects of based type. It enables specification of a segment name instead of a segment value when declaring a based type. Consider the following example:
// Keep all message strings for the File.Open dialog box
// in the same segment.
char __based(__segname("ERR_STRINGS")) *szErrStr[] =
{
"Cannot find file: %s",
"Cannot open file: %s",
"Invalid path: %s",
"Drive %c: does not exist or is not ready",
...
};
Collecting like data into the same segment is beneficial in Windows programming because users are commonly performing a task—such as opening a file—that requires only a certain subset of the application's data. Microsoft Windows manages segment swapping; however, the way data is organized in those segments can greatly affect how often these disk-intensive swaps occur.
The following list shows the predefined segment-name constants you can use as arguments to the __segname function:
Name | Refers To |
_CODE | The default code segment |
_DATA | The default data segment |
_CONST | The default CONST segment |
_STACK | The stack segment |
Pointers can be declared as based on __self. This form of declaration defers calculation of the pointer value until the point of dereference. It also ensures that the object the pointer references is in the same segment as the pointer itself. For example:
char __based( (__segment)__self ) *pszMessage;
...
pszMessage = new char __based( __self )[1024];
if( pszMessage == NULL )
cerr << "Cannot allocate 1K buffer.\n";
else
// 1K buffer allocated in same segment as pointer.
In the preceding example, the pointer is initialized using the new operator.