컴퓨터구조 1편

컴퓨터의 구성과 어드레싱 모드 — 다섯 구성 요소에서 명령어 형식까지

suhyun·2026년 10월 10일·읽는 데 약 20분
한 줄로: 명령어는 할 일(opcode)과 재료가 있는 곳(오퍼랜드)으로 되어 있고, 어드레싱 모드는 그 재료를 찾아가는 방법입니다.

1. 0과 1이 동작이 되기까지

컴퓨터구조 과목은 질문 하나로 정리됩니다. 컴퓨터는 0과 1의 나열을 어떻게 의미 있는 동작으로 바꾸는가. 이 시리즈는 그 답을 아래에서 위로 쌓아 갑니다. 이 글은 출발점입니다. 컴퓨터가 어떤 부품으로 되어 있는지, 명령어 한 줄은 어떤 모양인지, 명령어가 데이터를 어떻게 찾아가는지를 봅니다.

영어 computer는 원래 계산하는 사람을 부르는 말이었습니다. 과학자가 식을 세우면 계산은 사람 computer들이 손으로 했습니다. 1940년대에 들어 이 일을 기계가 맡기 시작합니다. 1945년에 완성된 ENIAC은 완전히 전자식으로 동작하는 디지털 컴퓨터였습니다. 무게가 30톤, 소비 전력이 150 kW였습니다. 프로그램은 케이블 배선으로 짰습니다. 다른 계산을 시키려면 케이블을 뽑아 다른 구멍에 꽂아야 했고, 프로그램 하나를 바꾸는 데 며칠이 걸렸습니다.

같은 해 폰 노이만(von Neumann)은 EDVAC이라는 다음 기계의 설계 보고서에서 프로그램도 데이터처럼 메모리에 넣어 두자고 제안했습니다. 이것을 내장 프로그램(stored program) 방식이라고 합니다. 프로그램을 바꾸려면 메모리 내용만 바꾸면 됩니다. 오늘날 대부분의 컴퓨터가 이 구조를 따르고, 이를 폰 노이만 구조라고 부릅니다. 명령어와 데이터를 서로 다른 메모리에 두는 방식은 하버드 구조(Harvard architecture)라고 합니다. 이 시리즈는 폰 노이만 구조를 기준으로 합니다.

2. A + B = C로 찾는 다섯 구성 요소

컴퓨터에 무엇이 필요한지는 계산 하나를 직접 해 보면 나옵니다. A + B = C를 컴퓨터가 처리한다고 생각해 봅니다.

해야 하는 일구성 요소
A와 B가 무엇인지 바깥에서 받는다입력 장치(input device)
C를 바깥에 알린다출력 장치(output device)
실제로 더한다. 빼기·곱하기 같은 산술과 참·거짓을 따지는 논리 연산도 여기서 한다산술논리장치(ALU, Arithmetic Logic Unit)
A, B, C를 담아 둔다메모리(memory)
언제 입력을 받고, 언제 더하고, 언제 내보낼지 순서와 시점을 정한다제어장치(control unit)

앞의 네 가지는 쉽게 떠오릅니다. 다섯째인 제어장치는 잘 보이지 않습니다. 그런데 부품이 다 있어도 순서가 없으면 입력이 들어오기 전에 더하거나, 더하기 전에 출력할 수 있습니다. 부품마다 할 일과 시점을 정해 주는 장치가 따로 있어야 합니다. ALU와 제어장치를 하나로 묶은 것이 중앙처리장치(CPU, Central Processing Unit)입니다. 뒤에 나올 레지스터도 CPU 안에 있습니다.

3. 2진수와 트랜지스터

사람은 10진수를 쓰는데 컴퓨터는 2진수(binary number)를 씁니다. 전자 회로는 수를 전압으로 나타냅니다. 전압을 열 단계로 나누면 잡음이 조금만 끼어도 이웃 단계와 헷갈립니다. 높음과 낮음 두 단계만 쓰면 둘 사이 간격이 넓어 잘못 읽을 일이 크게 줄어듭니다. 0과 1 두 값만 다루면 회로도 가장 단순해집니다.

