사람이 이해하는 언어로 쓴 소스코드를 컴퓨터가 이해하는 기계어로 번역하는 작업. C나 자바로 쓴 코드를 전기 신호인 0과 1로 바꾸는 과정이고, 이 일을 수행하는 프로그램이 컴파일러다.

환경마다 다른 기계

사람 말을 컴퓨터 말로 바꾼다는 설명은 절반만 맞다. 나머지 절반은 컴퓨터를 쓰는 사람들의 컴퓨터가 서로 같지 않다는 사실에 있다.

고급 언어는 약속된 문법을 쓴다. 맥북으로 작성하든 윈도우로 작성하든 같은 C 코드가 나온다. 그런데 운영체제가 다르면 ISA도 다르고 컴퓨터가 작동하는 원리에 차이가 생길 수밖에 없다. 같은 윈도우라도 7과 10이 다르고, 내부의 CPU와 RAM에도 차이가 있다.

사용자 환경에 맞는 적절한 전기 신호를 보내야 하는데, 사람이 작성한 고급 언어만 보고는 그런 작업을 할 수 없다. 각자의 환경에 맞는 어셈블리어로 변환하고 다시 기계어로 변환하는 단계가 그래서 필요하다.

소스에서 실행 파일까지

고급 언어로 소스코드를 만드는 데서 시작한다. 컴파일러가 그것을 어셈블리 언어로 바꾸는데, 어셈블리 언어는 하드웨어가 어떤 ISA를 쓰느냐에 따라 달라진다. 이어서 어셈블러가 어셈블리 언어를 기계어로 옮겨 목적 코드를 만들고, 링커가 목적 코드들의 관계를 연결해 리눅스의 out이나 윈도우의 exe 같은 실행 파일을 만든다. 마지막에 로더가 그것을 메모리에 올리면 코드가 읽히며 실행된다.

C에서는 같은 일을 전처리, 컴파일, 어셈블리, 링킹 네 단계로 부른다. 전처리기는 원시 프로그램을 번역하기 전에 프로그램 안에 들어 있는 특별한 지시어를 먼저 해독하는 번역 프로그램이고, 링커는 목적 프로그램에 라이브러리에서 꺼낸 표준 함수와 사용자 함수를 연결해 실행 가능한 프로그램을 생성한다. test라는 소스 코드를 C로 작성했다면 test.c, test.obj, test.exe 세 파일이 만들어진다.

컴파일과 빌드

네 단계를 묶어 컴파일 과정 또는 빌드 과정이라 부르기도 하고, 컴파일 과정과 링킹 과정을 따로 나눠 부르기도 한다. 보통 빌드는 컴파일보다 넓은 의미로 쓴다. 빌드 = 컴파일 + 링킹이다.

인터프리터와의 차이

인터프리터는 소스 코드를 한 줄씩 읽어들여 즉시 실행한다. 컴파일러가 사람이 하는 말을 처음부터 끝까지 들은 뒤에 옮기는 통역이라면, 인터프리터는 동시통역이다. 줄 단위로 바로바로 실행되기 때문에 목적 프로그램이 따로 생성되지 않는다. 줄 단위로 진행되어 시분할 시스템에 유용한 대신 CPU 사용 시간의 낭비가 크다.

컴파일러인터프리터
번역 단위전체한 줄마다
실행 속도빠름느림
번역 속도느림빠름
목적 코드생성 O생성 X
예시C, C++, Java CompilerPython, JavaScript, Java Interpreter

번역은 빠른데 실행이 느린 까닭은 프로그램 전체로 보면 번역과 실행을 계속 반복하기 때문이다. 인터프리터는 위 다섯 단계 중 컴파일부터 로딩까지를 프로그램을 실행시키면서 진행하므로 느리지만 호환성 걱정이 없다. 번역 기법은 한 번 디코딩해 반복 실행하니 실행 시간 효율이 좋은 대신 번역된 프로그램이 큰 기억 장치를 요구하고, 인터프리터 기법은 큰 기억 장치를 요구하지 않고 사용자 적응성을 주는 대신 실행 시간 효율이 떨어진다.

둘이 다르기는 해도 확실하게 경계선을 그을 수는 없다. 최근에는 두 방식을 섞어 쓴다. 자바가 그렇다. 자바 컴파일러가 소스를 바이트코드로 바꾸고 JVM이 그것을 인터프리터 방식으로 실행한다. 컴파일의 호환성 문제와 인터프리터의 속도 문제를 절충한 구조이고, 남은 속도 손해는 JIT 컴파일러가 메운다.

관련

출처