목차

소스코드 컴파일

일반적으로 소스 파일은 ASCII 나 기타 문자 셋으로 이루어져 있다. 순수 텍스트 파일로 사람이 인지할 수 있는 형태를 띄는데 이것을 기계가 이해할 수 있는 형태로 변환하는 과정을 컴파일이라 칭한다.

일부 언어 시스템은 소스 파일을 토큰화 형식으로 유지하기도 하는데 (특히 인터프리터) 이는 소스 언어에서 사용되는 예약어와 기타 문법 요소들을 단일 바이트의 토큰값으로 나타낸다.

언어 처리기

컴퓨터 언어 시스템은 다음의 네가지 종류가 있다.

컴파일러

위 네가지 종류중 가장 흔히 접하게 되는 컴파일러에 대해 더 알아보자면

일반적으로 다음의 네가지 단계로 이루어진다.

어휘 분석 단계

어휘 분석은 스캐너에 의해 수행된다. 원본 소스 파일을 구성하는 가장 작은 요소들(C언어의 if 나 while 같은 예약어나 bIsReady 같은 식별자 등) 을 토큰이라 불리우는 가장 작은 요소로 변환한다.

이 토큰들은 일반적으로 아래의 형태를 띈다.

문자열 12345 를 스캔하여 토큰화한 경우를 보면

 ┌─────┐
 │   345    │  - "토큰" 값
 ├─────┤
 │    5     │  - 토큰 유형. 즉 토큰이 예약어인지, 식별자인지, 정수 상수 혹은 연산자인지 여부
 ├─────┤
 │  12345   │  - 토큰 속성. 실제 값
 ├─────┤
 │ "12345"  │  - 스캔한 그대로의 문자열
 └─────┘

스캐너가 어휘소를 토큰화 함으로써 이후 이어지는 파싱 단계에서 파서가 문자열 데이터를 처리하는게 아니라 숫자 값을 처리하게 하여 속도를 절약할 수 있다.

구문 분석 (파싱)

파서는 토큰을 분석하여 문법적으로 의미적으로 옳은지 검사한다.3) 그리고 에러를 발견하여 보고하기도 한다.

파서가 위에 언급한 행위 (문법적 의미적으로 옳은지 검사) 를 수행하고 이 과정 이후로 이어지는 코드 생성 및 최적화를 수행하기 위해서는 소스 파일 본문을 랜덤하게 액세스할 필요가 있다. 따라서 파서는 소스 코드를 나타내는 자료 구조(Abstract Syntax Tree 로 줄여서 AST 라고 부른다.) 를 만들어 둠으로써 코드 생성과 최적화 단계에서 프로그램 본문의 여러 부분을 쉽게 참조하는 것을 가능토록 한다.

소스 코드 본문의 자료구조화의 예는 다음과 같다. “12345 + 6” 을 예로 들면

위와 같은 형태가 된다.

중간 코드 생성

AST 표현을 준 기계어 코드로 변환해주는 단계이다. 네이티브로 바로 변환하지 않고 중간 코드(준 기계어)로 바꾸는 이유는 이러하다.

위와 같이 중간 코드 생성단계를 둠으로써 네이티브 코드 생성 전에 수행할 수 있는, 즉 CPU 에 독립적인 모든 작업을 몰아 넣음으로써 네이티브 코드 생성 단계를 가볍게 유지할 수 있다.

중간 코드 최적화

중간 코드 최적화 단계에서는 CPU 에 독립적으로 수행할 수 있는 최적화가 이루어진다.

; 실제 어셈이 아니라 중간 코드라 가정

move i, 5            ; 5를 i에 대입
move j, i             ; i를 j에 대입
move k, j            ; j를 k에 대입
add m, k            ; k값을 m과 더하여 m에 대입

위 예에서 i 와 j 가 사용되지 않는 다면 이 코드는 아래처럼 최적화 가능하다.

move k, 5
add m, k

만약 k 도 이후에 사용되지 않는다면 add m, 5 로도 가능하다.

이는 CPU 에 의존적인 최적화가 아니라는 점을 알아두자.

중간 코드 최적화 종류에는 이런 것들이 있다.

네이티브 코드 생성

중간 코드의 최적화까지 마치면 이를 기계어 코드로 번역한다.

이 과정에서 CPU 에 의존적인 명령어를 사용한다. 이 다음 최적화도 마찬가지

컴파일러 출력물

컴파일러의 출력물은 일반적으로 실행가능한 기계어 코드겠지만 다른 경우도 있다.

다른 언어 코드로 된 출력물

컴파일러의 출력물이 다른 low 한 언어로 된 코드인 경우가 있다. 이 경우 대부분 C 언어를 택하는데 일단 모든 플랫폼에 컴파일러가 존재하고 충분히 low 하기 때문이다.

이런 경우 각 플랫폼의 c 언어 컴파일러에 의존할 수 있고 테스트해볼 수 있다는 장점이 있다.

하지만 두번째 컴파일러를 돌려야 하므로 최종 실행파일을 얻기 위해 시간이 많이 소비된다는 단점도 있다.

C-- 란 언어는 컴파일러의 결과물로 사용하기 위해 low 한 컨셉으로 제작된 언어이다.

어셈블리 언어 코드로 된 출력물

대표적인 예로는 FSF/GNU GCC 컴파일러의 경우 Gas 어셈블러용 어셈블리 언어를 출력한다.

장점으론 출력물이 어떤 기계어 명령에 대응되는지를 파악하기 쉽고, 소스 코드 중간에 직접 어셈블리 코드를 삽입할 수 있다는 점이 있다.

단점으로는 위와 같이 두번째 컴파일러(이 경우 어셈블러) 를 거쳐 실행파일을 만들어야 한다는 점이다.

1) 예약어나 리터럴 상수 등의 언어 요소
2) 자바는 이 속도 문제를 보완하기 위해 저스트 인 타임 컴파일 이란 테크닉을 사용한다. 이는 바이트코드 명령을 마주칠 때 해당 명령어를 실제 기계어 코드로 번역해둔다. 이렇게 함으로써 같은 구문을 다시 마주칠 때 번역하는 시간을 절약할 수 있게 해준다.
3) 구문 분석과 의미 분석을 구분한 컴파일러도 있지만 대개 파서 하나로 통합되어 있는 것이 많다.