2진 숫자 한 자리를 비트(bit), 8비트를 바이트(byte)라고 합니다. 8비트가 기본 단위가 된 데는 역사적 이유가 있습니다. 영어 알파벳과 숫자, 기호를 담는 ASCII 문자 코드는 7비트면 충분했고, 여기에 한 비트를 더한 8비트가 다루기 좋은 단위로 자리 잡았습니다.

2진 상태 하나를 물리적으로 만드는 데 처음에는 진공관(vacuum tube)을 썼습니다. 진공관 하나가 1비트를 담으니 기계가 방 하나만큼 커졌습니다. 그 뒤 트랜지스터(transistor)가 나왔습니다. 트랜지스터는 다리가 셋인 스위치입니다. 한 다리에 전압을 걸면 나머지 두 다리 사이에 전류가 흐릅니다. 흐르면 1, 안 흐르면 0으로 씁니다. 지금 트랜지스터는 나노미터 단위까지 작아져서 CPU 칩 하나에 수십억 개가 들어갑니다.

트랜지스터 몇 개를 엮으면 AND, OR, NOT, XOR 같은 게이트(gate)가 됩니다. 게이트를 엮으면 두 비트를 더해 합(sum)과 자리올림(carry)을 내는 덧셈기 같은 논리 회로(logic circuit)가 됩니다. 논리 회로를 엮은 것이 ALU이고 메모리이고 CPU입니다.

4. 추상화 계층과 ISA

컴퓨터는 층을 쌓아 만든 구조입니다. 아래층은 위층에 기능을 제공하고, 위층은 아래층의 내부를 몰라도 그 기능을 씁니다. 이런 구조를 추상화 계층(layers of abstraction)이라고 합니다.

구분층 (아래에서 위로)
하드웨어트랜지스터 → 회로 설계 → 디지털 설계 → 데이터패스와 제어 → 프로세서·메모리·입출력
경계명령어 집합 구조(ISA, Instruction Set Architecture)
소프트웨어어셈블러 → 컴파일러·운영체제 → 응용 프로그램

하드웨어와 소프트웨어 사이에 ISA가 있습니다. ISA는 프로세서가 어떤 명령어를 알아듣는지, 각 명령어가 무엇을 하는지, 레지스터가 몇 개인지를 정한 약속입니다. 프로그래머 쪽에서 본 프로세서의 모습이라고 할 수 있습니다. 회로가 어떻게 생겼는지는 ISA에 들어 있지 않습니다. 같은 ISA를 서로 다른 회로로 만들 수 있고, 그래도 같은 프로그램이 돌아갑니다.

프로그램이 실행되기까지의 흐름은 이렇습니다. C나 Python 같은 고급 언어로 쓴 프로그램을 컴파일러(compiler)가 어셈블리어(assembly language)로 옮기고, 어셈블러(assembler)가 그것을 0과 1로 된 기계어(machine language)로 바꿉니다. 프로세서는 기계어를 읽어 회로를 움직이는 제어 신호로 바꿉니다.

고급 언어는 어떤 CPU에서 돌지 신경 쓰지 않습니다. 어셈블리어는 프로세서마다 다릅니다. 어셈블리 문장 하나는 기계 명령어 하나에 정확히 대응합니다. 이를 일대일 대응이라고 하며, 그래서 두 말을 섞어 쓰기도 합니다. 프로그램은 명령어를 유한하게 늘어놓은 목록이고, 컴퓨터는 이 목록을 한 줄씩 차례로 실행합니다.

5. 레지스터와 명령어 사이클

레지스터(register)는 CPU 안에 있는 작은 저장 공간입니다. 메모리보다 훨씬 빠르지만 비싸서 개수가 적습니다. 덧셈 한 번에 필요한 값을 메모리까지 가서 가져오면 느리니, 자주 쓰는 값을 레지스터에 두고 계산합니다.

