명령어 주소 형식과 스택·누산기·레지스터 설계
명령어의 Operand 주소 필드에 따른 형식을 비교하고 스택, 누산기, 범용 레지스터 기반 연산 구조의 차이를 정리한다.
2026-08-14 · 최초 발행 2025-12-25
주소 필드가 바꾸는 연산 구조
명령어 방식(Instruction Format)은 명령어 안의 자료부(Operand), 즉 주소 필드 수를 기준으로 나눈다. 명령어는 명령부(OP Code + Mode)와 자료부(Operand)로 구성되며, 자료부 구성은 프로그램 길이, 실행 속도, 메모리 접근, 코드 밀도에 영향을 준다.
스택 기계, 누산기 기계, 범용 레지스터 기계는 각각 이 주소 형식을 다르게 활용한다.
스택에 피연산자를 맡기는 0주소 형식
0주소 명령어는 OP Code만 가지며 주소를 지정하는 자료부가 없다. 스택 메모리를 사용하는 스택 기계(Stack Machine)에서 쓰이고, 피연산자는 Stack의 TOP에 묵시적으로 놓인다. 수식은 Postfix(후위) 표기법으로 표현한다.
┌─────────────┐
│ OP Code │
└─────────────┘
스택 포인터는 TOP을 가리킨다. PUSH로 피연산자를 넣고, ADD처럼 0주소 연산을 수행하면 스택 상단의 값을 꺼내 계산한 뒤 결과를 다시 넣는다.
A + B * C는 중위 표기와 후위 표기가 다음처럼 대응한다.
| 표기법 | 수식 |
|---|---|
| Infix (중위) | A + B * C |
| Postfix (후위) | A B C * + |
명령어 시퀀스:
1. PUSH A → Stack: [A]
2. PUSH B → Stack: [A, B]
3. PUSH C → Stack: [A, B, C]
4. MUL (0주소) → Stack: [A, B*C]
5. ADD (0주소) → Stack: [A+B*C]
OP Code만 포함하므로 명령어 길이가 짧고 기억 공간 효율과 명령 인출 시간이 좋다. 반면 간단한 연산도 여러 명령이 필요하며, 스택 크기 제한으로 인한 스택 오버플로우와 중간 결과 추적의 복잡성이 남는다.
누산기를 중심에 두는 1주소 형식
1주소 명령어는 메모리 주소를 지정하는 자료부를 하나 두고, 다른 피연산자와 결과 저장소는 누산기(AC)로 처리한다.
┌─────────────┬─────────────┐
│ OP Code │ Address │
└─────────────┴─────────────┘
명시적 Operand는 메모리 주소 1개이고, 누산기(AC)는 묵시적 Operand이자 결과 저장소다.
A + B * C를 계산할 때 중간 결과는 누산기에 누적된다.
명령어 시퀀스:
1. LOAD B → AC = B
2. MUL C → AC = AC * C = B * C
3. ADD A → AC = AC + A = A + B * C
4. STORE RESULT → M[RESULT] = AC
누산기 하나만 필요해 하드웨어가 단순하고 주소 1개만 포함하므로 명령어도 짧다. 초기 컴퓨터의 8비트 시대에 널리 사용된 방식이다. 다만 중간 결과의 저장과 로드가 빈번해 메모리 접근이 많고, 모든 연산이 누산기를 거치므로 병목과 메모리 접근 오버헤드가 생긴다.
목적지 피연산자를 덮어쓰는 2주소 형식
2주소 명령어는 목적지와 원본을 함께 지정한다. 가장 일반적인 방식이며, OP Dest, Src 형식으로 Dest ← Dest OP Src를 수행한다. 결과는 첫 번째 피연산자 위치인 Dest에 저장되므로 기존 Dest 값은 소멸한다.
┌─────────────┬─────────────┬─────────────┐
│ OP Code │ Address 1 │ Address 2 │
└─────────────┴─────────────┴─────────────┘
레지스터-레지스터와 레지스터-메모리 연산이 가능하다. A + B * C는 다음처럼 계산할 수 있다.
명령어 시퀀스:
1. MOV R1, B → R1 = B
2. MUL R1, C → R1 = R1 * C = B * C
3. ADD R1, A → R1 = R1 + A = A + B * C
4. MOV RESULT, R1 → RESULT = R1
레지스터를 사용하면 메모리 접근을 최소화해 빠르게 실행할 수 있고, 중간 결과가 CPU에 남아 테스트하기도 쉽다. 대부분의 현대 프로세서가 지원하며 x86 아키텍처의 주요 방식이다. 대신 피연산자 한쪽이 덮어써지므로 원본을 보존하려면 별도 복사 명령이 필요하다.
결과 영역을 분리하는 3주소 형식
3주소 명령어는 목적지와 두 원본 피연산자를 모두 지정한다. OP Dest, Src1, Src2 형식으로 Dest ← Src1 OP Src2를 수행하므로 Src1과 Src2는 보존된다.
┌──────────┬──────────┬──────────┬──────────┐
│ OP Code │ Dest │ Src1 │ Src2 │
└──────────┴──────────┴──────────┴──────────┘
A + B * C에서는 곱셈 결과를 TEMP에 저장한 뒤 덧셈 결과를 RESULT에 둔다.
명령어 시퀀스:
1. MUL TEMP, B, C → TEMP = B * C
2. ADD RESULT, A, TEMP → RESULT = A + TEMP
원본 데이터가 보존되고 명령 수와 명령 인출 횟수를 줄일 수 있다. 독립적인 연산을 병렬로 실행하기도 쉽다. RISC 아키텍처(ARM, MIPS)의 주요 방식이지만, 주소 3개를 포함하므로 명령어가 길어지고 인출 시간 및 디코더 회로의 복잡성이 증가한다.
같은 식을 주소 형식별로 전개하면
X = (A + B) * (C + D)를 계산하는 명령어 시퀀스는 주소 형식에 따라 달라진다.
0주소 방식은 스택에 값을 넣고 결과를 다시 꺼낸다.
PUSH A
PUSH B
ADD
PUSH C
PUSH D
ADD
MUL
POP X
명령 수: 8개
1주소 방식은 누산기와 임시 저장소를 사용한다.
LOAD A
ADD B
STORE TEMP1
LOAD C
ADD D
MUL TEMP1
STORE X
명령 수: 7개
2주소 방식은 레지스터에 중간 결과를 둔다.
MOV R1, A
ADD R1, B
MOV R2, C
ADD R2, D
MUL R1, R2
MOV X, R1
명령 수: 6개
3주소 방식은 목적지 레지스터를 별도로 지정한다.
ADD R1, A, B
ADD R2, C, D
MUL R3, R1, R2
STORE X, R3
명령 수: 4개
주소 필드 수가 적으면 명령어 자체는 짧아지지만 프로그램은 길어진다. 반대로 2주소와 3주소 방식은 명령어가 길어지는 대신 프로그램 길이를 줄일 수 있다. 0주소와 1주소 방식은 메모리 접근이 많고, 3주소 방식은 메모리 접근이 적다.
현대 프로세서에서는 2주소(x86)와 3주소(RISC) 방식이 주로 쓰이며, JVM은 0주소 스택 기반으로 동작한다.