본문 바로가기

개발 여행/CS

[혼공컴운] Chapter 3. 명령어

소스 코드가 명령어로 어떻게 변할까?

3-1. 소스 코드와 명령어

모든 소스 코드는 컴퓨터가 이해 가능한 명령어로 변환된다.

 

고급 언어와 저급 언어

고급 언어: 인간이 이해할 수 있음

저급 언어: 컴퓨터가 직접 이해 가능함

 

기계어: 0과 1로 이루어진 언어

어셈블리어: 기계어를 읽기 편한 형태로 변환한 저급 언어.

한 줄 한 줄이 명령어 단위이다.

로우 레벨 개발자(임베디드 등)의 경우 위 어셈블리어를 직접 작성해야 하는 경우도 있다.

 

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

 

컴파일 언어: 소스 코드가 컴파일이라는 과정을 거쳐 저급 언어로 변환되는 언어

컴파일 결과로 나온 저급 언어는 목적 코드이다.

위 과정은 굉장히 단순화된 과정이며,

C의 경우 전처리기를 거치고, 실행 파일이 생성되는 경우 등 컴파일 언어마다 세부 추가 과정이 붙는다.

컴파일 언어 사용자라면 꼭 공부해야 한다!

 

인터프리트 언어: 인터프리터에 의해 한 줄씩 실행되는 언어

소스 코드 전체를 변환할 필요가 없이 실행되므로 기다릴 필요가 없다.

한 번에 번역하냐? 한 줄씩 알려주냐?

 

실제 변환 결과를 볼 수 있다.

http://godbolt.org

같은 소스코드에 대해서도, 컴파일러에 따라 / CPU 종류에 따라 다른 어셈블리어가 만들어진다.

파이썬, 자바와 같은 인터프리터 계열은 바이트코드라 하는 중간 과정을 거친다.

 

주의: 컴파일 언어와 인터프리트 언어는 양분되는, 상호배척되는 관계가 아니다.

 

 

정리: 고급 언어(컴파일 언어 / 인터프리트 언어), 저급 언어(기계어 / 어셈블리어)

 

3-2. 명령어의 구조

소스 코드가 저급 언어로 바뀌었다. 이제 그 저급 언어의 생김새를 알아보자.

명령어는 연산의 종류와 연산에 사용할 데이터(혹은 위치)를 갖고 있다.

연산 코드: 명령어가 수행할 연산

오퍼랜드: 연산에 사용할 데이터 혹은 데이터가 저장된 위치

 

오퍼랜드: 데이터 자체를 저장하는 경우도 있지만, 주소를 훨씬 많이 저장한다.

오퍼랜드 필드: 오퍼랜드가 위치하는 명령어 내 공간. 주소 필드라고도 부른다.

명령어에 필요한 오퍼랜드의 개수에 따라, n-주소 명령어로 부름. 0일수도, 3일수도.

 

연산 코드: CPU마다 다르나 크게 네 종류가 있다.

데이터 전송 / 산술, 논리 연산 / 제어 흐름 변경 / 입출력 제어.

어떠한 연산 종류들이 있는지는 한 번씩 생각해두면 좋다. ‘무엇을 할 수 있는가?’

  • 데이터 전송: MOVE(데이터 이동), STORE(저장), LOAD(CPU로), PUSH, POP(스택)
  • 산술, 논리 연산: 사칙연산, ++, --, 논리연산, 비교연산
  • 제어 흐름 변경: GOTO, 조건, 정지, 현재 주소를 기억하고 GOTO, 기억한 주소로 RETURN
  • 입출력 제어: READ, WRITE, START IO, TEST IO

주소를 많이 쓰는 이유는? 명령어 길이에 제한이 있기 때문이다.

데이터를 직접 들고 오다가 터진다.

 

유효 주소: 연산에 사용될 데이터가 저장된 위치. 메모리일수도, 레지스터일수도.

명령어 주소 지정 방식: 유효 주소를 찾는 방법, 데이터를 어떻게 찾아가는지.

아래는 매우 기초적인 예시이다.

  • 즉시 주소 지정 방식: 직접 데이터 명시. 빠르다.
  • 직접 주소 지정 방식: 오퍼랜드 필드에 (메모리의)유효 주소를 직접 적음. 유효 주소 길이에 제한이 있다.
  • 간접 주소 지정 방식: 오퍼랜드 필드에 주소의 주소를 적음. 느리다(메모리를 두번 뒤진다)
  • 레지스터 주소 지정 방식: 데이터가 저장된 레지스터 명시. 메모리보단 레지스터에 접근하는게 훨씬 빠르다. CPU는 CPU 안에서만 확인하는게 빠르다.
  • 레지스터 간접 주소 지정 방식: 레지스터 안에 유효 주소를 적고, 필드에는 레지스터를 적는다.

 

정리: 명령어(연산 코드 + 오퍼랜드), 연산 코드, 오퍼랜드(데이터 혹은 주소), 주소 지정 방식(데이터를 찾아가는 방법)

 

3-3. C언어의 컴파일 과정 예시

전처리기, 컴파일러, 어셈블러, 링커를 거쳐서 소스 코드가 실행 파일이 된다.

gcc 내의 특정 명령어를 통해 일부 과정까지만 일어나도록 직접 확인이 가능하다.

 

전처리기: 전처리 결과물을 만든다(test.i).

컴파일을 하기 위한 사전 처리 작업으로, 라이브러리 포함 / 매크로 변환 등.

 

컴파일러: 전처리 완료 소스 코드를 저급 언어(어셈블리어)로 변환(test.s)

 

어셈블러: 어셈블리어를 기계어로 변환(test.o). 목적 코드를 포함하는 목적 파일(소스 코드의 변환 목적 파일)이 된다.

 

링커: 각기 다른 목적 파일을 하나의 실행 파일로 묶어주는 작업(test.exe). 여러 목적 파일들을 묶어서 필요한 기능들을 연결해준다.