본문 바로가기
CS 공부/컴퓨터 구조, 운영체제

컴퓨터 구조(3) - 명령어

by 프롭 2026. 1. 27.

 

소스 코드와 명령어

 

고급 언어와 저급 언어

사람을 위한 언어를 고급언어라 한다. 우리가 아는 대부분의 프로그래밍 언어가 고급 언어에 속한다.

반대로 컴퓨터가 이해하고 실행할 수 있는 언어를 저급 언어라 한다. 저급 언어는 명령어로 이루어져 있다. 그래서 고급 언어로 작성된 소스 코드가 실행되려면 반드시 저급 언어, 즉 명령어로 반환되어야 한다.

저급 언어에는 두 가지 종류가 있는데 바로 기계어어셈블리어이다.

 

기계어란 0과 1의 명령어 비트로 이루어진 명령어 모음이다. 기계어를 이진수로 나열하면 너무 길어져 가독성을 위해 십육진수로 표현하기도 한다.

기계어는 오로지 컴퓨터를 위한 언어이기에 사람이 이해하기 어렵다. 그래서 등장한 저급 언어가 어셈블리어다. 0과 1로 표현된 명령어를 읽기 편한 형태로 번역한 언어이다.

 

기계어를 읽기 편하게 바꾸었을 뿐 개발자가 직접 이용하기엔 쉽지 않다. 그렇기에 고급 언어가 필요한 것이다. 고급 언어는 사람이 읽고 쓰기 편하며 더 나은 가독성, 변수나 함수 같은 편리한 문법을 제공하기 때문이다.

 

그렇다면 이러한 저급 언어는 딱히 쓰일 일이 별로 없을 것 같은데 왜 배워야 하나 의문이 들것이다. 어셈블리어를 작성하지 않는 개발자도 있으나 하드웨어와 밀접하게 맞닿아 있는 임베디드 개발자, 게임 개발자, 정보 보안 분야 등의 개발자는 어셈블리어를 많이들 사용한다. 이뿐 아니라 어셈블리어는 관찰의 대상으로 컴퓨터가 프로그램을 어떤 과정으로 실행하는지, 즉 어떤 절차로 작동하는지를 가장 근본적인 단계에서부터 관찰할 수 있기 때문이다.

 

컴파일 언어와 인터프린터 언어

고급 언어는 어떻게 저급 언어로 변환될까? 여기에는 크게 두 가지 컴파일 방식과 인터프리트 방식이 있다. 컴파일 방식으로 작동하는 프로그래밍 언어를 컴파일 언어, 인터프리트 방식으로 작동하는 언어를 인터프리터 언어라 한다.

 

  • 컴파일 언어

컴파일 언어는 컴파일러에 의해 소스 코드 전체가 저급 언어로 변환되어 실행되는 고급 언어다. 컴파일 언어로는 C가 있다. 이때 저급 언어로 변환 되는 과정을 컴파일이라 한다. 그리고 컴파일을 수행해 주는 도구를 컴파일러라 한다.

컴파일러는 소스 코드에 문법적인 오류는 없는지, 실행 가능한 코드인지, 실행하는 데 불필요한 코드는 없는지 등을 따지며 소스 코드를 처음부터 끝까지 저급 언어로 컴파일한다. 이때 오류가 하나라도 발견되면 소스 코드는 컴파일에 실패한다.

컴파일이 성공적으로 수행되면 저급 언어로 변환되는데 이때 변환된 코드를 목적 코드라 한다.

 

  • 인터프리터 언어

