본문 바로가기
Computer Science/컴퓨터 구조 (Computer Architecture)

[혼공컴운] Ch3. 명령어, 컴파일

by hyuga_ 2023. 9. 30.

고급 언어와 저급 언어

  • 고급 언어: 개발자가 이해하기 쉽게 만든 언어
    • C, C++, 파이썬, 자바, 자바스크립트 등

 

  • 저급 언어: 컴퓨터가 이해하고 실행하는 언어
    • 기계어: 0과 1로 이루어짐
    • 어셈블리어: 기계어를 사람이 이해하기 편하도록 만듦

 

(좌) 기계어 예시, (우) 어셈블리어 예시

 

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

고급 언어 → 저급 언어 변환 과정

  1. 컴파일
  2. 인터프리트
  • 고급 언어는 변환 방식에 따라 컴파일 언어 혹은 인터프리터 언어로 구분된다.
  • 그러나 칼로 무 자르듯 딱 나뉘는 것은 아니고(파이썬은 A, 자바는 B ..), 그냥 컴파일 방식과 인터프리터 방식 두 가지가 있다고 이해하는 편이 낫다

 

 

 

 

 

 

 

컴파일 언어

  • 고급 언어(소스 코드) → 컴파일(컴파일러) → 저급 언어(목적 코드)
  • 작성된 소스 코드는 컴파일러에 의해 저급 언어로 변환되고(컴파일), 컴파일 결과로 저급 언어인 목적 코드가 생성된다.
  •  
  • 컴파일은 오류를 사용자에게 알려준다. (컴파일 에러)
    • 컴파일은 단순히 바꾸는 것만 있는게 아니라 오류는 없는지, 부적절한 함수가 없는지 검사하는 과정도 포함된다.

 

  • 더 자세히 들어가면 전처리, 어셈블 등의 과정도 있다.

 

 

인터프리트 언어

  • 컴파일 처럼 코드 전체를 바꾸는 것이 아니라, 인터프리터에 의해 코드가 한 줄씩 실행
  • 소스 코드 전체가 저급 언어로 변환될 때까지 기다릴 필요가 없다
  • 오류 발생 직전까지의 코드는 실행된다.
  • 컴파일 & 인터프리트 과정을 실시간으로 볼 수 있는 사이트: https://godbolt.org/

 

 

 

 

컴파일 과정 (C 언어)

1) 전처리 → 2) 컴파일 → 3) 어셈블링 → 4) 링크

 

1. 전처리 과정 (preprocessing)

  • 소스 코드 전처리
  • 본격적으로 컴파일하기 전에 처리할 작업들을 수행
    • 외부에 선언된 다양한 소스 코드, 라이브러리 포함하기 (e.g. #include)
    • 프로그래밍 편의를 위해 작성한 매크로들을 변환하기 (e.g. #define)
    • 어디서부터 어디까지 컴파일할지, 컴파일 영역 명시 (e.g. #if, #ifdef …)

 

2. 컴파일 과정 (compile)

  • 전처리된 소스 코드 → 어셈블리어
  • 전처리가 완료되어도, 여전히 컴퓨터가 이해할 수 없는 소스 코드이다
  • 따라서 전처리된 소스 코드를 저급 언어(어셈블리어)로 변환하는 과정이 필요
  • 그걸 수행하는 게 컴파일 과정

 

3. 어셈블 과정 (assembling)

  • 어셈블리어 → 기계어
  • 목적 코드(object file)를 포함하는 목적 파일이 된다
    • 그러나 목적 파일은 실행 파일과 다르다
    • 둘 다 기계어로 이루어진 파일이지만, 목적 파일이 링킹(linking)을 거쳐야 실행파일이 된다

 

4. 링킹 (linking)

main.c만 실행하면 실행되지 않는다. main.c를 실행하기 위해선 helper.c에 있는 내용이 있어야 한다.

때문에 이들을 연결지어 하나의 파일로 합쳐주는 과정이 필요한데, 그것을 링킹(linking)이라고 한다.

 

 

 

main.c만 실행하면 실행되지 않는다. main.c를 실행하기 위해선 helper.c에 있는 내용이 있어야 한다.

때문에 이들을 연결지어 하나의 파일로 합쳐주는 과정이 필요한데, 그것을 링킹(linking)이라고 한다.

 

 

 

명령어의 구조

명령어의 형태

  • 1) 무엇을 대상으로, 2) 무엇을 수행하라

 

 

 

  • 이를 일반화하면, 명령어는 연산 코드오퍼랜드로 이루어져 있다.
    • 연산 코드: 수행할 연산
    • 오퍼랜드: 연산에 사용될 데이터 or 데이터가 저장된 위치 (데이터 주소)

기계어, 어셈블리어도 명령어이다.

연산 코드오퍼랜드 형태로 이루어져 있음을 확인 가능하다.

 

 

연산 코드

  • 연산 코드의 종류, 생김새는 CPU마다 다르다.
  • 공통적인 연산 코드의 종류는 크게 4가지가 있다. (외우기 보다는 이러한 느낌이다~를 받아들이자. CPU마다 연산 코드가 다르기 때문이다.) 
대표적인 연산 코드: 
1) 데이터 전송, 2) 산술/논리 연산, 3) 제어 흐름 변경, 4) 입출력 제어

 

 1. 데이터 전송

  • MOVE: 데이터를 옮겨라
  • STORE: 메모리에 저장하라
  • LOAD (FETCH): 메모리에서 CPU로 데이터를 가져와라
  • PUSH: 스택에 데이터를 저장하라
  • POP: 스택의 최상단 데이터를 가져와라

 

