kj0on

[Definition] 컴파일 (Compilation) 본문

Reversing/Definition

[Definition] 컴파일 (Compilation)

kj0on 2025. 7. 10. 20:04
목차 접기

1. 정의

표준 정의
ISO/IEC 15145:1997(en), 2.1.10 To transform source code into dictionary definitions.
ISO/IEC 20060:2010(en), 3.9 transform higher-level specifications of software and/or data into executable form
ISO/IEC 2382:2015(en), 2121864 translate all or part of a program expressed in a high-level language into a program expressed in an intermediate language, an assembly language, or a machine language
ISO/IEC/IEEE 24765:2017(en), 3.679 to translate a computer program expressed in a high-order language into its machine language equivalent

 

ISO/IEC 15145:1997은 "소스 코드를 사전(dictionary) 정의로 변환"

ISO/IEC 20060:2010은 "소프트웨어 또는 데이터의 상위 사양을 실행 가능한 형태로 변환"

ISO/IEC 2382:2015는 "고급 언어로 표현된 프로그램의 전부 또는 일부를 중간 언어, 어셈블리 언어 또는 기계 언어로 표현된 프로그램으로 번역"

ISO/IEC/IEEE 24765:2017은 "고급(고차) 언어로 작성된 컴퓨터 프로그램을 기계어 등가물로 번역"

 

위 표준들에서의 정의를 종합해 보면 컴파일은 사람이 이해할 수 있는 고수준 언어를 컴퓨터가 해석 또는 실행할 수 있는 저수준 형태(중간어, 기계어, 사전 구조)로 변환하는 과정을 의미한다.


2. 컴파일 & 인터프리터

[이미지1] 컴파일 & 인터프리터

일반적으로 C, C++은 소스 코드를 기계어로 변환해 목적 프로그램 또는 실행파일(exe)을 만들고, Python은 실행 시점에 소스를 바이트코드로 변환해 인터프리터(VM)가 즉시 실행하는 인터프리트 방식을 택한다. Java는 두 방식의 중간쯤에 위치해 소스를 먼저 바이트코드(.class)로 컴파일한 뒤 JVM이 그 바이트코드를 실행한다. 컴파일 방식은 한 번 빌드해 목적 프로그램을 만들어 두면 소스 없이도 반복 실행할 수 있는 반면, 인터프리트 방식은 매 실행마다 소스와 해당 인터프리터 환경이 함께 있어야 한다는 점이 가장 큰 차이다.

 

[이미지2] 컴파일 & 인터프리터

컴파일 언어는 분석 이후 합성(Synthesis) 단계를 거치기 때문에 처음 한 번은 빌드 시간이 필요하다. 반면 인터프리트 언어는 별도 빌드 단계 없이 곧바로 소스를 읽어 실행을 시작기 때문에 처음 실행은 인터프리트 방식이 더 빠를 수 있다. 다만 기계어로 완전히 변환된 컴파일 언어 프로그램은 실행 시 추가 해석이 없어, 일반적으로 인터프리트 언어 프로그램보다 빠르게 동작한다.


3. 컴파일 전체 과정

[이미지3] 컴파일 전체 과정

전형적인 빌드 파이프라인은 보통
(1) (소스) Skeleton Source → (2) 전처리기(Preprocessor) → (3) 컴파일러(Front-Back-End) → (4) 어셈블러 → (5) 링커 → (6) 완전한 실행 파일(Absolute Machine Code)
순으로 설명한다. 다만 문헌마다 컴파일의 범위를 다르게 잡기 때문에, 어떤 자료는 전처리, 컴파일, 어셈블까지만을 컴파일 단계로 보고 링커를 별도 단계로 분리하거나, 반대로 전처리까지 포함한 전 과정을 통틀어 컴파일이라 칭하기도 한다.


4. GCC

 

이번 실습에서는 GCC(GNU Compiler Collection)를 이용해 전처리 → 컴파일 → 어셈블 → 링크로 이어지는 전체 빌드 파이프라인을 직접 살펴본다. 각 단계 내부의 세부 절차는 설명 범위에서 제외한다.

 

4-1. 소스

[이미지4] hello.c

hello.c에는 전처리 지시문, 함수 정의, 주석 등이 그대로 들어 있다. 프로그램의 논리와 의도를 사람이 이해할 수 있는 형태로 담아 전처리기로 넘기는 출발점이 된다.


4-2. 전처리기

[이미지5] hello.i

전처리기는 #include, #define, 조건부 컴파일 지시문 등을 해석해 헤더를 삽입하고 매크로를 펼쳐 하나의 완전한 번역 단위로 정리한다. 컴파일러가 바로 토큰화를 시작할 수 있도록 소스를 정규화하고 불필요한 주석을 제거하는 역할을 한다.


4-3. 컴파일러

[이미지6] hello.s

컴파일러는 전처리 결과를 받아 토큰화, 분석을 거쳐 중간 표현(IR)을 만들고, 최적화를 수행한 뒤 대상 ISA에 맞는 어셈블리나 머신 코드로 변환한다. 언어 차원의 의미를 이해하고 하드웨어가 실행 가능한 명령, 레지스터 조합으로 설계하는 역할을 한다.


4-4. 어셈블러

[이미지7] hello.o

어셈블러는 컴파일러가 만든 어셈블리 텍스트를 0과 1의 기계어 바이트로 인코딩하고, 코드, 데이터, 심벌, 재배치 정보를 포함한 오브젝트 파일(.o)을 생성한다. 명령어 목록을 실제 CPU 명령어 시퀀스로 찍어내는 역할을 한다.


4-5. 링커

[이미지8] hello.out

링커는 여러 오브젝트 파일과 라이브러리를 모아 전역 심벌을 해석하고 섹션을 배치하며 재배치를 적용해 단일 실행 파일을 만든다. 흩어져 있던 코드와 데이터를 하나로 묶어 완전하게 주소가 결정된 프로그램 이미지를 조립하는 역할을 한다.


4-6. 완전한 실행 파일

[이미지9] hello.out

최종 실행 파일은 운영체제 로더가 바로 메모리에 적재해 CPU에 넘길 수 있는 형태로, 사용자가 명령을 내리면 즉시 실행된다.


4-7. GCC 전체 과정

[이미지10] GCC


5. Visual Studio

[이미지11] hello.c


cl /EP hello.c > hello.i # or cl /P hello.c

[이미지12] hello.i


cl /c /FAcs hello.c /Fohello.obj /Fahello.asm

[이미지13] hello.asm
[이미지14] hello.obj


link hello.obj /OUT:hello.exe /SUBSYSTEM:CONSOLE /ENTRY:mainCRTStartup

[이미지15] hello.exe


[이미지16] hello.exe


 

[이미지17] Visual Studio


6. 참고 문헌

 

[1] 컴파일(Compile)에 대한 이해, https://bradbury.tistory.com/226