인터프리터 언어는 인터프리터에 의해 소스 코드가 한 줄씩 실행되는 고급언어다. 인터프리터 언어로는 Python이 있다. 전체가 변환되는 컴파일 언어와 달리 인터프리터 언어는 소스 코드를 한 줄씩 한 줄씩 차례로 실행한다. 그리고 이를 실행해 주는 도구를 인터프리터라 한다. 인터프리터는 한 줄씩 실행하기에 소스 코드 N 번째 줄에 문법 오류가 있더라도 N-1 번째 줄까지는 올바르게 수행된다. 그리고 한 줄씩 실행하기에 소스 코드 전체를 저급 언어로 변환하는 시간을 기다릴 필요가 없다. 그렇다고 인터프리터 언어가 컴파일 언어보다 더 빠르냐? 그건 아니다. 컴파일을 통해 나온 목적 코드는 컴퓨터가 이해하고 실행할 수 있는 '저급 언어'인 반면 인터프리터는 한 줄 한 줄 차례로 저급 언어로 해석하기에 더 긴 시간이 걸린다.

 

+ 목적 파일, 실행 파일

목적 코드로 이루어진 파일을 목적 파일이라 한다. 마찬가지로 실행 코드로 이루어진 파일을 실행파일이라 한다.

여기서 목적 코드는 컴퓨터가 이해하는 저급 언어라 했는데 그렇다면 목적 코드와 실행 코드는 같은 의미일까? 그렇지 않다. 목적 코드가 실행 파일이 되기 위해서는 링킹이라는 작업을 거쳐야 하는데 목적 파일에 없는 외부 기능들을 연결하는 작업, 링킹이 이루어져야 실행 파일이 만들어지는 것이다.


명령어의 구조

 

연산 코드와 오퍼랜드

명령어는 연산 코드와 오퍼랜드로 구성되어 있다.

색 배경 필드 값을 '명령어가 수행할 연산'인 연산 코드라 하고, 흰색 배경 필드 값을 '연산에 사용할 데이터' 또는 연산에 사용할 데이터가 저장된 위치를 '오퍼랜드'라고 한다. 연산 코드는 연산자, 오퍼랜드는 피연산자라고도 부른다.

연산 코드가 담긴 영역을 연산 필드라 부르고, 오퍼랜드가 담기는 영역을 오퍼랜드 필드라 한다. 어셈블리어를 보면 붉은 글씨가 연산코드, 검은 글씨가 오퍼랜드이다.

 

  • 오퍼랜드

오퍼랜드 필드에는 숫자와 문자 등을 나타내는 데이터 또는 메모리나 레지스터 주소가 올 수 있다. 연산에 사용할 데이터를 직접 명시하기보다는 메모리 주소나 레지스터 이름이 담긴다. 그래서 주소 필드라 부르기도 한다.

오퍼랜드는 명령어 안에 하나도 없을 수 있고, 한 개, 두 개 또는 세 개 등 여러 개가 있을 수 있다.

오퍼랜드가 하나도 없는 명령어를 0-주소 명령어, 오퍼랜드가 하나인 명령어를 1-주소 명령어, 두 개인 명령어를 2-주소 명령어, 세 개인 명령어를 3-주소 명령어라 한다.

 

  • 연산 코드

연산 코드 종류는 매우 많지만, 가장 기본적인 연산 코드 유형은 크게 네 가지로 나눌 수 있다.

  1. 데이터 전송
    1. MOVE : 데이터를 옮겨라
    2. STOR : 메모리에 저장하라
    3. LOAD(FETCH) : 메모리에서 CPU로 데이터를 가져와라
    4. PUSH 스택에 데이터를 저장하라
    5. POP 스택의 최상단 데이터를 가져와라
  2. 산술/논리 연산
    1. ADD / SUBSTRACT / MULTIPLY / DIVIDE : 사칙연산
    2. INCREMENT / DECREMENT 오퍼랜드에 1을 더하라, 빼라
    3. AND / OR / NOT : 각 연산을 수행하라
    4. COMPARE : 두 개의 숫자 또는 TRUE / FALSE 값을 비교하라
  3. 제어 흐름 변경
    1. JUMP : 특정 주소로 실행 순서를 옮겨라
    2. CONDITIONAL JUMP : 조건에 부합할 때 특정 주소로 실행 순서를 옮겨라
    3. HALT : 프로그램의 실행을 멈춰라
    4. CALL : 되돌아올 주소를 저장한 채 특정 주소로 실행 순서를 옮겨라
    5. RETURN : CALL을 호출할 때 저장했던 주소로 돌아가라
  4. 입출력 제어
    1. READ(INPUT) : 특정 입출력 장치로부터 데이터를 읽어라
    2. WRITE(OUTPUT) : 특정 입출력 장치로 데이터를 써라
    3. START IO : 입출력 장치를 시작하라
    4. TEST IO 입출력 장치의 상태를 확인하라

 

