시작하기 전에
컴파일 과정을 단적으로 말하면 분석(Analysis)과 통합(Synthesis)의 과정이라고 할 수 있습니다. 이 과정이 오기 전에 배웠던 내용들을 정리해보도록 하겠습니다.
컴파일러와 컴파일 과정
컴파일러는 원시 코드(source code)로부터 실행 가능한 파일을 실행 코드(executable code)로 바꾸는 과정이라고 할 수 있습니다. 일반적으로, 분석과 통합의 과정으로 구성됩니다.
언어
컴파일은 원시에서 목표로 가는 단방향 작업입니다. 원시와 목표 중에 정보의 양은 원시가 목표보다 크거나 같습니다.
컴파일러와 인터프리터
컴파일러(Compiler)
컴파일러는 원시 언어로 작성된 프로그램을 같은 의미가 있는 목적 언어로 변경하는 프로그램을 의미합니다. 컴파일러는 원시 프로그램을 가지고 목적 프로그램을 만들어줍니다. 목적 프로그램은 입력과 출력을 받아들일 수 있습니다.
인터프리터(Interpreter)
인터프리터는 원시 프로그램으로 명령이 수행됩니다. 개념적으로는 인터프리터는 원시 프로그램과 입력을 동시에 받아들여서 출력을 발생시킵니다.
혼합형(Hybrid)
이 방식은 원시 프로그램을 포터블 바이트-코드(p-code)로 컴파일이 된 후에 가상 머신(virtual machine)에 의해서 인터프리팅이 됩니다.
분석-통합 모델
컴파일러에는 2가지 부분이 있습니다. 분석에서는 트리 구조로 저장된 원시 프로그램으로 수행되는 연산이 결정됩니다. 통합에서는 트리 구조를 가지고 연산을 번역해서 목표 프로그램으로 바꾸도록 합니다. 여기서 분석은 컴파일러의 전단부(front-end)에 해당하며 기계에 독립적이고, 통합은 후단부(back-end)에 해당하는 내용이라 기계에 독립적입니다.
컴파일러 분석의 결과로 중간 코드라 불리는 것이 나오게 됩니다. 즉, 분석 과정은 중간 코드를 생산하는 과정이고, 통합 과정은 중간 코드를 사용하는 과정이라고 할 수 있습니다. 좀 더 일반적으로 이야기를 하면 이 과정을 보고 중간 표현이라고 칭합니다.
그리고 분석-통합 모델을 사용하는 도구들 알아보면 아래와 같습니다.
- 에디터(구문 강조)
- 예쁘게 출력하는 것(e.g. Doxygen)
- 정적 검사기(e.g. Lint 또는 Splint)
- 인터프리터
- 텍스트 조판(e.g. TeX 또는 LaTex)
- 회로 컴파일러(e.g. VHDL)
- 쿼리 인터프리터/컴파일러(Databases)
즉, 통합-분석 모델은 매우 자주 사용되며 이를 잘 아는 것은 매우 중요한 일입니다.
간단은 예를 확인해보겠습니다. 식은 값을 나타낸다고 하겠습니다. 예를 들어, 1+2
는 3
을 지칭합니다. 이를 분석하기 전에 먼저 피연산자(operands)와 연산자(operator)를 알아보도록 하겠습니다. 연산자는 연산이 적용되기를 요구하는 데이터의 집합에 대해서 연산을 수행시키는 것을 의미합니다. 그리고 연산으로 처리되기를 원하는 데이터를 피연산자라고 합니다. 여기서 연산 하나가 다루는 연산자의 수에 의해서 분류됩니다. 아래의 표가 그 분류 기준입니다.
연산자 | 예제 |
---|---|
단항(unary) 연산자 | ++1 |
이항(binary) 연산자 | 1 + 2 |
삼항(ternery) 연산자 | 0 ? 1 : 2 |
그렇다면 1 + 2 * 3
를 통해 분석-통합 모델이 어떻게 동작을 하는지 보여드리겠습니다. 먼저 식의 구문트리는 (ADD 1 (MUL 2 3))
으로 구성될 수 있습니다. 시각적인 모델로 만들면 아래와 같은 모습을 띨 것입니다.
익히 알려진 컴파일러인 gcc는 아래와 같이 동작합니다.
정리하면 컴파일러의 과정은 아래와 같습니다.
과정 | 출력 | 예제 |
---|---|---|
프로그래머 | 원시 문자열 | A=B+C; |
스캐너(Scanner)1 | 토큰 문자열 | ‘A’, ‘=’, ‘B’, ‘+’, ‘C’, ‘;’ 2 |
파서(Parser)3 | 파스 트리 혹은 문맥 트리 | (; (= A (+ B C))) |
의미 분석4 | 주석이 달린 파스 트리 또는 추상 문맥 트리 | |
중간 코드 생성기 | 3 주소 코드5 | |
최적화 | 3 주소 코드 | |
코드 생성기 | 어셈블리 코드 | |
Peephole 최적화기 | 어셈블리 코드 |
또한, 전체 원시 코드를 스캔하는 것을 패스(pass)라고 합니다. 오직 한 번 만에 단계가 끝나는 것을 단일 패스라고 하고, 그렇지 않은 것을 다중 패스라고 합니다. 정확히는 단일 패스는 보통 원시 프로그램을 사용하기 전에 모든 것을 정의할 필요가 있습니다. 하지만 다중 패스는 컴파일러가 메모리에서 표현된 프로그램 전체를 보통은 가집니다.
소프트웨어 개발 도구는 하나에서 그 이상의 컴파일러 과정의 실행으로 가능합니다. 그 내용은 아래와 같습니다.
- 파서 생성기(Parser Generator)
- 스캐너 생성기(Scanner Generator)
- 구문지시 번역 엔진(Syntax-directed translation engine)
- 코드-생성기 생성기(Code-generator grammar)
- 데이터흐름 분석 엔진(Data-flow analysis engine)