종류예담는 값
범용 레지스터(general purpose register)r0, r1, …계산에 쓸 아무 값
특수 목적 레지스터(special purpose register)PC (Program Counter)지금 실행할 명령어의 메모리 주소
특수 목적 레지스터IR (Instruction Register)메모리에서 가져온 지금 명령어
특수 목적 레지스터AC (Accumulator, 누산기)계산 결과가 쌓이는 곳

내장 프로그램 방식에서는 명령어도 메모리에 있습니다. CPU는 PC와 IR을 써서 명령어 하나를 다음 단계로 처리하고, 이것을 끝없이 되풀이합니다. 이 반복을 명령어 사이클(instruction cycle)이라고 합니다.

  1. 인출(fetch) — PC가 가리키는 메모리 주소에서 명령어를 읽어 IR에 넣습니다. 그리고 PC를 다음 명령어 주소로 올립니다. 명령어가 4바이트라면 PC에 4를 더합니다.
  2. 해독(decode) — IR의 비트를 보고 무슨 연산인지, 재료가 어디 있는지 알아냅니다.
  3. 실행(execute) — 필요한 값을 레지스터나 메모리에서 가져와 ALU로 계산하고, 결과를 정해진 곳에 씁니다.

보통은 PC가 한 칸씩 올라가니 명령어가 적힌 순서대로 실행됩니다. 반복문이나 if 문처럼 다른 곳으로 가야 할 때는 실행 단계에서 PC 값 자체를 바꿉니다.

6. 명령어 형식 — opcode와 오퍼랜드

기계 명령어는 CPU에 연산 하나를 시키는 비트 묶음입니다. 어셈블리 문장은 정해진 명령어 형식(instruction format)에 따라 비트로 바뀝니다. 명령어 안에서 한 가지 목적에 쓰는 비트 묶음을 필드(field)라고 합니다. 명령어 길이는 8, 16, 32, 64비트 등이 있고, 모든 명령어가 같은 길이인 고정 길이 방식과 명령어마다 길이가 다른 가변 길이 방식이 있습니다. 설명하기 쉽게 16비트 고정 길이 명령어로 시작합니다.

| opcode (4비트) |        오퍼랜드 (12비트)        |

opcode(operation code, 연산 코드)는 할 연산을 정합니다. 예를 들어 1000이 덧셈이라고 약속하면, CPU는 앞 4비트를 보고 덧셈을 준비합니다. opcode가 n비트면 서로 다른 연산을 2n가지 나타낼 수 있습니다. 4비트면 16가지, 6비트면 64가지입니다. 오퍼랜드(operand, 피연산자)는 연산에 쓸 데이터가 어디 있는지 알려 줍니다. 오퍼랜드 필드를 주소부라고도 부릅니다.

오퍼랜드는 몇 개인가

덧셈에는 출처(source) 값 둘과 결과를 넣을 목적지(destination) 하나가 필요합니다. 이것을 명령어에 몇 개 적느냐로 형식이 갈립니다.

개수예동작설명
3ADD A, B, CA ← B + C목적지와 출처 둘을 모두 적습니다. 가장 읽기 쉽습니다.
2ADD A, BA ← A + BA의 원래 값을 덮어써도 될 때 씁니다. A가 출처이면서 목적지입니다.
1ADD AAC ← AC + A나머지 출처와 목적지가 AC로 정해져 있어 적지 않습니다.
0HALT실행을 멈춤필요한 정보가 연산 안에 다 정해져 있습니다.

오퍼랜드를 덜 적을수록 명령어에서 남는 비트를 다른 정보에 쓸 수 있습니다. 대신 원래 값이 지워지거나 값을 AC로 옮기는 명령어가 더 필요해집니다. 오퍼랜드 0개는 이상해 보이지만 가능합니다. 멈추기, 서브루틴에서 돌아오기처럼 무엇을 할지가 연산 자체로 정해지는 명령어가 있습니다. 스택(stack)을 쓰는 기계에서는 ADD 하나가 스택 맨 위의 두 값을 꺼내 더하고 결과를 다시 넣으므로, 덧셈도 오퍼랜드 없이 됩니다.