주소 지정 방식

위에서 '명령어의 오퍼랜드 필드에 메모리나 레지스터의 주소를 담는 경우가 많다, 그래서 오퍼랜드 필드를 주소 필드라 부르기도 한다'라고 하였다. 근데 왜 데이터를 바로 넣어서 연산을 하지 않을까? 그 이유는 명령어의 길이 때문이다. 하나의 명령어가 n 비트이고 그중 연산 코드 필드가 m 비트라 가정해 보자. 이때 오퍼랜드 필드에 가장 많은 공간을 할 당할 수 있는 1-주소 명령어라 해도 오퍼랜드 필드의 길이는 연산 코드만큼의 길이를 뺀 n-m 비트가 된다. 2-주소 명령어, 3-주소 명령어라면 더욱 작아질 것이다.

 

하지만 만약 오퍼랜드 필드 안에 메모리 주소가 담긴다면 데이터의 크기는 하나의 메모리 주소에 저장할 수 있는 공간만큼 커진다. 여기에 메모리 주소가 아닌 레지스터 이름이 명시되어도 마찬가지다. 해당 레지스터가 저장할 수 있는 공간만큼 커지는 것이다.

 

연산코드에 사용할 데이터가 저장된 위치를 유효 주소라 한다. 오퍼랜드 필드에 데이터가 저장된 위치를 명시할 때 연산에 사용할 데이터 위치를 찾는 방법을 주소 지정 방식이라 한다. 즉, 주소 지정 방식은 유효 주소를 찾는 방법이다.

 

대표적인 주소 지정 방식을 정리하겠다.

 

즉시 주소 지정 방식

  • 연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시하는 방식
  • 표현할 수 있는 데이터의 크기는 작아지는 단점이 있지만, 연산에 사용할 테이터를 메모리나 레지스터로부터 찾는 과정이 없기에 다른 주소 지정 방식들보다 빠르다.

 

직접 주소 지정 방식

  • 오퍼랜드 필드에 유효 주소를 직접적으로 명시하는 방식
  • 표현할 수 있는 데이터의 크기는 커졌지만 여전히 유효 주소를 표현할 수 있는 범위가 연산 코드의 비트 수만큼 줄었다.

 

간접 주소 지정 방식

  • 유효 주소의 주소를 오퍼랜드 필드에 명시한다.
  • 두 번의 메모리 접근이 필요하기 때문에 앞서 설명한 주소 지정 방식들 보다 느리다.

 

레지스터 주조 지정 방식

  • 직접 주소 지정 방식과 비슷하게 연산에 사용할 데이터를 저장한 레지스터를 직접 명시하는 방식
  • CPU 외부 메모리에 접근하는 것보다 내부 레지스터에 접근하는 것이 더 빠르기에 직접 주소 방식보다 빠르게 데이터에 접근 가능하다.
  • 직접 주소 지정 방식과 비슷하게 표현할 수 있는 레지스터 크기에 제한이 생길 수 있다.

 

레지스터 간접 주소 지정 방식

  • 연산에 사용할 데이터를 메모리에 저장하고, 그 주소를 저장한 레지스터를 오퍼랜드 필드에 명시하는 방식
  • 간접 주소 지정 방식과 비슷하지만 메모리에 접근하는 횟수는 한 번으로 줄어든다.
  • 메모리에 접근하는 것보다 레지스터에 접근하는 것이 더 빠르기에 간접 주소 지정 방식보다 빠르다.

 

이 외의 주요 주소 지정 방식들은 CPU에 대해 공부하며 레지스터를 공부하고 진행하겠다.