3.2.4 Indirect Memory Operands

Like direct memory operands, indirect memory operands specify the contents of a given address. However, the processor calculates the address at run time by referring to the contents of registers. Since values in the registers can change at run time, indirect memory operands provide dynamic access to memory.

Indirect memory operands make possible run-time operations such as pointer indirection and dynamic indexing of array elements, including indexing of multidimensional arrays.

Strict rules govern which registers can be used for indirect memory operands under 16-bit versions of the 8086-based processors. The rules change significantly for 32-bit processors starting with the 80386. However, the new rules apply only to code that does not need to be backward compatible.

This section first discusses features of indirect operands in either mode. Then it explains the specific 16-bit rules and 32-bit rules separately.

3.2.4.1 Indirect Operands with 16- and 32-Bit Registers

Some rules and options for indirect memory operands always apply, regardless of the size of the register. For example, you must always specify the register and operand size for indirect memory operands. But you can use various syntaxes to indicate an indirect memory operand. This section describes the rules that apply to both 16-bit and 32-bit register modes.

Summary: Certain rules govern the use of base and index registers.

Specifying Indirect Memory Operands

The index operator specifies the register or registers for indirect operands. The processor uses the data pointed to by the register. For example, the following instruction moves the word-sized data at the address contained in DS:BX into AX:

mov ax, WORD PTR [bx]

When you specify more than one register, the processor adds the two addresses together to determine the effective address (the address of the data to operate on):

mov ax, [bx+si]

Summary: An indirect memory operand can have a displacement.

Specifying Displacements

You can specify an address displacement— a constant value to add to the effective address. A direct memory specifier is the most common displacement:

mov ax, table[si]

In the relocatable expression above, the displacement table is the base address of an array; SI holds an index to an array element. The SI value is calculated at run time, often in a loop. The element loaded into AX depends on the value of SI at the time the instruction is executed.

Each displacement can be an address or numeric constant. If there is more than one displacement, the assembler adds them together at assembly time and encodes the total displacement. For example, in the statement

table WORD 100 DUP (0)

.

.

.

mov ax, table[bx][di]+6

both table and 6 are displacements. The assembler adds the value of table to 6 to get the total displacement. However, this statement is not legal:

mov ax, mem1[si] + mem2

Summary: Indirect memory operands must always have a size.

Specifying Operand Size

Indirect memory operands must always have a specified size. Often the size is specified by the size of the identifier. In the example above, the size of the table array determines the operand size. If an indirect memory operand is used with a register operand, the register size determines the size of the memory object:

mov ax, [bx] ; Size is 2 bytes - same as AX

mov table[bx], 0 ; Size is 2 bytes - from size

; of table

If there is no address or register operand, the size must be given specifically with the PTR operator, as shown below:

inc WORD PTR [bx] ; Word size

mov BYTE PTR [bp+6], 0 ; Byte size

Syntax Options

The assembler allows a variety of syntaxes for indirect memory operands. However, all registers must be inside brackets. You can enclose each register in its own pair of brackets, or you can place the registers in the same pair of brackets separated by a plus operator (+). All the following variations are legal and equivalent:

mov ax, table[bx][di]

mov ax, table[di][bx]

mov ax, table[bx+di]

mov ax, [table+bx+di]

mov ax, [bx][di]+table

All of these statements move the value in table indexed by BX+DI into AX.

Summary: Registers pointing into arrays must be zero-based and scaled for the size of the array.

Scaling Indexes

The value of index registers pointing into arrays must often be adjusted for zero-based arrays and scaled according to the size of the array items. For a word array, the item number must be multiplied by two (shifted left two places). When you are using 16-bit registers, scaling must be done with separate instructions, as shown below:

mov bx, 5 ; Get sixth element (adjust for 0)

shl bx, 1 ; Scale by two (word size)

inc wtable[bx] ; Increment sixth element in table

When using 32-bit registers on the 80386/486 processor, you can include scaling in the operand, as described in Section 3.2.4.3, “Indirect Memory Operands with 32-Bit Registers.”

Accessing Structure Elements

The structure member operator can be used in indirect memory operands to access structure elements. In this example, the structure member operator loads the year field of the fourth element of the students array into AL:

STUDENT STRUCT

grade WORD ?

name BYTE 20 DUP (?)

year BYTE ?

STUDENT ENDS

students STUDENT < >

.

. ; Assume array initialized

. ; earlier

mov bx, OFFSET students ; Point to array of students

mov ax, 4 ; Get fourth element

mov di, SIZE STUDENT ; Get size of STUDENT

mul di ; Multiply size times

; elements to point to

; current element

; Load field from element:

mov al, (STUDENT PTR[bx+di]).year

See Section 5.2 for more information on MASM structures.

3.2.4.2 Indirect Memory Operands with 16-Bit Registers

For 8086-based computers and DOS, you must follow the strict indexing rules established for the 8086 processor. Only four registers are allowed—BP, BX, SI, and DI—and those only in certain combinations.

BP and BX are base registers. SI and DI are index registers. You can use either a base or an index register by itself. But if you combine two registers, one must be a base and one an index. Here are legal and illegal forms:

mov ax, [bx+di] ; Legal

mov ax, [bx+si] ; Legal

mov ax, [bp+di] ; Legal

mov ax, [bp+si] ; Legal

; mov ax, [bx+bp] ; Illegal - two base registers

