3.3.3 Basic Pointer and Address Operations

You can do these basic operations with pointers and addresses:

Initialize a pointer variable by storing an address in it

Load an address into registers, directly or from a pointer

The sections in the rest of this chapter describe variations of these tasks with both pointers and addresses. The examples in these sections assume that you have previously defined the following pointer types with the TYPEDEF directive:

PBYTE TYPEDEF PTR BYTE ; Pointer to bytes

NPBYTE TYPEDEF NEAR PTR BYTE ; Near pointer to bytes

FPBYTE TYPEDEF FAR PTR BYTE ; Far pointer to bytes

3.3.3.1 Initializing Pointer Variables

Summary: Let the assembler initialize pointer variables when possible.

If the value of a pointer is known at assembly time, the assembler can initialize it automatically so that no processing time is wasted on the task at run time. The following example illustrates how to do this:

Msg BYTE "String", 0

pMsg PBYTE Msg

If a pointer variable can be conditionally defined to one of several constant addresses, initialization must be delayed until run time. The technique is different for near pointers than for far pointers, as shown below:

Msg1 BYTE "String1"

Msg2 BYTE "String2"

npMsg NPBYTE ?

fpMsg FPBYTE ?

.

.

.

mov npMsg, OFFSET Msg1 ; Load near pointer

mov WORD PTR fpMsg[0], OFFSET Msg2 ; Load far offset

mov WORD PTR fpMsg[2], SEG Msg2 ; Load far segment

If you know that the segment for a far pointer is currently in a register, you can load it directly:

mov WORD PTR fpMsg[2], ds ; Load segment of

; far pointer

Dynamic Addresses

Often the address to be initialized is dynamic. You know the register or registers containing the address, and you want to save them in a variable for later use. Typical situations include memory allocated by DOS (see interrupt 21h function 48h in online help) and addresses found by the SCAS or CMPS instructions (see Section 5.1.3.1). The technique for saving dynamic addresses is illustrated below:

; Dynamically allocated buffer

fpBuf FPBYTE 0 ; Initialize so offset will be zero

.

.

.

mov ah, 48h ; Allocate memory

mov bx, 10h ; Request 16 paragraphs

int 21h ; Call DOS

jc error ; Return segment in AX

mov WORD PTR fpBuf[2], ax ; Load segment

. ; (offset is already 0)

.

.

error: ; Handle error

Summary: There are several options for copying pointers.

Copying Pointers

Sometimes one pointer variable must be initialized by copying from another. Here are two ways to copy a far pointer:

fpBuf1 FPBYTE ?

fpBuf2 FPBYTE ?

.

.

.

; Copy through registers is faster, but requires a spare register

mov bx, WORD PTR fpBuf1[0]

mov WORD PTR fpBuf2[0], bx

mov bx, WORD PTR fpBuf1[2]

mov WORD PTR fpBuf2[2], bx

; Copy through stack is slower, but does not use a register

push WORD PTR fpBuf1[0]

push WORD PTR fpBuf1[2]

pop WORD PTR fpBuf2[2]

pop WORD PTR fpBuf2[0]

Summary: Pointers passed as procedure arguments are pushed onto the stack.

Pointers as Arguments

When a pointer is passed as an argument to a procedure, it must be pushed onto the stack. The procedure then sets up a stack frame so that it can access the arguments from the stack. This technique is discussed in detail in Section 7.3.2, “Passing Arguments on the Stack.” Pushing a pointer is illustrated below:

; Push a far pointer (segment always pushed first)

push WORD PTR fpMsg[2] ; Push segment

push WORD PTR fpMsg[0] ; Push offset

Pushing an address is somewhat different:

; Push a far address as a far pointer

mov ax, SEG fVar ; Load and push segment

push ax

mov ax, OFFSET fVar ; Load and push offset

push ax

On the 80186 and later processors, you can shorten pushing a constant to one step:

push SEG fVar ; Push segment

push OFFSET fVar ; Push offset

3.3.3.2 Loading Addresses into Registers

Loading an address into a pair of registers is one of the most common tasks in assembly-language programming. You cannot do processing work with a constant address or a pointer variable until the address is loaded into registers.

Summary: Certain register pairs have standard uses.

You often load addresses into particular segment:offset pairs. The following pairs have specific uses:

Segment:Offset Pair Standard Use

DS:SI Source for string operations
ES:DI Destination for string operations
DS:DX Input for DOS functions
ES:BX Output from DOS functions

In addition, you can use ES:SI, DS:DI, DS:BX, or any segment:offset pair for your own indirect memory operands. You can use SS:BP with a displacement to access procedure arguments or local variables in procedures.

Addresses from Data Segments

For near addresses, you need only load the offset; the segment is assumed as SS for stack-based data and as DS for other data. You must load both segment and offset for far pointers.

Here is an example of loading an address to DS:BX from a near data segment:

.DATA

Msg BYTE "String"

.

.

.

mov bx, OFFSET Msg ; Load address to BX

; (DS already loaded)

If the data is in a far data segment, it is loaded like this:

.FARDATA

Msg BYTE "String"

