digitalmars.com                        
Last update Sun Mar 4 11:58:07 2018

16 Bit Pointer Types and Type Modifiers

Digital Mars C++ provides the following keywords to modify pointer types:
__cs, __far,
__handle, __huge,
__near, __ss,
__interrupt 
These keywords provide support for mixed language programming and for mixed memory models. Although they are not necessary for all applications, their judicious use can significantly enhance program performance.

Note: The keywords __far, __interrupt, __handle, __loadds, and __huge are ignored by default in compilations using the Win32 (-mn) memory model. Ignore these keywords in any compilation by specifying the -NF compiler option.

__handle

This extension to the normal pointer type allows access to memory that can only be accessed through indirection (a handle). It is used for the implementation of simulated virtual memory.

As with other pointer modifiers, __handle is placed immediately before the * in a pointer type description. For example:

int __handle *ptr; 
For more information, see Using Handle Pointers.

__huge

Huge pointers are supported through the __huge keyword. They are identical to __far pointers, except that they allow access to data objects larger than 64KB. To accomplish access to large data objects, the compiler inserts extra code so that __huge pointers will not roll over when a segment boundary is reached.

Note: Digital Mars C++ does not support the Huge memory model.

For more information see Choosing a Memory Model.

Using __near and __far with pointers

Override the default pointer type for data pointers by using the __near or __far keywords. For example:
int __far *p;	// p is a <far pointer to><int> 
int * __far *p;	// p is a <far pointer to><pointer to><int> 
The __near and __far keywords are right-associative. In other words, they associate with the * on their right. Remember that <pointer to> becomes <near pointer to> for the Tiny, Small, Medium, DOSX, and Phar Lap models, and a <far pointer to> for the Compact, and Large models.

Converting between near and far pointers

Convert a far pointer to a near pointer as follows:
#include <dos.h>
char __near *np, __far *fp; 
np = (char __near *) fp; 
Note: FP_SEG(fp) must equal DS!

Convert a near pointer to far pointer as follows:

#include <dos.h>
char __near *np, __far *fp; 
fp = MK_FP(getDS(), (unsigned)np); 
Notice how the value of DS is obtained for constructing the __far pointer.

Using Extended Keywords with const and volatile

ANSI C has introduced the const and volatile keywords as type modifiers. Unfortunately, their syntax is different from that of the extended keywords const and volatile bind to the left, while the extended keywords bind to the right. This is an historical accident and cannot be changed. Some examples of the difference include:
int * const p; // const pointer to int
int __handle * p; // handle pointer to int 
int * const *p; // pointer to a const pointer to int 
int * __far *p; // far pointer to a pointer to int 
A declaration with the specifier volatile tells the compiler that the declared object changes in an undetectable way. These objects are not optimized.

ARM p. 110, Gray p. 521

__far, __near, __ss, and __cs

These keywords support mixed memory model programming. They are generally used with pointer types to let the programmer access data that would normally be inaccessible in the memory model in use. They are also used to provide greater efficiency. The __near and __far keywords can also be used to qualify functions and __far may be used with data.

The sizes of these pointer types are different and will depend on the memory model in use. For more information, see the section "Sizes and Addressability of Pointers" later in this chapter.

For more information see Choosing a Memory Model.

__loadds

This keyword aid those programming Microsoft Windows. __loadds causes the compiler to load DS from DGROUP on entry to the function and to restore DS on exit. The -mu compiler option applies __loadds to all functions.

Interrupt Functions

Digital Mars C++ supports a very specialized function type called an interrupt function. It is used for creating interrupt handlers. Declare an interrupt function with the __interrupt keyword. Using the __cdecl, __far, or __loadds keywords is redundant when declaring an interrupt function; using the __near or __pascal keywords is an error.

Interrupt functions should not be inline or non-static member functions.

An interrupt function saves all the registers on the stack, reloads DS from DGROUP (so that global variables in the data segment can be accessed), and enables interrupts. At the end of the function, the registers are restored and an IRET instruction is executed.

An interrupt function is normally declared as:

void __interrupt isr(void); 
or:
void __interrupt isr( unsigned es, 
unsigned ds, unsigned di, 
unsigned si, unsigned bp, 
unsigned sp, unsigned bx, 
unsigned dx, unsigned cx, 
unsigned ax, unsigned ip, 
unsigned cs, unsigned flags); 
The second form allows access to the values the flags and registers had when the interrupt occurred.

Modifying these variables causes the corresponding register to have the modified value upon returning from the function. Be very sure of what you are doing if you modify the bp, sp, ip, cs or flags registers.

The SS, FS and GS registers are not saved; if they need to be, use the inline assembler to save and restore them. The floating-point registers on the numeric coprocessor are not saved automatically either.

Since an interrupt function sets the values of the registers upon function exit, using a:

return exp; 
statement will not have the expected effect. To return a value, set the appropriate register( s) to the value.

Do not call an interrupt function and pass parameters to it. To pass values to it, place the values into registers with the inline assembler, and then access the register values from within the interrupt function using the second form of the declaration above. If you do pass parameters, they will appear after the "flags" parameter.

If the interrupt function will be using a significant amount of stack, it may be necessary to switch to a separate stack of a known size. This can be done with the inline assembler. Note also that in small and medium model programs, SS will not be the same as DS inside the interrupt function.

Some guidelines for writing interrupt functions:

Alternatively, you can write an interrupt handler using the interrupt package in the run-time library. This package has the advantage of allowing you to designate a stack for the handler, and sets the stack correctly for small and medium model programs.

Portability of Extended Keywords