2. 산술/논리 연산

  • ADD/ SUBTRACT/ MULTIPLY/ DIVIDE: 사칙연산
  • INCREMENT/ DECREMENT: 오퍼랜드에 +1 / -1
  • AND/ OR/ NOT
  • COMPARE: 두 개의 숫자 또는 TRUE/FALSE 값을 비교하라

 

3. 제어 흐름 변경

  • : 특정 주소로 실행 순서를 jump해서 옮기는 연산 코드 유형
    • JUMP: 특정 주소로 실행 순서를 옮겨라
    • CONDITIONAL JUMP: 조건에 부합할 때 특정 주소로 실행 순서를 옮겨라
    • HALT: 프로그램의 실행을 멈춰라
    • CALL: 되돌아올 주소를 저장한 채 특정 주소로 실행 순서를 옮겨라 (= 함수 호출)
    • RETURN: CALL을 호출할 때 저장했던 주소로 돌아가라

 

4. 입출력 제어

  • READ (INPUT): 특정 입출력 장치로부터 데이터를 읽어라
  • WRITE (OUTPUT): 특정 입출력 장치로 데이터를 써라
  • START IO: 입출력 장치를 시작하라
  • TEST IO: 입출력 장치의 상태를 확인하라

 

 

오퍼랜드

  • 실제 오퍼랜드 필드 (오퍼랜드가 담긴 곳)에는 데이터 자체보다 데이터 주소(유효 주소)가 주로 담기게 된다.
    • 이에 오퍼랜드 필드 = 주소 필드 라고 부르기도 한다.
    • 유효 주소 (effective address): 연산에 사용할 데이터가 저장된 위치

 

  • 오퍼랜드는 없을 수도 있고, 2개 이상일 수도 있다.

 

 

 

Q. 왜 오퍼랜드에 데이터를 직접 넣지 않고, 데이터 주소를 주로 넣는 걸까?

명령어 내에서 표현할 수 있는 데이터의 크기가 제한되어 있기 때문이다.

명령어의 전체 크기가 16bit라고 가정해보자.
연산 코드에 4bit가 쓰이고, 오퍼랜드가 두 개 있을 경우 각각 6bit씩 가질 수 있다.
그럼 하나의 명령어에서 표현할 수 있는 데이터 크기는 2^6이 최대일 것이다.
따라서 값 대신 메모리 주소를 입력을 하면, 실제 값은 훨씬 큰 공간에 넣기 때문에 표현할 수 있는 정보의 크기가 제한되지 않는다.

 

(예시) 값 대신 R1이라는 레지스터(유효주소)를 가리키고, 컴퓨터는 해당 주소에 접근하는 구조.