.

.

.

mov ax, SEG Msg ; Load address to ES:BX

mov es, ax

mov bx, OFFSET Msg

Stack Variables

The technique for loading the address of a stack variable is significantly different from the technique for loading near addresses. You may need to put the correct segment value into ES for string operations. The following example illustrates how to load the address of a local (stack) variable to ES:DI:

Task PROC

LOCAL Arg[4]:BYTE

push ss ; Since it's stack-based, segment is SS

pop es ; Copy SS to ES

lea di, Arg ; Load offset to DI

Summary: Use LEA to load the offset of an indirect memory operand.

The local variable in this case actually evaluates to SS:[BP-4]. This is an offset from the stack frame (described in Section 7.3.2, “Passing Arguments on the Stack”). Since you cannot use the OFFSET operator to get the offset of an indirect memory operand, you must use the LEA (Load Effective Address) instruction.

Summary: Use MOV and OFFSET to load the offset of a direct memory operand.

Direct Memory Operands

To get the address of a direct memory operand, you can use the MOV instruction with OFFSET or the LEA instruction. MASM 6.0 automatically optimizes the LEA statement by generating the smaller and faster code, as shown in this example:

lea si, Msg ; If you code this statement,

mov si, OFFSET Msg ; MASM 6.0 generates this code

The LEA instruction can be used to determine the address of indirect memory operands, as shown below.

lea si, [bx] ; Legal - LEA required for indirect

; mov si, OFFSET [bx] ; Illegal - no OFFSET on indirect

Far Pointers

Use the LES and LDS instructions to load far pointers. Use the MOV instruction to load a near pointer. The following example shows how to load a far pointer to ES:DI and a near pointer to SI (assuming DS as the segment):

InBuf BYTE 20 DUP (1)

OutBuf BYTE 20 DUP (0)

npIn NPBYTE InBuf

fpOut FPBYTE OutBuf

.

.

.

les di, fpOut ; Load far pointer to ES:DI

mov si, npIn ; Load near pointer to SI (assume DS)

Copying between Segment Pairs

Copying from one register pair to another is complicated by the fact that you cannot copy one segment register directly to another. Two methods are shown below. Timings are for the 8088 processor:

; Copy DS:SI to ES:DI, generating smaller code

push ds ; 1 byte, 14 clocks

pop es ; 1 byte, 12 clocks

mov di, si ; 2 bytes, 2 clocks

; Copy DS:SI to ES:DI, generating faster code

mov di, ds ; 2 bytes, 2 clocks

mov es, di ; 2 bytes, 2 clocks

mov di, si ; 2 bytes, 2 clocks

3.3.3.3 Model-Independent Techniques

Summary: Use conditional assembly to write memory-model independent code.

Often you may want to write code that is memory-model independent. If you are writing libraries that must be available for different memory models, you can use conditional assembly to handle different sizes of pointers. You can use the predefined symbols @DataSize and @Model to test the current assumptions.

Summary: Use conditional assembly to handle pointers that have no specified distance.

You can use conditional assembly to write code that works with pointer variables that have no specified distance. The predefined symbol @DataSize tests the pointer size for the current memory model:

Msg1 BYTE "String1"

pMsg PBYTE ?

.

.

.

IF @DataSize

mov WORD PTR pMsg[0], OFFSET Msg1 ; Load far offset

mov WORD PTR pMsg[2], SEG Msg1 ; Load far segment

ELSE

mov pMsg, OFFSET Msg1 ; Load near pointer

ENDIF

In the following example, a procedure receives as an argument a pointer to a word variable. The code inside the procedure uses @DataSize to determine whether the current memory model supports far or near data. It loads and processes the data accordingly:

; Procedure that receives an argument by reference

mul8 PROC arg:PTR WORD

IF @DataSize

les bx, arg ; Load far pointer to ES:BX

mov ax, es:[bx] ; Load the data pointed to

ELSE

mov bx, arg ; Load near pointer to BX (assume DS)

mov ax, [bx] ; Load the data pointed to

ENDIF

shl ax, 1 ; Multiply by 8

shl ax, 1

shl ax, 1

ret

mul8 ENDP

If you have many routines, writing the conditionals for each case can be tedious. The following conditional statements generate the proper instructions and segment overrides automatically.

; Equates for conditional handling of pointers

IF @DataSize

lesIF TEXTEQU <les>

ldsIF TEXTEQU <lds>

esIF TEXTEQU <es:>

ELSE

lesIF TEXTEQU <mov>

ldsIF TEXTEQU <mov>

esIF TEXTEQU <>

ENDIF

Once you define these conditionals, you can use them to simplify code that must handle several types of pointers. This next example rewrites the above mul8 procedure to use conditional code.

mul8 PROC arg:PTR WORD

lesIF bx, arg ; Load pointer to BX or ES:BX

mov ax, esIF [bx] ; Load the data from [BX] or ES:[BX]

shl ax, 1 ; Multiply by 8

shl ax, 1

shl ax, 1

ret

mul8 ENDP

The conditional statements from the examples above can be defined once in an include file and used whenever you need to handle pointers.