이 글은 1~48페이지까지 내용을 다루고 있습니다.
컴파일러란
컴파일러는 인간이 인식할 수 있는 언어에서 CPU가 인식할 수 있는 기계 명령어로 번역하는 역할을 담당하고 있습니다.
예를 들어 사람이 컴퓨터한테 print(3*3)을 실행시켜줘 했을 때 컴파일러가 CPU에서 이 말을 알아들을 수 있도록 기계 명령어로 번역하는 것을 의미합니다.
컴파일러의 동작 원리
컴파일러를 사용하지만 컴파일러가 어떻게 작동하는지 원리를 모를 수 있습니다. 아래 그림은 컴파일러가 왼쪽 코드를 오른쪽
int a = 1;
int b = 2;
while (a < b)
{
b = b - 1;
}
위 코드의 의미를 생각해봤을 때 다음과 같이 분석할 수 있습니다.
a 변수에 1을 할당합니다;
b 변수에 2를 할당합니다;
a < b이면 b가 1씩 줄어듭니다;
더 이상 a < b가 성립하지 않을 때까지 앞의 문장을 반복합니다;
사람은 위처럼 생각할 수 있지만 CPU 그렇지 않습니다, 그렇기 때문에 이제 컴파일러가 역할을 하게됩니다.
컴파일러는 먼저 각 항목을 잘게 쪼갭니다. 이때 각 항목이 가지고 있는 추가 정보를 함께 묶어서 관리합니다.
아래는 코드의 첫 번째 단어인 int를 예로 들어서 확인해보겠습니다. int 키워드의 정보 2개가 포함되어 있습니다. 이렇게 각 항목에 추가로 정보를 결합한 것을 전문 용어로 토큰(token)이라고 합니다.
컴파일러가 하는 첫 번째 작업은 소스 코드를 돌아나니면서 모든 토큰을 찾아내는 것입니다. 앞의 소스 코드를 처리하면 다음과 같은 토큰 24개가 추출됩니다.
이때 각각의 줄은 하나의 토큰을 의미합니다. T로 시작하는 왼쪽 열은 토큰 의미로 나타내고, 오른쪽 열은 각각의 토큰이 가지는 값을 나타냅니다. 이렇게 소스 코드에서 토큰을 추출하는 과정을 어휘 분석이라고 합니다.
여기까지 정리하면 컴파일러는 먼저 코드를 잘게 쪼개어 토큰을 만듭니다. 이때 토큰은 T(토큰 의미)와 결합된 토큰의 값으로 구성되어 있습니다.
이때 토큰이 표현하고자 하는 의미
위에서 컴파일러를 사용하여 소스코드를 토큰으로 변환했습니다. 하지만 이 토큰만으로는 아무것도 할 수 없기 때문에 토큰의 의도를 표현해야 합니다.
컴파일러는 구문에 따라서 토큰을 처리해야 하는데 아래 while문을 보면서 알 수 있습니다.
while (표현식)
{
반복 내용
}
컴파일러가 키워드의 토큰을 찾으면 다음 토큰이 ( 라는 것을 알고 있는 상태로 기다리게 됩니다. 하지만 토큰이 while 키워드에 필요한 토큰(T_While)이 아니라면, 컴파일러는 문법 오류를 보고하기 시작합니다. 이 과정을 무사히 넘어가면 다음 토큰이 bool 표현식이어야 한다는 것을 알고 기다립니다. 이어서 )와 {를 거쳐 마지막에 }를 만날 때까지 계속 기다리고 처리하는 과정을 반복하게 됩니다.
이러한 과정을 해석이라고 하며, 컴파일러는 구문에 따라 한 글자도 놓치지 않고 꼼꼼하게 작업을 진행하게 됩니다.
컴파일러가 해석해 낸 구조는 트리로 표현하는 것이 가장 좋습니다. 아래는 구문 규칙에 따라서 토큰을 해석한 다음 생성된 트리가 1.1절에서 설명했던 구문 트리이며, 이 트리를 생성하는 전체 과정을 '구문 분석'이라고 합니다.
구문 트리가 만들어지면 구문 트리에 이상이 있는지 확인합니다. 이때 이상이라는 것은 정수 값에 문자열을 더하면 안 되고, 비교 기호의 좌우에 있는 값 형식이 다르면 안 됩니다.
이런 과정을 거치면 프로그램에 이상이 없기 때문에 컴파일에 오류가 발생하지 않는 다는 것을 증명하게 됩니다.
컴파일러는 위에 구문 트리의 이상이 없는 것을 확인하게 된다면 위에서 만들어진 구문 트리를 기반으로 중간 코드(Intermediate Representation Code, IR Code)를 생성합니다.
a = 1
b = 2
goto B
A: b = b - 1
B: if a < b goto A
코드 생성
이 과정까지 진행되면 컴파일러는 이제 앞의 중간 코드를 어셈블리어 코드로 변환합니다. 다음 코드는 x86 어셈블리어 기반의 예시입니다.
이제 마지막으로 컴파일러는 이 어셈블리어 코드를 기계 명령어로 변환하게 되며 이런 방식으로 컴파일러는 인간이 소스 코드라고 부르는 문자열을 CPU가 실행할 수 있는 기계 명령어로 번역합니다.