; mov ax, [di+si] ; Illegal - two index registers

Table 3.1 shows the modes in which registers can be used to specify indirect memory operands.

Table 3.1 Indirect Addressing Modes with 16-Bit Registers

Mode Syntax Effective Address

Register indirect [BX] [BP] [DI] [SI] Contents of register

Base or index displacement[BX] displacement[BP] displacement[DI] displacement[SI] Contents of register plus displacement

Base plus index [BX][DI] [BP][DI] [BX][SI] [BP][SI] Contents of base register plus contents of index register

Base plus index with displacement displacement[BX][DI] displacement[BP][DI] displacement[BX][SI] displacement[BP][SI] Sum of base register, index register, and displacement

Different combinations of registers and displacements have different timings, as shown in the Macro Assembler Reference.

3.2.4.3 Indirect Memory Operands with 32-Bit Registers

Instructions for the 80386/486 processor can be given in two segment modes—16-bit and 32-bit. Indirect memory operands are different in each mode. The segment mode is independent of the register size; you can use 32-bit registers in either mode.

In 16-bit mode, the 80386/486 operates in the mode used by all other 8086-based processors, with one difference: you can use 32-bit registers. If the 80386/486 processor is enabled (with the .386 or .486 directive), 32-bit general-purpose registers are available in either segment mode. Using them eliminates many of the limitations of 16-bit indirect memory operands. Using 80386/486 features can make your DOS programs run faster and more efficiently if you are willing to sacrifice backward compatibility with other processors.

In 32-bit mode, an offset address can be up to four gigabytes. (Segments are still represented in 16 bits.) This effectively eliminates size restrictions on each segment, since few programs need four gigabytes of memory. OS/2 2.x uses 32-bit mode and flat model, which spans all segments. XENIX 386 uses 32-bit mode with multiple segments.

Summary: Any general-purpose 32-bit register can be used as either the base or the index.

80386/486 Enhancements

On the 80386/486, the processor allows any general-purpose 32-bit register to be used as either the base or the index register (except ESP, which can be a base but not an index). The same register can also be used as both the base and index, but you cannot combine 16-bit and 32-bit registers. Several examples are shown below:

add edx, [eax] ; Add double

mov dl, [esp+10] ; Add byte from stack

dec WORD PTR [edx][eax] ; Decrement word

cmp ax, array[ebx][ecx] ; Compare word from array

jmp FWORD PTR table[ecx] ; Jump into pointer table

Summary: The index register can have a scaling factor of 1, 2, 4, or 8.

Scaling Factors

With 80386/486 registers, the index register can have a scaling factor of 1, 2, 4, or 8. Any register except ESP can be the index register and can have a scaling factor. Specify the scaling factor by using the multiplication operator (*) adjacent to the register.

You can use scaling to index into arrays with different sizes of elements. For example, the scaling factor is 1 for byte arrays (no scaling needed), 2 for word arrays, 4 for doubleword arrays, and 8 for quadword arrays. There is no performance penalty for using a scaling factor. Scaling is illustrated in the following examples:

mov eax, darray[edx*4] ; Load double of double array

mov eax, [esi*8][edi] ; Load double of quad array

mov ax, wtbl[ecx+2][edx*2] ; Load word of word array

Scaling is also necessary on earlier processors, but it must be done with separate instructions before the indirect memory operand is used, as described in Section 3.2.4.2, “Indirect Memory Operands with 16-Bit Registers.”

Summary: The number of registers and the scaling factor affect base and index registers.

The default segment register is SS if the base register is EBP or ESP; it is DS for all other base registers. If two registers are used, only one can have a scaling factor. The register with the scaling factor is defined as the index register. The other register is defined as the base. If scaling is not used, the first register is the base. If only one register is used, it is considered the base for deciding the default segment unless it is scaled. The following examples illustrate how to determine the base register:

mov eax, [edx][ebp*4] ; EDX base (not scaled - seg DS)

mov eax, [edx*1][ebp] ; EBP base (not scaled - seg SS)

mov eax, [edx][ebp] ; EDX base (first - seg DS)

mov eax, [ebp][edx] ; EBP base (first - seg SS)

mov eax, [ebp*2] ; EBP base (only - seg SS)

Mixing 16-Bit and 32-Bit Registers

Statements can mix 16-bit and 32-bit registers if the register use is correct. For example, the following statement is legal for either 16-bit or 32-bit segments:

mov eax, [bx]

This statement moves the 32-bit value pointed to by BX into the EAX register. Although BX is a 16-bit pointer, it can still point into a 32-bit segment.

However, the following statement is never legal, since the CX register cannot be used as a 16-bit pointer (although ECX can be used as a 32-bit pointer):

; mov eax, [cx] ; illegal

Operands that mix 16-bit and 32-bit registers are also illegal:

; mov eax, [ebx+si] ; illegal

The following statement is legal in either mode:

mov bx, [eax]

This statement moves the 16-bit value pointed to by EAX into the BX register. This works fine in 32-bit mode. However, in 16-bit mode, moving a 32-bit pointer into a 16-bit segment is illegal. If EAX contains a 16-bit value (the top half of the 32-bit register is 0), the statement works. However, if the top half of the EAX register is not 0, the operand points into a part of the segment that doesn't exist, and this generates an error. If you use 32-bit registers as indexes in 16-bit mode, you must make sure that the index registers contain valid 16-bit addresses.