문제 1

W = (X − Y) × Z를 1-오퍼랜드 명령어만으로 쓰고, 명령어가 몇 개 필요한지 세어 보십시오. 명령어로는 AC로 값을 읽어 오는 LOAD, AC를 메모리에 쓰는 STORE, 그리고 SUB와 MUL이 있다고 합니다. 3-오퍼랜드로 쓰면 몇 개인지도 비교해 보십시오.

1-오퍼랜드에서는 모든 계산이 AC를 거칩니다. 먼저 X를 AC에 올리고, Y를 빼고, Z를 곱한 뒤, AC를 W에 씁니다.

LOAD  X    ; AC ← X
SUB   Y    ; AC ← AC − Y
MUL   Z    ; AC ← AC × Z
STORE W    ; W ← AC

모두 4개입니다. 3-오퍼랜드라면 SUB W, X, Y와 MUL W, W, Z 두 줄이면 됩니다. 명령어 수는 절반이지만 명령어 하나에 주소를 셋씩 담아야 하니 명령어가 길어집니다. 1-오퍼랜드 명령어는 짧은 대신 개수가 늘어납니다.

7. 어드레싱 모드

오퍼랜드 필드에 적힌 비트를 어떻게 해석하느냐에 따라 데이터를 찾는 방법이 달라집니다. 이 방법들을 어드레싱 모드(addressing mode, 주소 지정 방식)라고 합니다. 여러 단계를 거쳐 실제 데이터에 닿았을 때 그 데이터가 있는 메모리 주소를 유효 주소(effective address)라고 합니다. 아래 예는 ARM 어셈블리 문법을 따릅니다.

