010-014. 비간접 피연산자
즉시 피연산자

즉시 피연산자 — 상세
- 즉시값은 어셈블 시점에 바이너리 안에 직접 인코딩된다.
- 실행 시 메모리를 별도로 참조하지 않고, 명령어 바이트에서 바로 꺼낸다.
.DATA num DB 5 ; 메모리 공간 차지 O CONST EQU 9 ; 메모리 공간 차지 X — 어셈블 중 숫자로 치환됨 mov bx, 9+3 ; → mov bx, 12 (0Ch) — 어셈블러가 계산 mov cx, CONST+3 ; → mov cx, 12 (0Ch) — EQU가 9로 치환된 뒤 계산 add num, 3 ; num 주소의 값에 3을 더함 — 3이 즉시값 or bx, 12h ; → 12h가 바이너리에 그대로 cmp cx, 00110001b ; → 31h가 바이너리에 그대로 int 21h ; → 21h가 바이너리에 그대로바이너리에서 빨간 박스로 하이라이트된 바이트가 즉시 피연산자다.
명령어 바이너리 mov bx, 12BB0C 00mov cx, 12B90C 00add num, 380 06 14 0103or bx, 12h83 CB12cmp cx, 31h83 F931int 21hCD21 00
EQU Directive
EQU — 어셈블 타임 상수
EQU는 메모리를 전혀 할당하지 않는다.DB와의 차이가 핵심.
DB EQU 선언 num DB 5CONST EQU 9메모리 할당 ✅ 있음 ❌ 없음 동작 주소를 통해 값 참조 어셈블 중 숫자로 매크로처럼 복붙 결과 메모리 피연산자 즉시 피연산자로 인라인됨
CONST+3은 어셈블러가9+3 = 12로 계산해 바이너리에0C 00을 박는다. 런타임에는 CONST 라는 이름 자체가 존재하지 않는다.원본 링크Programmer's Guide.pdf, Chapter 1 (Page 12) - Symbolic Integer Constants
You can define symbolic integer constants with either of the data assignment directives,
EQUor the equal sign (=).The directives
EQUand=have slightly different purposes. Integers defined with=can be redefined, but those defined withEQUcannot. Once a symbolic constant is defined withEQU, redefining it generates an error.Syntax:
symbol EQU expression
6502와 비교
6502의
#즉시 주소와 동일한 개념. 값이 바이너리에 직접 들어간다.
6502 x86-16 표기 LDA #$0CMOV AX, 12바이너리 A9 0CB8 0C 00크기 제한 1바이트 고정 8bit / 16bit 상수 정의 없음 (직접 숫자) EQU로 이름 붙여 사용 가능
레지스터 피연산자

레지스터 직접 피연산자 (Register Direct Operand)
레지스터 이름 자체가 피연산자. 레지스터를 지칭하는 식별자가 바이너리에 인코딩된다. 메모리 참조 없이 CPU 내부 레지스터에서 바로 읽고 쓴다.
; ✅ 모든 피연산자가 레지스터 직접 add ax, bx ; AX ← AX + BX mov ds, ax ; DS ← AX cmp ah, bh ; AH 와 BH 비교
혼합 예시 — 피연산자 종류 구분
명령어 하나에 서로 다른 종류의 피연산자가 섞일 수 있다.
and num, dx ; num → 메모리 직접 / dx → 레지스터 직접 sub nums[bx+si], ax ; nums[bx+si] → 메모리 간접(베이스+색인) / ax → 레지스터 직접 shl ax, 1 ; ax → 레지스터 직접 / 1 → 즉시 cmp cx, counter ; cx → 레지스터 직접 / counter → 메모리 직접
단일 피연산자
단일 피연산자 명령어도 마찬가지로 레지스터를 피연산자로 사용한다.
mul bx ; bx → 레지스터 직접 pop cx ; cx → 레지스터 직접 inc ah ; ah → 레지스터 직접
6502와 비교
6502의
A(누산기) 표기와 동일한 개념.레지스터 자체를 피연산자로 지칭하고, 레지스터의 식별자가 opcode에 인코딩된다.
6502 x86-16 표기 A(누산기 고정)ax, bx, ah, si등 다수레지스터 수 A, X, Y — 3개 범용 + 색인 + 세그먼트 다수 8비트 분할 불가 ah/al 처럼 상위·하위 분리 가능
직접 메모리 피연산자

직접 메모리 피연산자와 색인 연산자
직접 메모리 피연산자 —
table[4]오프셋
table[4]는table심볼의 시작 주소 + 4바이트 위치를 가리킨다. 어셈블 시점에table의 주소 + 4가 계산되어 바이너리에 16비트 오프셋으로 인코딩된다..DATA table DW 10, 20, 30, 40, 50 ; DW = 2바이트씩 mov ax, table[0] ; table + 0 → 10 mov ax, table[2] ; table + 2 → 20 mov ax, table[4] ; table + 4 → 30 ← 3번째 원소의 주소 mov ax, table[6] ; table + 6 → 40
[4]는 인덱스가 아니라 바이트 오프셋이다. DW(2바이트) 기준으로 3번째 원소에 접근하려면4를 써야 한다.
오프셋 바이트 위치 DW 원소 [0]+0 10 [2]+2 20 [4]+4 30 [6]+6 40
흔한 실수
C/C# 의
table[2]는 2번 인덱스(3번째) 지만, 어셈블리의table[2]는 +2바이트(2번째 원소) 다. 고수준 언어처럼 첨자를 사용하려면 자료형 크기를 직접 곱해서 오프셋을 계산해야 한다.
직접 메모리 피연산자의 동작은 다음과 같다.
- 어셈블 과정에서 2바이트 메모리 주소(offset)값이 바이너리에 박힌다.
- 실행 도중에는 박힌 offset값과 세그먼트를 이용해 20비트 주소를 계산한다.