Except for __ss and __handle, the extended keywords are portable to other 16 bit compilers for MS-DOS and Windows. One useful method of ensuring portability to other systems is by conditionally defining the extended keywords as empty:
// Remove extended keywords for
// non Digital Mars compilers 

#if !defined(__DMC__)
#define __far 
#define __near
#define __cs 
#define __ss 
#define __handle
#endif 

Sizes and Addressability of Pointers

Digital Mars C++ supports the development of both 16-bit and 32-bit applications. In 16-bit applications, __near and __ss pointers are 16-bit, whereas __far and __handle pointers are 32-bit. In 32-bit applications, __near and __ss pointers are 32-bit, whereas __far and __handle pointers are 48-bit. Digital Mars C++ allows an int to be cast directly to a near pointer. Casting an int to a far pointer is not allowed and generates a syntax error. If you need to cast an int to a far pointer, you should first cast the int to a long and then cast the long to a far pointer.

For information on using different pointer types, see Choosing a Memory Model.

__near pointers

In 16-bit memory models (Tiny, Small, Compact, Medium, and Large memory models), near pointers consist of a 16-bit offset and can be used to access any location within a 64KB segment. Because they require only two bytes of storage to hold the offset, near pointers are more efficient than far pointers. Near pointers also require less time for access.

For near references to be correct when the processor is in real mode, the segment register DS must contain the correct segment value for data pointers. When the processor is in protected mode, the segment register must contain the correct selector value. CS contains the segment value for function pointers.

Near pointers are used for function pointers in the Tiny, Small, and compact models. Near pointers are the default in the Tiny, Small, and Medium memory models for data pointers.

In the 32-bit memory models (NT, DOSX, and Phar Lap), near pointers can be used to access any location within a 4GB segment. Each near pointer requires four bytes of storage to hold the offset. For near references to be correct, the segment register DS must contain the correct selector for data pointers. For function pointers, CS must contain the selector, rather than DS. Near pointers are the default for both code and data in 32-bit memory models.

__far pointers

In 16-bit memory models, far pointers require four bytes of storage. Two of the four bytes contain the segment value in real mode, or the selector value in protected mode. The other two bytes contain the offset. The segment is a base address that will be put into the correct segment register. The offset provides an index relative to the base address. When a far pointer is dereferenced, the ES register is loaded with the segment value. Far pointers are the default for data pointers in the Compact, and Large models and for function pointers in the Medium and Large models.

In 32-bit memory models, far pointers require six bytes: a two-byte selector and a four-byte offset. The selector is an index into the Global Descriptor Table. This index is put into the appropriate segment register. The offset provides an offset within the selected memory segment. In 32-bit programming, far pointers are only useful when working at operating system level.

Note: Never use far pointers when compiling with the NT (-mn) memory model.

__ss pointers

Digital Mars C++ provides an additional pointer type to allow the programmer to produce pointers to objects contained on the stack. Stack (__ss) pointers are offsets into the stack segment. In the Tiny, Small, Compact, Medium, and Large memory models this is a 16-bit offset, while in the DOSX, Phar Lap, and NT memory models it is a 32-bit offset. The value in SS contains the segment value in real mode and the selector value in protected mode.

When applied to a pointer, the __ss keyword is used exactly the same way as the __near keyword. However, the compiler generates code to ensure that the current segment address is set to the stack segment rather than the data segment. Consequently, __ss pointers are relative to the SS segment register as opposed to near pointers, which are relative to DS. If SS == DS, as in the Tiny, Small, Medium, DOSX, and Phar Lap models, then there is no difference between __ss pointers and near pointers, except for type checking. In the Compact and Large models, or if SS != DS, then __ss pointers can be used only to point to parameters and automatic variables. Under the same conditions, near pointers can point only to static and global data. (SS==CS==DS==ES at all times for programs compiled for the NT memory model.)

Some specialized applications require use of the Small or Medium memory models but also require that SS != DS. Similarly, SS != DS in applications which are put into ROM, Microsoft Windows applications, or routines for OS/ 2 dynamic link libraries, even when the Small and Medium memory models are used.

Force SS!=DS by appending a w to the -mmodel compiler option, for instance -msw. The __ss pointer then becomes quite important as auto variables can be accessed via two-byte __ss pointers rather than four-byte far pointers.

Pointer examples

An example using the Compact and Large models:
void func()
{   
  static int darray[10];// array in data seg
  int __near *pd; // pointer into data seg 
  int sarray[10]; // array in stack seg
  int __ss *ps; // pointer into stack seg 

  ps = sarray;
  for (pd = darray; pd &darray[10]; pd++, ps++)
    *ps = *pd; 
  pd = sarray; // Error: near pointer cannot access stack 
  ps = darray; // Error: vice versa
} 
Far pointers are useful in accessing specific memory locations in the computer. The video display memory from the Small model is one example:
#include <dos.h> 
unsigned short __far *pvideo;
int i; 

/* Point into mono display ram */
pvideo = (unsigned short __far *)MK_FP(0xB000,0); 

/* Clear screen */
for (i = 0; i< 25 * 80; i++) 
  pvideo[i] = 0x0700 + ' '; 

Pointers to Functions

A near pointer to a near function returning int is declared with:
int (__near *fp)(); 
A far pointer to a far Pascal function returning char is declared with:
char (__far __pascal *fp)(); 
Due to limitations of this syntax, it is impossible to declare constructs such as a near pointer to a far function or a far pointer to a near function. The compiler always selects the function type according to the type of pointer. This compiler selection cannot be overridden.

For more information on mixing pointer types, see Mixing Languages.

Home | Runtime Library | IDDE Reference | STL | Search | Download | Forums