본문 바로가기
AI & CS 지식/컴퓨터구조

[혼공컴운] 3. 명령어

by rahites 2024. 7. 21.

명령어

1. 소스 코드와 명령어

사람의 명령을 컴퓨터가 처리하는 법을 이해하기 위해서는 우선 소스 코드명령어에 대해 알아야 한다. 

 

일반적으로 우리가 공부하는 Python, Java, C 와 같은 프로그래밍 언어들은 모두 소스 코드를 만드는 것이고 모든 소스 코드들은 컴퓨터 내부에서 명령어로 변환된다. 그 말은 즉 컴퓨터는 소스 코드 그 자체로는 명령을 이해할 수 없다는 의미이다.

 

고급 언어와 저급 언어

이와 같이 일반적으로 소스 코드로 짜여지는 프로그래밍 언어들을 고급 언어, 컴퓨터가 직접 이해할 수 있는 언어를 저급 언어라고 부른다. 저급 언어는 기계어와 어셈블리어 2가지 종류가 있으며 각각의 특징은 다음과 같다.

  • 기계어 : 0과 1 명령어 비트로 이루어진 언어(2진수로 표현하기에 너무 길어서 16진수로 표현하기도 함)
  • 어셈블리어 : 기계어를 읽기 편한 형태로 번역한 언어. 컴퓨터가 어떤 과정으로 프로그램을 실행하는지 알 수 있다.

 

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

컴퓨터가 명령을 이해할 수 있도록 고급 언어를 저급 언어로 변환하는 방식에는 크게 컴파일 방식인터프리트 방식이 존재한다. 

  1. 컴파일 언어
    대표적인 언어 : C
    - 컴파일러를 활용하여 작성된 소스 코드 전체를 저급 언어로 컴파일 하는 방식.
    - 소스 코드 내에 하나라도 오류가 존재한다면 해당 소스 코드는 컴파일에 실패함.
    - 컴파일러를 통해 저급 언어로 변환된 코드를 목적 코드라고 부름

  2. 인터프리터 언어
    대표적인 언어 : Python
    - 인터프리터를 통해 한 줄씩 소스 코드가 실행되는 언어.
    - 소스 코드를 한 줄씩 실행하기 때문에 소스 코드 전체를 저급 언어로 변환하는 시간을 기다리지 않아도 됨
    - N번째 줄에 문법 오류가 있다면 N-1번째 줄까지는 정상 수행

일반적으로 컴파일 언어가 인터프리터 언어보다 빠르다. 또한 하나의 언어가 하나의 방식만을 사용하는 것은 아니며 두 방식을 같이 사용하기도 한다.

LLM이 발전한다면 현재 존재하는 고급 언어들 대신 자연어 만으로 모든 코딩이 이루어 질 수 있을까..?

 

※ 링킹

- 우리가 일반적으로 사용하는 .exe 와 같은 실행 파일을 실행시킬 때 컴퓨터가 이해할 수 있는 목적 코드의 역할을 하기 위해서는 링킹 과정을 거져야 한다. 예를 들어 main.c파일과 sub.c파일이 존재할 때 각각의 파일을 컴파일 한 후(main.o, sub.o) main.o를 실행할 경우 main.o에는 sub.o의 정보가 없기 때문에 제대로 실행될 수 없다.

- 따라서 sub 파일들을 main.o와 연결짓는 링킹 작업을 거친 후 실행 파일을 만들어주어야 한다.

 

2. 명령어의 구조

기계어와 어셈블리어를 이루는 각각의 명령어들을 자세히 살펴보자

 

연산 코드와 오퍼랜드

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

  • 연산 코드(Operation Code) : 명령어가 수행할 연산(연산자)(ex. 더해라)
  • 오퍼랜드(Operand) : 연산에 사용할 데이터 or 데이터가 저장된 위치(피연산자)(ex. 10과 11을)

오퍼랜드는 연산에 사용하는 데이터를 직접 명시하거나 데이터가 저장된 위치를 알려주어 오퍼랜드 필드를 주소 필드라고 부르기도 한다. 오퍼랜드는 명령어 안에 있을 수도 있고 없을 수도 있으며, 이 때 명령어 속에 존재하는 오퍼랜드의 개수에 따라 0-주소 명령어 ~ 3-주소 명령어라고 부른다.

 

연산 코드의 유형은 크게 (1) 데이터 전송 (2) 산술/논리 연산 (3) 제어 흐름 변경 (4) 입출력 제어 4가지로 나뉜다. CPU에 따라 명령어와 연산 코드의 종류가 다르지만 대표적인 예시는 다음과 같다.

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

주소 지정 방식

일반적으로 명령어가 너무 길어지는 것을 대비해 오퍼랜드 필드에 직접 데이터를 넣지 않고 메모리나 레지스터의 주소를 저장한다.

예를 들어 명령어가 16비트 크기이고 연산 코드가 4비트라면 2-주소 명령어는 오퍼랜드에 6비트, 3-주소 명령어는 4비트만의 정보를 표현할 수 있다.

이 때 저장되는 메모리나 레지스터의 주소를 유효 주소(effective address)라고 하며 오퍼랜드 필드에 저장된 유효 주소를 찾는 방법을 주소 지정 방식(addressing mode)이라고 한다.

 

대표적인 주소 지정 방식은 다음과 같다.

  1. 즉시 주소 지정 방식
    - 연산에 사용할 데이터를 오퍼랜드 필드에 직접 명시하는 방식
    - 속도가 빠르다는 장점이 있다.
  2. 직접 주소 지정 방식
    - 오퍼랜드 필드에 유효 주소를 직접적으로 명시하는 방식
    - 표현할 수 있는 오퍼랜드 필드의 길이가 연산 코드 길이만큼 짧아져 표현할 수 있는 유효 주소에 제한이 존재
  3. 간접 주소 지정 방식
    - 유효 주소의 주소를 오퍼랜드 필드에 명시하는 방식
    - 직접 주소 지정 방식보다 표현할 수 있는 유효 주소가 길지만 2번의 메모리 접근이 필요하기 때문에 느리다는 단점이 있다.
  4. 레지스터 주소 지정 방식
    - 연산에 사용할 데이터를 저장한 레지스터를 오퍼랜드 필드에 직접 명시하는 방식(직접 주소 지정 방식과 비슷)
    - 2, 3번처럼 CPU 외부에 있는 메모리에 접근하는 것보다 CPU 내부에 있는 레지스터에 접근하는 것이 더 빠름(직접 주소 지정 방식보다 빠름)
  5. 레지스터 간접 주소 지정 방식
    - 연산에 사용할 데이터를 메모리에 저장하고 유효 주소를 저장한 레지스터를 오퍼랜드 필드에 명시하는 방법
    - 간접 주소 지정 방식과 비슷하지만 메모리에 한번, 레지스터에 한번 접근하기 때문에 더 빠르다.

출처

강민철, 혼자 공부하는 컴퓨터 구조 + 운영체제, 한빛미디어, 2022

댓글