컴파일러
컴파일러는 한 언어로 쓰인 프로그램을 다른 언어로 옮기는 프로그램입니다. 옮기는 동안 프로그램의 뜻은 그대로 둡니다. 사람이 읽고 쓰는 언어로 적은 소스를 기계가 실행할 형태로 바꾸는 자리에 주로 씁니다.
상세
외국어로 쓰인 책을 번역가가 우리말로 옮깁니다. 원문과 번역본은 다른 언어로 적혀 있지만 담긴 이야기는 같습니다.
컴파일러는 한 언어로 쓰인 프로그램을 다른 언어로 옮기되 그 뜻을 보존하는 프로그램입니다. 옮기기 전의 언어를 원시 언어, 옮긴 뒤의 언어를 목적 언어라고 부릅니다. 옮기는 일 자체는 번역(translation)이라고 부릅니다. C 언어 표준 ISO/IEC 9899:2011 의 최종 위원회 초안인 N1570 은 번역을 여덟 단계로 나눠 적습니다. 번역 문법 규칙 사이의 우선순위는 그 단계들이 정합니다.
목적 언어가 반드시 기계어인 것은 아닙니다. 가상 머신이 실행하는 형식일 수도 있고, 실행 전에 한 번 더 다뤄지는 중간 표현일 수도 있습니다. 무엇으로 옮기느냐는 그 컴파일러가 정하는 것이지 컴파일러라는 말이 정하는 것이 아닙니다.
컴파일러가 맡은 일은 옮기는 데서 끝납니다. 옮긴 결과를 실행하는 일은 다른 프로그램이 맡습니다.
배경
컴퓨터 초창기에 프로그램은 기계가 곧바로 해석하는 수치 명령으로 적었습니다. 그 명령의 생김새는 기계마다 달랐습니다. 사람이 읽어서는 무엇을 하는 코드인지 알아보기 어려웠습니다. 같은 계산을 다른 기계에서 돌리려면 명령을 처음부터 다시 적어야 했습니다.
그래서 사람이 읽고 쓰기에 맞는 표기로 프로그램을 적어 두고, 그것을 기계가 실행할 형태로 바꾸는 일은 기계에게 맡기자는 요구가 생겼습니다. 바꾸는 일을 사람이 손으로 하면 옮겨 적는 자리마다 실수가 생깁니다. 대상 기계가 바뀔 때마다 같은 일을 처음부터 되풀이해야 합니다.
그 요구를 받아 나온 것이 원시 언어를 목적 언어로 옮기는 프로그램입니다. 여기에 컴파일러라는 이름이 붙었습니다. 이름이 가리키는 것은 옮기는 절차가 아니라 그 절차를 수행하는 프로그램입니다.
동작
flowchart TD
A["문자 매핑과 줄 잇기"] --> B["전처리 토큰으로 분해"]
B --> C["전처리 지시문 실행"]
C --> D["문자와 문자열 변환"]
D --> E["토큰으로 바꿔 번역 단위로 번역"]
E --> F["외부 참조 해소와 프로그램 이미지"]
첫 단계는 소스 파일의 다중바이트 문자를 소스 문자 집합으로 옮깁니다. 줄 끝 표시가 있으면 필요에 따라 개행 문자를 넣습니다. 이 매핑을 어떤 방식으로 할지는 구현이 정합니다. 삼중자 열은 그에 해당하는 한 글자짜리 내부 표현으로 바뀝니다. 이어서 백슬래시 바로 뒤에 개행이 오는 자리를 지워 물리적 소스 줄을 논리적 소스 줄로 잇습니다.
세 번째 단계에서 소스 파일이 전처리 토큰과 공백 문자 열로 쪼개집니다. 주석은 각각 공백 한 글자로 바뀝니다. 개행 문자는 남습니다. 개행이 아닌 공백 열을 그대로 둘지 공백 하나로 줄일지는 구현이 정합니다.
네 번째 단계에서 전처리 지시문이 실행되고 매크로 호출이 확장됩니다. #include 지시문을
만나면 그 헤더나 소스 파일을 1단계부터 4단계까지 재귀적으로 처리합니다. 처리가 끝나면
전처리 지시문은 모두 지워집니다.
다섯 번째와 여섯 번째 단계는 문자를 다룹니다. 문자 상수와 문자열 리터럴 안의 문자와 이스케이프 시퀀스가 실행 문자 집합의 대응 원소로 바뀝니다. 대응하는 원소가 없으면 널 문자가 아닌, 구현이 정한 원소로 바뀝니다. 그다음 서로 붙어 있는 문자열 리터럴 토큰들이 하나로 이어집니다.
일곱 번째 단계에서 토큰을 가르던 공백은 더 이상 의미를 갖지 않습니다. 전처리 토큰이 토큰으로 바뀝니다. 그 토큰들이 구문 분석과 의미 분석을 거쳐 하나의 번역 단위로 번역됩니다.
여덟 번째 단계에서 외부 객체와 함수 참조가 모두 해소됩니다. 지금 번역하는 단위에 정의가 없는 함수와 객체의 외부 참조를 채우려고 라이브러리 구성 요소가 링크됩니다. 이렇게 나온 번역기 출력이 모두 모여 프로그램 이미지가 됩니다. 프로그램 이미지에는 실행 환경에서 실행하는 데 필요한 정보가 담깁니다.
여덟 단계를 다 지나야 프로그램 이미지가 나옵니다. 실제 도구는 중간에서 멈추기도 합니다. 어디서 멈추느냐에 따라 손에 남는 것이 전처리된 소스, 어셈블리 코드, 목적 파일로 갈립니다. 멈추는 지점을 고르는 실물은 아래 예시가 받습니다.
예시
Clang
clang 은 자신을 C, C++, Objective-C 컴파일러라고 소개합니다. 전처리, 구문 분석, 최적화, 코드 생성, 어셈블, 링크를 아우른다고 적습니다. 어떤 상위 모드 설정이 전달되느냐에 따라 전체 링크를 하기 전에 멈춘다고도 적습니다. 문서는 컴파일 단계를 여섯으로 나눕니다.
- 드라이버 —
clang실행 파일은 컴파일러, 어셈블러, 링커 같은 다른 도구의 전체 실행을 제어하는 작은 드라이버입니다 - 전처리 — 입력 소스 파일의 토큰화, 매크로 확장,
#include확장, 그 밖의 전처리기 지시문 처리를 맡습니다. 이 단계의 출력은 C 의 경우 대개.i파일이라고 부릅니다 - 구문 분석과 의미 분석 — 입력 파일을 파싱해 전처리기 토큰을 파스 트리로 옮깁니다. 파스 트리가 되고 나면 의미 분석을 적용해 식의 타입을 계산하고 코드가 제대로 된 형태인지 판정합니다. 컴파일러 경고 대부분과 파스 오류를 내는 것이 이 단계입니다. 이 단계의 출력이 추상 구문 트리(Abstract Syntax Tree, AST)입니다
- 코드 생성과 최적화 — 추상 구문 트리를 저수준 중간 코드(LLVM Intermediate Representation,
줄여서 LLVM IR)로 옮기고 최종적으로 기계어까지 옮깁니다. 출력은 대개
.s파일 또는 어셈블리 파일이라고 부릅니다. 통합 어셈블러를 쓰면 코드 생성기가 목적 파일을 곧바로 냅니다 - 어셈블러 — 대상 어셈블러를 돌려 컴파일러의 출력을 대상 목적 파일로 옮깁니다. 출력은 대개
.o파일이라고 부릅니다 - 링커 — 대상 링커를 돌려 여러 목적 파일을 실행 파일이나 동적 라이브러리로 합칩니다
단계를 끊는 옵션이 따로 있습니다.
-E 전처리 단계를 돌린다
-fsyntax-only 전처리 · 파서 · 의미 분석 단계를 돌린다
-S 앞 단계에 더해 LLVM 생성과 최적화, 대상별 코드 생성까지 돌려 어셈블리 파일을 낸다
-c 위의 전부에 어셈블러까지 돌려 대상 `.o` 목적 파일을 만든다
GCC
GCC(GNU Compiler Collection) 문서는 파일 확장자로 무엇이 들어왔는지 가릅니다. file.c 는
전처리가 필요한 C 소스 코드입니다. file.s 는 어셈블러 코드입니다. 알아보는 접미사가 없는
파일 이름은 링크로 곧장 들어가는 목적 파일로 취급합니다. 멈추는 지점을 정하는 옵션의 뜻도
함께 적혀 있습니다.
-E 전처리 단계 뒤에 멈춘다. 컴파일러 본체는 돌리지 않는다.
출력은 전처리된 소스 코드이고 표준 출력으로 나간다
-S 컴파일 본단계 뒤에 멈춘다. 어셈블은 하지 않는다.
출력은 어셈블러가 아닌 입력 파일마다 하나씩 나오는 어셈블러 코드 파일이다
-c 소스 파일을 컴파일하거나 어셈블하되 링크는 하지 않는다.
최종 출력은 소스 파일마다 하나씩 나오는 목적 파일이다
javac
javac 명령은 자바 프로그래밍 언어로 쓰인 모듈, 패키지, 타입 선언이 담긴 소스 파일을 읽습니다. 그리고 그것을 자바 가상 머신에서 도는 클래스 파일로 컴파일합니다. 여기서 목적 언어는 기계어가 아니라 가상 머신이 실행하는 형식입니다. javac 명령은 자바 소스 파일과 클래스 안의 어노테이션도 처리할 수 있습니다.
Python 의 compile()
컴파일이 명령줄 도구의 모습으로만 나타나는 것은 아닙니다. 파이썬은 이 일을 내장 함수로 냅니다.
compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1)
소스를 코드 객체나 추상 구문 트리 객체로 컴파일합니다. 코드 객체는 exec() 나 eval()
이 실행합니다. source 는 일반 문자열, 바이트 문자열, 추상 구문 트리 객체 가운데 하나일 수
있습니다. filename 인자는 코드를 읽어 온 파일을 가리킵니다. 파일에서 읽은 것이 아니면
알아볼 만한 값을 넘기라고 적혀 있습니다. '<string>' 이 흔히 쓰입니다. mode 인자는 어떤
종류의 코드를 컴파일할지 정합니다. 소스가 문장 여러 개면 'exec', 식 하나면 'eval',
대화형 문장 하나면 'single' 입니다.
관련 항목
번역이 지나가는 처리 단계
전처리기 · 매크로 확장 · 전처리 토큰 · 삼중자 · 어휘 분석 · 구문 분석 · 파스 트리 · 추상 구문 트리 · 의미 분석 · 중간 표현 · 최적화 · 코드 생성 · 번역 단위 · 소스 문자 집합 · 실행 문자 집합 · 이스케이프 시퀀스 · 어노테이션 처리
산출물이 갖는 형태
어셈블리 · 목적 파일 · 실행 파일 · 동적 라이브러리 · 클래스 파일 · 바이트코드 · 프로그램 이미지
산출물을 만들고 잇는 도구·규약
어셈블러 · 링커 · 빌드 시스템 · ABI(Application Binary Interface)
번역 결과를 실행하는 런타임
인터프리터 · 가상 머신 · JIT(Just-In-Time) 컴파일 · 실행 환경
컴파일러와 헷갈리는 이웃
다른 이름: compiler