프로그래밍 언어를 공부하다 보면 자연스럽게 마주치는 개념이 컴파일러입니다. 하지만 컴파일러가 정확히 무엇인지, 어떻게 작동하는지에 대해서는 막연한 경우가 많죠. 이번 글에서는 컴파일러의 개념부터 구조, 자동화 도구까지 차근차근 정리해 보겠습니다.
🎯컴파일러란?
A compiler is a computer program which translates other programs written in a particular high-level programming language into executable code for a specific target computer.
컴파일러(Compiler)란 고급 프로그래밍 언어로 작성된 소스 코드를 기계어로 변환해 실행 가능한 형태로 만드는 프로그램입니다. 다시 말해, 사람이 이해할 수 있는 코드 → 컴퓨터가 이해할 수 있는 코드로 번역하는 역할을 하죠.

📌 번역기(Translator)의 정의
번역기란, 한 프로그래밍 언어로 작성된 프로그램을 입력으로 받아,
그와 동등한 의미를 가지는 다른 프로그래밍 언어로 변환해 주는 시스템 프로그램입니다.
쉽게 말해, 사람이 이해하기 쉬운 언어 → 컴퓨터가 이해할 수 있는 언어로 바꿔주는 **'언어 간 통역기'**라고 할 수 있죠.
💻 구성 요소 정리
소스 프로그램 (Source Program) | 번역기의 입력으로 들어가는 원본 코드 |
소스 언어 (Source Language) | 소스 프로그램이 작성된 언어 (예: 파스칼, C) |
목적 언어 (Object/Target Language) | 변환된 프로그램이 표현되는 언어 (예: 어셈블리어, 기계어) |
목적 프로그램 (Object Program) | 번역기를 통해 생성된 프로그램 |
예시
소스 언어 : 파스칼, C언어 (고급언어)
목적 언어 : 어셈블리어, 기계어
사용되는 번역기 : 컴파일러
✅ 참고 사항
- 소스 프로그램이 목적 언어로 번역되어 실행 가능한 상태가 되면,
소스 코드를 수정하지 않는 한 언제든지 반복 실행할 수 있습니다. - 이는 개발의 생산성을 높이고, 프로그램의 재사용성과 효율성을 크게 향상하는 요소 중 하나입니다.
- 로드 모듈(load module) : 로더(loader)의 출력이 실행 가능한 프로그램(executable program)
=> 주 기억 장치의 사용자 영역에 적재되어 운영체제와 실행 환경(run-time environment)의 도움을 받아 실행되어 결과를 출력
* 크로스 컴파일러(cross-compiler)란?
: 소스 프로그램을 컴파일러가 실행되고 있는 기계에 대한 기계어로 번역하는 것이 아니라 다른 기종에 대한 기계어로 번역하는 컴파일러
ex) 썬사의 워크 스테이션(SPARC 프로세서)에서 수행되는 C 언어 컴파일러가 펜티엄 프로세서를 위한 목적코드를 생성하는 경우
=> 크로스 컴파일러의 출력 프로그램을 실행하기 위해서는 코드 인터프리터(애뮬레이터)가 필요
다양한 번역기들의 정의
어셈블러(assembler) | 어셈블러 언어로 쓰여진 프로그램을 입력으로 받아 기계어 프로그램으로 바꾸어 주는 번역기 |
인터프리터(interpreter) | 중간 언어를 입력으로 받아 목적 언어로 변환하지 않고 직접 실행해서 그 결과를 출력하여 주는 프로그램 |
프리프로세서(preprocessor) | 프로그래밍 언어에 유용한 기능들을 추가시켜 언어를 확장하는 역할 |
* 반복 실행하는 운용 시스템에서는 컴파일러가 효율적, 개발 시스템이나 교육용 시스템에서는 인터프리터가 능률적
프리 프로세서의 기능
- 매크로 체제(유사한 소스코드를 매크로로 정의하고 확장)
- 컴파일 시간에 필요한 컴파일 시간 라이브러리(compile-time library)들을 포함
- 조건부 컴파일(조건에 따라 소스프로그램의 일부분을 선택적으로 삽입 또는 삭제)
요약
🧩 컴파일러의 기본 구조
컴파일러의 구조는 전단부(front-end)와 후단부(back-end)로 나눌 수 있다
🔍 전단부 (Front-End)
소스 프로그램을 분석하고 중간 코드를 생성하는 부분
📌 주요 특징
- 소스 언어에 의존적입니다.
- 문법 이론(grammar theory)에 기반하여 잘 정리되어 있으며, 언어마다 하나씩 필요합니다.
- 어휘 분석기 → 구문 분석기 → 의미 분석기 → 중간 코드 생성기 순으로 처리됩니다.
📂 하는 일
- 프로그램을 읽어 들이고 토큰(token) 단위로 분석
- 문법과 의미 오류를 체크
- 중간 코드(Intermediate Code)를 생성
⚙️ 후단부 (Back-End)
중간 코드를 실제 목적 기계에 맞는 기계어 코드로 번역하는 부분
📌 주요 특징
- **목적 기계(Target Machine)**에 의존적입니다.
- 전단부에서 생성된 중간 코드를 받아, 해당 기계가 이해할 수 있는 목적 코드를 생성합니다.
- 기계 최적화, 레지스터 관리, 메모리 할당 등 실제 실행 성능과 관련된 요소를 처리합니다.
- 목적 기계당 하나씩 필요하며, 여전히 경험적인 방법으로 구현되는 경우가 많습니다.
컴파일러는 보통 다음과 같은 단계로 구성됩니다.
1. 어휘 분석기 (Lexical Analyzer)
어휘 분석기(또는 스캐너(scanner))에 의해 이루어지며 소스 코드를 토큰(token) 단위로 쪼개는 과정입니다. 예를 들어 if (a > 10)이라는 코드가 있다면 if, (, a, >, 10, ) 각각이 토큰이 됩니다.
* 토큰 : 문법적으로 의미를 갖는 최소의 단위로 프로그램은 토큰의 열로서 구성되어 있다.
- 어휘 분석 단계의 출력은 이러한 일련의 토큰들이다.
- 각 토큰들은 효율적인 처리를 위해 고유의 내부번호를 정수 형태로 갖는데 이것을 토큰 번호(token number)라 부른다.
- 생성된 토큰들은 다음 과정인 구문 분석 단계의 입력이 된다
2. 구문 분석기 (Syntax Analyzer = Parser)
토큰들이 문법적으로 맞는지 확인하고(error checking) 구문 트리(Syntax Tree)를 생성합니다. 문장이 맞는 구조인지 판별하죠.
- 소스 프로그램에 에러가 있으면 에러 메시지를 출력하고 그렇지 않으면 프로그램에 대한 구문 구조를 트리 형태로 만들어 출력
- 파스 트리(parse tree) & 추상 구문 트리(abstract syntax tree)
3. 의미 분석기 (Semantic Analyzer)
변수 타입, 연산 가능 여부 등을 분석하여 의미적인 오류를 확인합니다.
가장 중요한 일은 형 검사(type checking)이다.
* 형 검사란 각 연산자(operator)가 소스 언어의 정의에 맞는 피연산자(operand)를 가지는가를 검사하는 것이다.
4. 중간 코드 생성기(intermediate code generator)
입력을 추상 구문 트리로 받음 => 의미 검사(semantic checking) => 중간 코드 생성
기계어에 가까운 중간 표현(IR, Intermediate Representation)을 생성합니다. 예: Ucode, CIL 등
중간 코드의 생성은 구문 분석 단계에서 만들어진 구문 구조를 이용하여 코드를 생성한다.
5. 코드 최적화기(code optimization)
- 불필요한 연산을 제거하거나, 반복문 내 중복 계산을 줄이는 등 실행 속도를 높이기 위한 최적화를 수행합니다.
- 최적화 과정은 선택적인 단계(optional phase)로 생략되는 경우도 있다.
- 그 기능은 같은 의미를 유지하면서 코드를 보다 더 효율적으로 만들어 코드 실행시 기억 공간이나 실행 시간을 절약하는 데 있다
최적화
- 지역 최적화(local optimization, peeophole optimization)
=> 기본 블록(basic block)에서 행해지며 일련의 비효율적인 코드들을 구분하고 좀 더 효율적인 코드로 개선
* 지역 최적화 효과
(1) 컴파일 시간 상수 연산(constant folding)
(2) 중복된 load, store 명령문 제거
(3) 식(expression)의 대수학적 간소화(algebraic simplifcation)
(4) 연산 강도 경감(strength reduction)
(5) 불필요한 일련의 코드 블록(null sequence) 삭제
- 전역 최적화(global optimization)
=> 흐름 분석 기술(flow anaylsis technique)을 이용하여 기본 블록들 사이에 최적화를 행하는 것
- 공통 부분식(common subexpression)의 축약
- 루프 내에서 값이 변하지 않는 코드(loop invariant code)를 루프 밖으로 이동(code motion)
- 도달될 수 없는 코드(unreachable code)의 제거
* 최적화는 위치에 따라 precode optimization과 postcode optimization으로 나눌 수 있다
- Precode optimization은 중간 코드를 이용하여 최적화를 수행하며 위치는 목적 코드 생성 전에 행해짐
- Postcode optimization은 목적 코드 생성 후에 목적 코드를 최적화하는 방법(기계 의존적인 최적화 방법)
6. 목적 코드 생성기 (Target Code Generator)
중간 코드를 입력으로 받아 그와 의미적으로 동등한 목적 기계(target machine)에 대한 코드를 생성하는 일을 한다.
목적 코드는 **중간 코드(Intermediate Code)**를 실제 기계가 이해할 수 있는 **기계어(Machine Code)**로 변환한 것입니다. 이 과정에서 컴파일러는 다음과 같은 작업들을 수행합니다.
📌 1. 목적 코드 선택 및 생성
- 중간 코드의 의미에 맞는 적절한 기계어 명령어를 선택하여 생성합니다.
- 각 플랫폼(SPARC, Intel, ARM 등)의 특성을 고려하여 최적의 명령어를 구성합니다.
📌 2. 레지스터 운영
- **레지스터(Register)**는 CPU가 직접 접근하는 가장 빠른 저장 공간입니다.
- 효율적인 레지스터 운영은 임시 변수 사용을 줄이고, 전체적인 실행 속도를 향상시킬 수 있습니다.
📝 **레지스터 할당 문제(Register Allocation)**는 고성능 코드를 만들기 위한 매우 중요한 최적화 포인트입니다.
📌 3. 기억 장소 할당
- 변수나 데이터에 대해 적절한 메모리 주소를 지정합니다.
- 스택, 힙, 글로벌 영역 등을 적절히 활용하여 메모리 충돌 없이 실행되도록 합니다.
📌 4. 기계 의존적인 코드 최적화
- 성능을 높이기 위해, 여러 명령어를 하나의 효율적인 명령어로 대체하거나,
- 처리 속도가 빠른 명령어로 바꿔주는 작업입니다.
📂 심벌 테이블(Symbol Table)
- 어휘 분석기와 구문 분석기에서 추출한 정보(변수명, 타입, 범위 등)는 **심벌 테이블(symbol table)**에 저장됩니다.
- 이후 의미 분석, 코드 생성, 최적화 등 다양한 단계에서 참조의 기준이 됩니다.
🚨 에러 처리(Error Handling)
컴파일러는 단순히 번역만 하는 것이 아닙니다.
프로그래머가 작성한 코드에서 오류를 찾아내고, 명확하게 알려주는 기능도 매우 중요합니다.
- 에러 감지(Error Detection)
- 에러 복구(Error Recovery)
- 에러 보고(Error Reporting)
이러한 기능은 **컴파일러 내의 에러 처리 루틴(error handling routine)**에서 수행됩니다.
🧩 컴파일러는 어떻게 구성될까? – 패스(Pass) 구조
컴파일러는 여러 복잡한 작업을 단계별로 분리하여 처리하는데, 이를 **패스(pass)**라는 단위로 나누어 구현합니다.
📌 패스란?
하나의 패스는 소스 프로그램 또는 앞 패스의 출력을 읽어들여,
정해진 기능을 수행한 뒤 중간 파일로 출력하고, 이 결과는 다음 패스의 입력이 됩니다.
🧪 패스 구조의 종류
단일 패스 컴파일러 (Single-Pass Compiler) | 전 과정을 한 번에 처리 빠르지만 기능에 제약 |
다중 패스 컴파일러 (Multi-Pass Compiler) | 여러 단계로 나눠서 처리 기억 공간은 적게 사용하지만 속도는 느림 |
💡 다중 패스는 메모리를 절약할 수 있지만, 한 패스를 끝낸 후 다음을 기다려야 하므로 속도가 느릴 수 있습니다.
✨ 컴파일러 자동화 도구란?
컴파일러 자동화 도구(Compiler-Generating Tools)란,
언어 명세(language description)와 기계 명세(machine description)를 입력으로 받아 컴파일러의 특정 단계를 자동으로 생성해주는 도구입니다.
왜 필요한가요?
- 프로그래밍 언어와 하드웨어 플랫폼이 다양해지면서 N개의 언어 × M개의 플랫폼 = N × M 개의 컴파일러가 필요합니다.
- 이를 일일이 수작업으로 만들기는 매우 번거롭고 비효율적이기 때문에, 자동화 도구가 탄생하게 된다
생성기- 생성기(generator-generator)는 생성될 단계의 기능을 묘사하는 메타 언어(meta language)를 입력으로 받아
각 단계가 사용하게 될 테이블을 출력한다.
=> 그러면, 각 구동기(driver routine)은 이 테이블을 이용하여 그 단계에서 수행해야 할 일을 처리한다
🧩 주요 컴파일러 자동화 도구
1️⃣ Lex (Lexical Analyzer Generator)
Lex는 1975년 M. E. Lesk가 개발한 어휘 분석기 생성기입니다.
📌 주요 역할
- 소스 코드에서 **토큰(token)**을 추출하는 어휘 분석기를 자동으로 생성합니다.
- 입력: 정규 표현식(Regular Expression) + 액션 코드
- 출력: 토큰 스트림을 생성하는 C 코드 (lex.yy.c)
* 파서 생성기
파서 생성기(parser generator)란 언어의 문법 표현(grammar description)으로부터 파서(또는 구문 분석기)를 자동으로 생성하는 도구이다.
- 파서를 제어하는 테이블을 생성한다.
- 파서는 테이블을 이용하여 다음 단계에서 필요한 의미정보(semantic information)을 만든다
- 일반적인 문법 표현으로는 context-free 문법을 사용한다.
2️⃣ YACC (Yet Another Compiler Compiler)
YACC는 구문 분석기(Parser)를 자동 생성하는 도구입니다.
📌 주요 특징
- UNIX 환경에서 실행
- y.tab.c가 YACC로부터 생성된 프로그램으로 문법 규칙에 의해 기술된 언어의 문장에 대해 구문 검사(syntax
checking)을 하며 액션 코드를 필요할 때마다 수행한다. - 문법 규칙(Grammar Rules) + 액션 코드를 입력으로 받아, C 언어 기반 파서를 출력
- 출력 파일: y.tab.c – 파싱 로직이 포함된 코드
🏗 고급 자동화 시스템
3️⃣ PQCC (Production Quality Compiler Compiler)
PQCC는 Carnegie Mellon University의 W.A. Wulf가 개발한 고품질 컴파일러 생성 시스템입니다.
📌 특징
- 입력: 언어 기술(Language Description) + 기계 기술(Machine Description)
- 중간 표현: TCOL(Tree-structured Intermediate Language)
- 코드 생성: Pattern Matching 기반
- 결과물: 고품질 컴파일러 + 코드 생성 테이블
- PQC(Production Quality Compiler)라고 부르는 기초 코드 생성기와 최적화기가 사용하는 테이블을 만듬
- PQC는 이 테이블을 이용하여 전단부에서 생성한 TCOL을 목적 코드로 변화나흔 작업을 함
4️⃣ ACK (Amsterdam Compiler Kit)
Andrew S. Tanenbaum가 주도한 프로젝트로, 이식성 높은 컴파일러 제작 도구입니다.
📌 특징
- 중간 언어: EM(Abstract Machine Code)
- EM-코드는 가상적인 스택 기계에 근거를 둔 일종의 어셈블러
- EM 코드에 대한 최적화 과정(peephole optimization & global optimization)을 거친후 후단부에 의해 목적 프로그램으로 변환
- 다양한 언어(C, Pascal, Ada 등)와 다양한 하드웨어(Intel, Motorola, SPARC 등)를 지원
- 개념적 기반: UNCOL(Universal Intermediate Code)
🔄 자동화 구조 요약
Lex | 어휘 분석기 | 정규 표현식 + 액션 코드 | lex.yy.c |
YACC | 구문 분석기 | 문법 규칙 + 액션 코드 | y.tab.c |
PQCC | 고품질 컴파일러 생성기 | 언어/기계 기술 | 중간 코드(TCOL), 목적 코드 |
ACK | 이식 가능한 컴파일러 키트 | 언어/기계 기술 | EM 코드 → 목적 코드 |
이러한 도구들은 정규 표현식, 문법 기술 등을 입력받아 자동으로 분석기나 코드 생성기를 만들어줍니다.
📝 마무리
컴파일러는 프로그래밍 언어와 기계 사이를 연결해 주는 핵심 도구입니다. 단순한 번역기를 넘어, 의미 분석, 최적화, 실행 환경과의 연계까지 수행하는 복잡한 시스템이죠. 컴파일러의 작동 원리를 이해하면, 프로그래밍 언어에 대한 이해도 함께 깊어질 것입니다.
'컴파일러' 카테고리의 다른 글
구문 분석 (1) | 2025.06.05 |
---|---|
Context-free 문법 (1) | 2025.06.04 |
어휘 분석 (2) | 2025.06.04 |
정규언어(후반부) (0) | 2025.06.03 |
컴파일러의 기초: 형식 언어와 문법 파헤치기 💻 (0) | 2025.04.15 |