An effective address is any operand to an instruction which references memory. Effective addresses, in NASM, have a very simple syntax: they consist of an expression evaluating to the desired address, enclosed in square brackets. For example:
wordvar dw 123
mov ax,[wordvar]
mov ax,[wordvar+1]
mov ax,[es:wordvar+bx]
Anything not conforming to this simple system is not a valid memory reference in NASM, for example es:wordvar[BX].
More complicated effective addresses, such as those involving more than one register, work in exactly the same way:
mov eax,[ebx*2+ecx+offset]
mov ax,[bp+di+8]
NASM is capable of doing algebra on these effective addresses, so that things which don't necessarily look legal are perfectly all right:
mov eax,[ebx*5] ; assembles as [ebx*4+ebx]
mov eax,[label1*2-label2] ; ie [label1+(label1-label2)]
Some forms of effective address have more than one assembled form; in most such cases NASM will generate the smallest form it can. For example, there are distinct assembled forms for the 32-bit effective addresses [EAX*2+0] and [EAX+EAX], and NASM will generally generate the latter on the grounds that the former requires four bytes to store a zero offset.
NASM has a hinting mechanism which will cause [EAX+EBX] and [EBX+EAX] to generate different opcodes; this is occasionally useful because [ESI+EBP] and [EBP+ESI] have different default segment registers.
However, you can force NASM to generate an effective address in a particular form by the use of the keywords BYTE, WORD, DWORD and NOSPLIT. If you need [EAX+3] to be assembled using a double-word offset field instead of the one byte NASM will normally generate, you can code [dword EAX+3]. Similarly, you can force NASM to use a byte offset for a small value which it hasn't seen on the first pass (see Section 5.8 for an example of such a code fragment) by using [byte EAX+offset]. As special cases, [byte EAX] will code [EAX+0] with a byte offset of zero, and [dword EAX] will code it with a double-word offset of zero. The normal form, [EAX], will be coded with no offset field.
The form described in the previous paragraph is also useful if you are trying to access data in a 32-bit segment from within 16 bit code. In particular, if you need to access data with a known offset that is larger than will fit in a 16-bit value, if you don't specify that it is a dword offset, NASM will cause the high word of the offset to be lost.
Similarly, NASM will split [EAX*2] into [EAX+EAX] because that allows the offset field to be absent and space to be saved; in fact, it will also split [EAX*2+offset] into [EAX+EAX+offset]. You can combat this behaviour by the use of the NOSPLIT keyword: [nosplit EAX*2] will force [EAX*2+0] to be generated literally.