PTR
PTR operator Reference, Chapter 3 — Symbols and Operators (p.46–47)
type PTR expression
Forces the expression to be treated as having the specified type.
BYTE PTR nums 표현에서 BYTE 는 type 에 대응된다. nums 는 expression 에 대응된다.
슬라이드의 예시를 보면, nums는 DW로 선언되어 기본 타입이 WORD(2바이트)다. 그런데 mov al, BYTE PTR nums라고 쓰면 “nums 주소에서 WORD가 아니라 BYTE(1바이트)만 읽어서 al에 넣어라”라고 어셈블러에게 알려주게 된다.
어셈블 시점에 메모리 피연산자의 기본 크기를 무시하고, 원하는 크기로 간주할 수 있다.
- 어셈블 시점에
nums변수의 크기를 알 수 있다.BYTE PTR nums는 어셈블 시점에 1바이트 크기로 계산된다.
TYPE
TYPE operator Reference, Chapter 3 — Symbols and Operators (p.46–47)
TYPE expression
Returns the type of expression.
Programmer's Guide, Chapter 4 (p.86)
The SIZEOF and TYPE operators, when applied to a type, return the size of an integer of that type.
Data Type Bytes BYTE, SBYTE 1 WORD, SWORD 2 DWORD, SDWORD 4 FWORD 6 QWORD 8 TBYTE 10
TYPE nums 는 어셈블 시점에 2를 반환한다. nums 가 DW로 선언 되었기 때문이다.
직접 메모리 피연산자 예시
nums DW 1,2,3,4,5— nums의 시작 주소가0121h라고 가정. 바이너리(메모리)에는 리틀 엔디언으로21 01로 기록된다.오프셋 접근 3가지 비교
mov ax, nums[2] ; 0121h + 2 = 0123h → 23 01 (메모리의 바이너리) mov ax, nums+2 ; 위와 동일 — 표기만 다름 mov ax, nums[TYPE nums * 2] ; TYPE nums = 2(DW크기, 어셈블 시점에 계산) → 2*2=4 → 0121h+4 = 0125h → 25 01 (메모리의 바이너리)C#의
nums[2]처럼 원소 단위로 접근하려면TYPE을 곱해야 한다.TYPE nums는 어셈블러가 선언(DW)을 보고 2 로 계산해준다.
타이니 모델에서의 세그먼트
타이니 모델(
.COM)은cs = ds = es = ss— 모든 세그먼트 레지스터가 동일한 주소를 가리킨다.
- 이 때문에 es:num은 “01 20”으로 계산된다.
따라서 ds:num 이든 es:num 이든 실제로는 같은 메모리에 접근한다.
세그먼트를 바꿀 일이 없으므로 오버라이드 문법을 쓸 실질적 이유가 없다.
즉시 피연산자
mov nums, 3 에서 “21 01” 옆 하이라이트한 “03 00”은 즉시 피연산자이다.
주의: 상수 “색인”과 피연산자 종류

왼쪽 표현은 모두 동일하다. 오른쪽도 마찬가지로 모두 동일하다.
주의 — 상수 색인과 피연산자 종류 혼동
100h라는 같은 숫자가 피연산자 종류에 따라 완전히 다르게 동작한다.
ds의 값은 00 00 이라고 가정한다.
직접 메모리 피연산자 즉시 피연산자 코드 mov ax, ds:100hmov ax, 100h동작 주소 0100h 의 값을 읽어 ax에 저장 값 0100h 를 ax에 저장 결과 ax = 0xA010 (메모리 내용) ax = 0x0100 (숫자 그 자체)
ds:가 붙는 순간 무조건 직접 메모리 피연산자 — 100h는 값이 아닌 주소다.

코딩 습관 —
[]사용 원칙
[]는 본래 간접 메모리 접근 표기다. 피연산자 종류를 명확히 드러내기 위해 아래 습관을 권장한다.; ✅ 권장 mov ax, 100h ; 즉시 피연산자 — [] 없음 mov ax, ds:100h ; 직접 메모리 — [] 없음 mov ax, [bx] ; 간접 메모리 — [] 있음 ; ⚠️ 혼동 유발 mov ax, [100h] ; 즉시처럼 보이지만 사실 즉시 피연산자 mov ax, ds:[100h] ; 직접 메모리 — [] 가 없는 버전과 동일 동작
[]없음 → 즉시 또는 직접 메모리 피연산자[]있음 → 간접 메모리 접근 전용으로 남겨두기동작은 같아도 의도를 코드에서 바로 읽을 수 있도록 구분해서 쓰는 것이 좋다.