모드데이터가 있는 곳예
묵시적 (implied)미리 정해진 곳. 적지 않음PUSH A, ADD A
즉시 (immediate)명령어 안ADD r3, r3, #4
레지스터 (register)레지스터ADD r0, r1, r2
직접 (direct)메모리. 유효 주소가 명령어 안에 있음—
간접 (indirect)메모리. 유효 주소가 메모리에 있음—
레지스터 간접 (register indirect)메모리. 유효 주소가 레지스터에 있음LDR r1, [r0]
베이스 레지스터 (base register)메모리. 유효 주소 = 베이스 레지스터 + 오프셋LDR r1, [r0, #8]
상대 (relative)메모리. 유효 주소 = PC + 오프셋분기 명령어

묵시적 어드레싱

주소를 명령어에 적지 않습니다. 스택은 나중에 넣은 값이 먼저 나오는 LIFO(Last In, First Out) 구조입니다. 스택이 메모리 어디에 있는지와 지금 맨 위가 어디인지(TOS, Top Of Stack)는 하드웨어가 정해 둡니다. 그래서 PUSH A는 넣을 값 A만 적으면 됩니다. 1-오퍼랜드 ADD A도 결과가 AC로 간다는 사실이 정해져 있으니 묵시적 어드레싱을 씁니다.

즉시 어드레싱과 레지스터 어드레싱

즉시 어드레싱은 오퍼랜드 필드에 값 자체를 넣습니다. ADD r3, r3, #4는 r3에 4를 더합니다. #은 뒤의 숫자가 값이라는 표시입니다. 메모리에 갈 필요가 없어 빠릅니다. 대신 필드가 좁으면 담을 수 있는 값이 작습니다. 필드가 4비트라면 0부터 15까지입니다.

레지스터 어드레싱은 필드에 값 대신 레지스터 번호를 적습니다. 레지스터가 16개라면 번호는 4비트로 충분합니다. 같은 4비트 필드로 16비트 레지스터를 가리키면 0부터 65,535까지의 값을 쓸 수 있습니다. ADD r0, r1, r2는 r1과 r2에 든 값을 더해 r0에 넣습니다. 여기서 레지스터에 든 것은 계산에 쓸 값입니다.

직접, 간접, 레지스터 간접

레지스터는 몇 개 안 되니 대부분의 데이터는 메모리에 있습니다. 메모리의 각 칸에는 주소가 있고, 칸을 읽으려면 그 주소를 알아야 합니다.

직접 어드레싱은 오퍼랜드 필드에 유효 주소를 그대로 적습니다. 필드에 0x120이 있으면 메모리 0x120번지의 값을 씁니다. 앞에 붙은 0x는 16진수라는 표시입니다. 즉시 어드레싱과 같은 한계가 있습니다. 12비트 필드로는 4,096개 칸만 가리킬 수 있는데, 메모리 주소 범위는 그보다 훨씬 넓습니다.

간접 어드레싱은 필드가 가리키는 메모리 칸에 유효 주소가 들어 있습니다. 메모리를 두 번 읽어야 해서 잘 쓰이지 않습니다. 대신 레지스터 간접 어드레싱을 많이 씁니다. 명령어에는 레지스터 번호만 적고, 그 레지스터에 유효 주소를 담아 둡니다. LDR r1, [r0]은 r0에 든 값을 주소로 삼아 메모리를 읽고 그 값을 r1에 넣습니다. 레지스터의 비트 전체를 주소로 쓸 수 있으니 넓은 메모리 어디든 가리킬 수 있습니다. 값의 범위가 좁을 때 레지스터를 썼듯이, 주소의 범위가 좁을 때도 레지스터를 씁니다.

베이스 레지스터와 상대 어드레싱

베이스 레지스터 어드레싱은 레지스터에 든 기준 주소(base address)에 명령어의 오프셋(offset)을 더해 유효 주소를 만듭니다. LDR r1, [r0, #8]은 r0 + 8번지를 읽습니다. 배열을 다룰 때 잘 맞습니다. 배열 시작 주소를 레지스터에 두고 원소 위치만큼 오프셋을 더하면, 원소마다 주소를 따로 들고 다닐 필요가 없습니다. 프로그램을 메모리의 다른 위치로 옮길 때(재배치, relocation)도 베이스 레지스터 값만 바꾸면 됩니다.

상대 어드레싱은 기준이 PC입니다. 유효 주소 = PC + 오프셋입니다. 분기 명령어가 씁니다. 반복문 끝에서 시작 부분으로 돌아갈 때, 목적지의 절대 주소 대신 지금 위치에서 몇 칸 떨어졌는지만 적습니다.

문제 2

레지스터와 메모리가 아래와 같습니다. 각 명령어가 r1에 넣는 값을 구하십시오. 메모리 칸은 4바이트 단위입니다.

r0 = 0x200        r2 = 0x20C
Mem[0x200] = 0x20C
Mem[0x204] = 7
Mem[0x208] = 9
Mem[0x20C] = 15
  1. MOV r1, #0x200 (즉시)
  2. MOV r1, r0 (레지스터)
  3. LDR r1, [r0] (레지스터 간접)
  4. LDR r1, [r0, #8] (베이스 레지스터)
  5. 오퍼랜드 필드가 0x200인 간접 어드레싱 적재

1은 필드의 값 자체이므로 0x200입니다. 2는 r0에 든 값이므로 역시 0x200입니다. 두 명령어는 결과가 같지만 값을 얻은 곳이 다릅니다. 3은 r0의 값 0x200을 주소로 읽으므로 Mem[0x200] = 0x20C입니다. 4는 유효 주소가 0x200 + 8 = 0x208이므로 9입니다. 5는 메모리를 두 번 읽습니다. 먼저 Mem[0x200]에서 0x20C를 얻고, 이것이 유효 주소이므로 Mem[0x20C] = 15를 r1에 넣습니다. r2를 쓰는 레지스터 간접 LDR r1, [r2]도 같은 15를 한 번의 메모리 읽기로 얻습니다.

8. 명령어 집합 — 종류와 설계 방향

세 종류의 명령어

종류하는 일예
산술·논리 (data operation)값을 계산해 바꿉니다ADD, SUB, AND, OR, 시프트, 부동소수점 연산
데이터 전송 (data transfer)값을 한 곳에서 다른 곳으로 복사합니다LOAD(메모리 → 레지스터), STORE(레지스터 → 메모리), MOVE(레지스터 → 레지스터)
제어 전송 (control transfer)다음에 실행할 명령어를 바꿉니다조건 분기, 무조건 점프, 서브루틴 호출과 복귀, 인터럽트, 정지

명령어 집합이 완전하다(complete)는 말은, 계산할 수 있는 함수라면 무엇이든 그 명령어들을 조합해 프로그램으로 만들 수 있다는 뜻입니다. 그러려면 위 세 종류를 모두 갖춰야 합니다. 명령어를 다 따로 둘 필요는 없습니다. 뺄셈은 부호를 바꾼 수를 더하면 되고, AND·OR·NOT은 NAND 하나 또는 NOR 하나만으로 모두 만들 수 있습니다. 그래서 NAND와 NOR를 범용 게이트(universal gate)라고 부릅니다. 명령어끼리 기능이 겹치지 않으면 그 집합을 직교한다(orthogonal)고 합니다. 수학에서 서로 직교하는 벡터가 서로의 성분을 갖지 않는 데서 온 말입니다.

입출력 장치도 주소로

메모리 주소 공간은 용도별 구획으로 나뉩니다. 프로그램 코드가 놓이는 코드 영역, 데이터 영역이 있고, 입출력 장치에 주소 구획을 내주기도 합니다. 그러면 장치의 레지스터를 메모리 칸처럼 LOAD와 STORE로 읽고 씁니다. 이 방식을 메모리 맵 입출력(memory-mapped I/O)이라고 합니다. 입출력 전용 명령어가 없어도 데이터 전송 명령어로 장치를 다룰 수 있습니다.

CISC와 RISC

CISC (Complex Instruction Set Computer)RISC (Reduced Instruction Set Computer)
명령어종류가 많고 하나가 여러 일을 함작고 단순한 집합
명령어 길이가변. 해독 회로가 복잡함대개 고정. 해독이 단순함
프로그램당 명령어 수적음많음
명령어 하나의 실행 시간여러 클럭 사이클한 사이클이나 몇 사이클
하드웨어복잡함단순해 비용이 적음

클럭(clock)은 하드웨어가 동작하는 시점을 맞추는 신호입니다. 일정한 간격으로 0과 1을 오가고, 한 번 오가는 시간이 클럭 사이클입니다. CPU 사양의 GHz가 1초에 사이클이 몇 번 도는지를 나타냅니다. RISC는 명령어 길이가 같아서 해독이 쉽고, 명령어 대부분을 짧은 사이클 안에 끝낼 수 있습니다. 앞으로 볼 MIPS와 스마트폰의 ARM이 RISC입니다.

정리

컴퓨터는 입력, 출력, ALU, 메모리, 제어장치로 되어 있고, 프로그램도 데이터처럼 메모리에 들어 있습니다. CPU는 PC가 가리키는 명령어를 IR로 가져와 해독하고 실행하는 사이클을 반복합니다. 명령어는 opcode와 오퍼랜드로 되어 있고, 어드레싱 모드가 오퍼랜드 필드를 읽는 방법을 정합니다. 다음 글부터 다룰 MIPS에서는 이 모드들이 이렇게 나타납니다. 레지스터 셋을 적는 add는 레지스터 어드레싱, 상수를 담는 addi는 즉시 어드레싱, lw와 sw는 베이스 레지스터 어드레싱, beq는 PC 상대 어드레싱입니다. 다음 글에서는 MIPS 명령어 32비트를 필드로 나누는 방법부터 봅니다.

dev-news학습 노트소개개인정보 처리