CPU 주소지정방식과 메모리 피연산자 접근
CPU 주소지정방식의 피연산자 위치 계산 원리와 즉시·직접·간접·레지스터·인덱스·베이스·상대 주소지정의 특성을 정리한다.
2026-08-14 · 최초 발행 2026-01-03
피연산자 위치를 계산하는 규칙
주소지정방식(Addressing Mode)은 CPU가 명령어를 실행할 때 피연산자가 있는 위치를 지정하는 방법이다. 명령어 안에 값을 넣을 수도 있고, 메모리 주소나 레지스터를 참조할 수도 있다.
명령어 집합은 여러 주소지정방식을 제공하며, 이는 배열·포인터·구조체 같은 데이터 접근 패턴을 표현하는 기반이 된다. 코드 크기와 실행 속도, 메모리 접근 사이에는 절충이 따른다.
주소지정방식은 명령어 길이를 줄이고 실행 속도를 높이며 메모리 접근을 최적화하는 데도 관여한다. 복잡한 데이터 구조를 간결하게 표현할 수 있다는 점 역시 이 방식들이 필요한 이유다.
명령어가 값을 가리키는 방식
즉시 주소지정
즉시 주소지정은 피연산자 자체를 명령어에 포함한다. 별도 메모리 접근이 없어 상수 값을 처리할 때 가장 빠르지만, 명령어 크기는 커질 수 있다.
명령어: ADD R1, #5
의미: R1 = R1 + 5
메모리 접근은 0회이며 상수 값에 적합하다.
; x86
MOV EAX, 100 ; EAX = 100
; ARM
MOV R0, #42 ; R0 = 42
ADD R1, R1, #10 ; R1 = R1 + 10
직접 주소지정
직접 주소지정은 명령어에 메모리 주소를 직접 기록하고, 해당 주소의 데이터를 사용한다. 절대 주소를 쓰므로 전역 변수 접근에는 유용하지만, 주소를 포함한 만큼 명령어 길이가 증가하고 재배치는 어렵다.
명령어: LOAD R1, 1000
의미: R1 = Memory[1000]
메모리 접근은 1회다.
; x86
MOV EAX, [0x12345678] ; EAX = Memory[0x12345678]
; MIPS
LW $t0, 0x1000($zero) ; $t0 = Memory[0x1000]
간접 주소지정
간접 주소지정은 주소를 담은 레지스터 또는 메모리를 지정한 뒤, 그 주소를 통해 데이터를 참조한다. 포인터를 구현하고 동적으로 주소를 지정할 수 있지만 2단계 참조가 필요하다.
명령어: LOAD R1, (R2)
의미: R1 = Memory[Memory[R2]]
메모리 접근은 2회이며, 유연성이 높은 대신 실행 속도는 느리다.
; x86
MOV EAX, [EBX] ; EAX = Memory[EBX]
; ARM
LDR R0, [R1] ; R0 = Memory[R1]
레지스터 주소지정
레지스터 주소지정은 피연산자를 레지스터에서 찾는다. 메모리를 거치지 않으므로 즉시 주소지정을 제외하면 가장 빠르며, RISC 아키텍처에서 주로 사용된다. 다만 사용 가능한 레지스터 수에는 제한이 있어 대용량 데이터를 직접 처리하기는 어렵다.
명령어: ADD R1, R2
의미: R1 = R1 + R2
메모리 접근은 0회이고 명령어도 짧다.
; x86
ADD EAX, EBX ; EAX = EAX + EBX
; ARM
ADD R0, R1, R2 ; R0 = R1 + R2
인덱스 주소지정
인덱스 주소지정은 베이스 주소와 인덱스 레지스터를 더해 피연산자 위치를 구한다. 배열을 순회하거나 루프를 최적화할 때 적합하며, 구조체 필드 접근에도 사용할 수 있다. 주소 계산에는 약간의 오버헤드가 생긴다.
명령어: LOAD R1, 1000(R2)
의미: R1 = Memory[1000 + R2]
; x86
MOV EAX, [EBX + 100] ; EAX = Memory[EBX + 100]
; ARM
LDR R0, [R1, #12] ; R0 = Memory[R1 + 12]
; 배열 접근
LDR R0, [R1, R2] ; R0 = Memory[R1 + R2]
베이스 주소지정
베이스 주소지정은 베이스 레지스터와 오프셋을 조합한다. 인덱스 주소지정과 유사하지만 구조체와 스택 프레임 접근, 재배치 가능한 코드에서 특히 쓰인다.
명령어: LOAD R1, offset(Base)
의미: R1 = Memory[Base + offset]
; x86 (스택 프레임)
MOV EAX, [EBP + 8] ; 함수 매개변수 접근
; ARM
LDR R0, [SP, #16] ; 스택의 지역 변수 접근
상대 주소지정
상대 주소지정은 PC(Program Counter)에 오프셋을 더해 주소를 계산한다. 분기 명령어에 주로 쓰이며 위치 독립적 코드(PIC)를 만들고 재배치하기 쉽다. 짧은 명령어로 표현할 수 있지만, 분기 가능한 범위에는 제한이 있어 먼 곳으로 이동할 때는 추가 명령어가 필요하다.
명령어: BRANCH offset
의미: PC = PC + offset
; x86
JMP +20 ; PC = PC + 20
; ARM
B +8 ; PC = PC + 8 (2 명령어 앞으로)
메모리 접근 횟수가 만드는 차이
즉시 주소지정과 레지스터 주소지정은 메모리 접근이 0회이므로 가장 빠르다. 직접·인덱스·베이스·상대 주소지정은 1회 접근으로 데이터를 다루며, 간접 주소지정과 이중 간접 주소지정은 2회 이상 접근하므로 느리다.
| 방식 | 메모리 접근 | 상대 속도 | 용도 |
|---|---|---|---|
| 즉시 | 0회 | 가장 빠름 | 상수 |
| 레지스터 | 0회 | 가장 빠름 | 임시 변수 |
| 직접 | 1회 | 빠름 | 전역 변수 |
| 간접 | 2회 | 느림 | 포인터 |
| 인덱스 | 1회 | 빠름 | 배열 |
| 베이스 | 1회 | 빠름 | 구조체, 스택 |
| 상대 | 1회 | 빠름 | 분기 |
배열과 구조체, 포인터에서의 주소 계산
배열 원소는 인덱스 주소지정으로 접근할 수 있다. 아래 예시에서는 배열의 베이스 주소와 인덱스를 사용하며, LSL #2로 i*4 바이트 위치를 계산한다.
int arr[10];
int sum = 0;
for (int i = 0; i < 10; i++) {
sum += arr[i];
}
; ARM
LDR R0, =arr ; R0 = arr의 베이스 주소
MOV R1, #0 ; R1 = i = 0
MOV R2, #0 ; R2 = sum = 0
loop:
LDR R3, [R0, R1, LSL #2] ; R3 = arr[i] (i*4 바이트)
ADD R2, R2, R3 ; sum += arr[i]
ADD R1, R1, #1 ; i++
CMP R1, #10
BLT loop
구조체의 각 필드는 베이스 주소에 필드 오프셋을 더해 접근한다.
struct Person {
int age; // offset 0
char name[20]; // offset 4
float salary; // offset 24
};
struct Person p;
p.age = 30;
p.salary = 50000.0;
; x86
LEA EBX, [p] ; EBX = p의 베이스 주소
MOV DWORD [EBX + 0], 30 ; p.age = 30
MOV DWORD [EBX + 24], 50000.0 ; p.salary = 50000.0
포인터는 간접 주소지정으로 실제 데이터에 도달한다.
int x = 10;
int *ptr = &x;
int y = *ptr;
; x86
MOV EAX, [x] ; EAX = x = 10
LEA EBX, [x] ; EBX = &x (포인터)
MOV ECX, [EBX] ; ECX = *ptr = 10
함수 매개변수는 스택 프레임의 베이스 주소와 오프셋을 이용해 읽을 수 있다.
int add(int a, int b) {
return a + b;
}
; x86 (cdecl 호출 규약)
add:
PUSH EBP
MOV EBP, ESP
MOV EAX, [EBP + 8] ; EAX = a (첫 번째 매개변수)
ADD EAX, [EBP + 12] ; EAX += b (두 번째 매개변수)
POP EBP
RET
CISC와 RISC가 주소를 다루는 차이
CISC인 x86은 메모리 직접 연산과 다양한 주소지정방식 조합을 제공한다. 한 명령어로 복잡한 작업을 표현할 수 있어 강력하고 유연하지만, 디코딩은 복잡하다.
; 메모리 직접 연산
ADD [EBX + ECX*4 + 8], EAX
; Memory[EBX + ECX*4 + 8] += EAX
; 베이스 + 인덱스 + 스케일 + 오프셋
MOV EAX, [EBX + ESI*8 + 0x1000]
RISC인 ARM은 레지스터 간 연산과 메모리 접근을 분리한다. Load/Store만 메모리에 접근하고 주소지정방식도 제한적이어서 파이프라인 효율이 높고 예측 가능하다. 대신 같은 작업에 필요한 명령어 수는 증가한다.
; 레지스터 간 연산만
ADD R0, R1, R2 ; R0 = R1 + R2
; 메모리 연산은 Load/Store로 분리
LDR R0, [R1, #12] ; R0 = Memory[R1 + 12]
ADD R0, R0, R2 ; R0 = R0 + R2
STR R0, [R1, #12] ; Memory[R1 + 12] = R0
| 구분 | CISC (x86) | RISC (ARM) |
|---|---|---|
| 복잡도 | 복잡 | 단순 |
| 메모리 연산 | 직접 가능 | Load/Store만 |
| 조합 | 다양한 조합 | 제한적 |
| 명령어 수 | 적음 | 많음 |
| 디코딩 | 복잡 | 단순 |
| 파이프라인 | 어려움 | 쉬움 |
주소지정방식 선택은 데이터가 상수인지, 레지스터에 있는지, 배열·구조체·포인터·분기 대상인지에 따라 달라진다. 메모리 접근 횟수와 재배치 가능성, 명령어 표현력을 함께 고려해야 한다.