28|컴파일러 세부 과정·어휘·구문·의미 분석·코드 생성

글 요약

사무자동화산업기사 – 컴파일러의 전처리·어휘·구문·의미 분석, 하향식·상향식 파싱, 중간 코드·최적화·코드 생성과 디버깅을 정리합니다.

고급 언어로 작성된 소스 코드는 CPU가 곧바로 실행할 수 있는 기계 명령이 아닙니다. 컴파일러는 문자로 된 소스 코드를 토큰과 문법 구조로 분석하고, 프로그램의 의미가 올바른지 검사한 뒤 중간 표현과 목적 코드를 생성합니다.

이 장에서는 컴파일러 세부 과정을 전처리·어휘 분석·구문 분석·의미 분석·중간 코드 생성·코드 최적화·코드 생성으로 구분합니다. 또한 하향식·상향식 파싱의 차이, 파스 트리와 추상 구문 트리, 심벌 테이블, 링크와 실행 코드 생성, 디버깅까지 시험에 필요한 기준으로 연결합니다.

이 장의 핵심 내용

  • 전처리는 헤더 포함·매크로 확장·조건부 컴파일 등을 본격적인 컴파일 전에 처리합니다.
  • 어휘 분석기는 문자의 연속을 식별자·키워드·리터럴·연산자 등의 토큰으로 변환합니다.
  • 구문 분석기는 토큰의 배열이 문법에 맞는지 검사하고 파스 트리 또는 추상 구문 트리를 구성합니다.
  • 하향식 파싱은 시작 기호에서 입력 문자열을 향해 전개하며 좌단 유도와 연결됩니다.
  • 상향식 파싱은 입력 문자열에서 시작 기호를 향해 축약하며 우단 유도의 역과 연결됩니다.
  • 의미 분석은 자료형·선언·사용 범위·함수 인수·식별자 사용이 의미상 타당한지 검사합니다.
  • 심벌 테이블은 식별자의 이름·종류·자료형·범위·저장 위치 등의 정보를 여러 단계에서 공유합니다.
  • 중간 코드는 특정 기계에 덜 의존하는 내부 표현으로 최적화와 코드 생성을 연결합니다.
  • 코드 최적화는 프로그램의 의미를 유지하면서 실행 시간·코드 크기·자원 사용을 개선합니다.
  • 코드 생성은 중간 표현을 타깃 기계의 어셈블리 또는 기계 코드로 변환합니다.
  • 목적 파일과 라이브러리를 실행 파일로 결합하는 링크는 일반적으로 컴파일러 핵심 분석 단계와 구분합니다.
  • 디버거는 실행 중인 프로그램의 상태를 관찰하여 런타임 오류와 논리 오류를 찾는 도구입니다.

컴파일러 세부 과정은 어떻게 구성되는가?

컴파일러는 일반적으로 프런트엔드에서 소스 언어를 분석하고, 중간 표현을 생성한 뒤, 최적화와 백엔드 코드 생성을 통해 타깃 기계용 코드를 만듭니다. 전처리와 링크는 전체 빌드 흐름에는 포함되지만 엄밀하게는 컴파일러 핵심 분석 단계와 별도의 도구가 담당할 수 있습니다.

구분단계입력핵심 처리대표 출력
사전 처리전처리(선행 처리)원시 코드·전처리 지시문헤더 포함·매크로 확장·조건부 선택확장된 원시 코드
프런트엔드어휘 분석문자열문자를 토큰으로 분류토큰의 연속
프런트엔드구문 분석토큰문법 구조 검사·트리 구성파스 트리·AST
프런트엔드의미 분석구문 트리·심벌 정보자료형·선언·범위·호출 규칙 검사의미가 부여된 트리
중간 단계중간 코드 생성의미 분석 결과기계 독립적 내부 표현으로 변환IR·중간 코드
중간 단계코드 최적화중간 코드의미를 보존하며 성능·크기 개선최적화된 중간 코드
백엔드코드 생성최적화된 IR명령 선택·레지스터 할당·명령 배치어셈블리·목적 코드
빌드 후속링크목적 파일·라이브러리심벌 해결·재배치·결합실행 파일·로드 모듈
컴파일러 핵심 6단계: ‘어휘 분석 → 구문 분석 → 의미 분석 → 중간 코드 생성 → 코드 최적화 → 코드 생성’입니다.

전체 세부 처리 흐름: ‘전처리(선행 처리) → 어휘 분석 → 구문 분석 → 의미 분석 → 중간 코드 생성 → 코드 최적화 → 코드 생성’을 기본 순서로 기억합니다.

전처리기는 컴파일 전에 무엇을 처리하는가?

전처리기(Preprocessor, 선행 처리)는 컴파일러의 어휘·구문 분석이 시작되기 전에 소스 텍스트를 확장하거나 선택합니다. C 계열 언어에서는 파일 포함, 매크로 치환, 조건부 컴파일이 대표적입니다.

전처리기는 원시 프로그램을 기계어 목적 프로그램으로 번역하지 않습니다. 파일 포함, 매크로 확장, 조건부 컴파일 등의 사전 처리를 수행하여 컴파일러가 분석할 확장된 원시 코드를 생성합니다.

#include <stdio.h>
#define MAX_COUNT 100

#if MAX_COUNT >= 100
const char *mode = "large";
#else
const char *mode = "small";
#endif
전처리 기능설명
파일 포함다른 파일의 내용을 현재 위치에 포함#include
매크로 확장매크로 이름을 정의된 토큰열로 치환#define
조건부 컴파일조건에 따라 컴파일할 코드 영역을 선택#if·#else
주석 처리시험에서 주석 제거를 전처리 기능으로 분류실제 처리 단계는 언어·도구 구현에 따라 다를 수 있음

시험 기준: 전처리기의 대표 기능으로 주석 제거·상수 정의 치환·매크로 확장이 출제될 수 있습니다. 실제 컴파일러에서는 주석 처리와 상수 계산의 정확한 단계가 구현에 따라 달라질 수 있습니다.

어휘 분석은 소스 코드를 어떻게 토큰으로 나누는가?

어휘 분석(Lexical Analysis)은 소스 프로그램의 문자 흐름을 읽고 문법적으로 의미 있는 최소 단위인 토큰(Token)으로 분류하는 단계입니다. 어휘 분석기는 스캐너(Scanner) 또는 렉서(Lexer)라고도 하며, 인식한 토큰을 구문 분석기에 전달합니다.

int total = price * count + 100;
문자열 조각 Lexeme토큰 종류구문 분석기에 전달되는 의미
int키워드정수형 선언
total식별자변수 이름
=대입 연산자대입 연산
price식별자변수 이름
*산술 연산자곱셈
count식별자변수 이름
+산술 연산자덧셈
100정수 리터럴상수 값
;구분자문장 종료
토큰은 종류를 나타내고, 렉심은 소스 코드에 실제로 나타난 문자열을 의미합니다.

어휘 오류

언어가 허용하지 않는 문자나 끝나지 않은 문자열 리터럴처럼 토큰 자체를 구성할 수 없는 경우입니다.

어휘 분석의 출력

토큰 종류와 필요하면 식별자·리터럴 값 또는 심벌 테이블을 가리키는 부가 정보를 전달합니다.

입출력 관계: 소스 코드의 문자 흐름 → 어휘 분석기 → 토큰의 연속입니다.

구문 분석기는 토큰을 어떻게 문장 구조로 만드는가?

구문 분석(Syntax Analysis)은 토큰의 순서가 언어의 문맥 자유 문법에 맞는지 검사하고 계층적인 문장 구조(ex. 파스 트리)를 만드는 단계입니다. 이 작업을 파싱(Parsing)이라고 하며, 구문 분석기 또는 파서(Parser)가 수행합니다.

<대입문> ::= id "=" <표현식> ";"
<표현식> ::= <표현식> "+" <항> | <항>
<항> ::= id | number

이 문법에서 id = id + number;는 대입문 규칙과 표현식 규칙에 따라 유도할 수 있으므로 구문상 올바른 문장입니다. 반면 id = + number;는 덧셈 연산자 앞에 필요한 표현식이 없으므로 구문 오류입니다.

분석 결과특징사용 목적
파스 트리문법 규칙에 등장하는 단말·비단말 기호를 비교적 자세히 표현입력이 문법에서 어떻게 유도되는지 확인
추상 구문 트리 AST괄호·세미콜론 등 불필요한 문법 세부사항을 줄이고 핵심 연산 구조를 표현의미 분석·중간 코드 생성·최적화에 활용

구문 분석의 역할: 토큰이 존재하는지만 확인하는 것이 아니라 토큰이 문법적으로 허용된 순서와 계층으로 결합되는지 판단합니다.

하향식 파싱과 상향식 파싱은 무엇이 다른가?

비교 항목하향식 파싱 Top-Down상향식 파싱 Bottom-Up
출발점문법의 시작 기호·트리의 루트입력 토큰·트리의 잎
진행 방향루트에서 잎을 향해 생성 규칙을 전개잎에서 루트를 향해 핸들을 축약
유도 관계일반적으로 좌단 유도우단 유도의 역
핵심 동작예측하고 규칙을 선택하여 확장Shift로 입력을 쌓고 Reduce로 규칙의 오른쪽을 왼쪽으로 축약
대표 파서재귀 하강(Recursive Descent Parser), 예측 파서(Predictive Parser), LL 파서Shift-Reduce, LR, SLR, LALR 파서
문법 주의전통적 LL·예측 파서는 좌재귀 제거가 필요하며 좌인수분해가 필요할 수 있음좌재귀 문법도 처리할 수 있으며 더 넓은 문법을 다룸
시험에서는 하향식–좌단 유도–LL, 상향식–우단 유도의 역–LR의 연결을 중심으로 구분합니다.

하향식 파싱의 흐름

<대입문>
⇒ id = <표현식> ;
⇒ id = <표현식> + <항> ;
⇒ id = <항> + <항> ;
⇒ id = id + <항> ;
⇒ id = id + number ;

상향식 파싱의 흐름

id = id + number ;
          number → <항>
     id → <항> → <표현식>
<표현식> + <항> → <표현식>
id = <표현식> ; → <대입문>

표현 보완: 하향식 파싱을 ‘루트에서 단말 노드 방향’으로, 상향식 파싱을 ‘단말 노드에서 루트 방향’으로 기억하면 됩니다. 엄밀한 유도 기준은 하향식이 좌단 유도, 상향식이 우단 유도의 역입니다.

시험 함정: ‘단말 노드에서 뿌리 노드를 만들어 간다’는 설명은 하향식 파싱이 아니라 상향식 파싱에 해당합니다.

파스 트리와 추상 구문 트리는 어떻게 다른가?

트리는 하나의 루트 노드에서 하위 노드가 계층적으로 연결되는 비선형 자료 구조입니다. 컴파일러는 문법 전체의 유도 관계를 나타낼 때 파스 트리를 사용하고, 이후 분석과 변환에는 불필요한 노드를 줄인 추상 구문 트리를 주로 사용합니다.

구분: 파스 트리는 문법 규칙을 자세히 보존하고, 추상 구문 트리는 프로그램의 계산 의미를 중심으로 단순화합니다.

의미 분석은 어떤 오류를 검사하는가?

의미 분석(Semantic Analysis)은 파서가 생성한 구문 트리와 심벌 테이블을 이용하여 자료형, 선언, 범위, 함수 호출, 연산의 적합성을 검사하는 단계입니다. 의미 분석을 통과한 트리는 다음 단계인 중간 코드 생성 단계로 전달됩니다.

검사 항목올바른 예오류 예
선언 확인선언한 변수 사용선언하지 않은 변수 사용
자료형 검사정수 변수에 정수값 대입정수 변수에 호환되지 않는 객체 대입
연산자 적합성숫자끼리 산술 연산지원하지 않는 자료형 사이의 연산
함수 호출매개변수 개수·자료형 일치인수 개수 또는 자료형 불일치
사용 범위유효한 블록 안에서 식별자 사용범위를 벗어난 지역 변수 참조
반환 규칙함수 반환형에 맞는 값 반환반환형과 호환되지 않는 값 반환
int count = 10;
count = "ten";   // 구문은 만들 수 있지만 자료형 규칙에 어긋나는 의미 오류

중요한 보완: 매크로 확장은 일반적으로 의미 분석기의 기능이 아니라 전처리기의 기능입니다. 의미 분석은 자료형·선언·범위·심벌 정보와 같은 의미 규칙을 검사합니다.

심벌 테이블과 중간 코드는 왜 필요한가?

심벌 테이블(Symbol Table)은 소스 코드의 식별자 정보를 저장하는 자료 구조이며 어휘 분석부터 의미 분석·코드 생성까지 여러 단계가 함께 사용합니다. 의미 분석을 통과한 프로그램은 최적화와 타깃 코드 생성을 쉽게 하기 위해 중간 표현(Intermediate Representation, IR)으로 변환됩니다.

구분저장·표현 내용주요 사용 단계
심벌 테이블식별자 이름·종류·자료형·범위·저장 위치·함수 매개변수어휘 분석·구문 분석·의미 분석·코드 생성
중간 표현 IR연산과 제어 흐름을 기계에 덜 의존적인 형식으로 표현최적화·타깃 코드 생성
원시 식: total = price * count + 100

3주소 형식의 중간 코드 예:
t1 = price * count
t2 = t1 + 100
total = t2

단계 구분: 의미 분석은 프로그램의 의미적 타당성을 검사하고, 중간 코드 생성은 검사를 통과한 구조를 IR로 낮추는 별도 단계로 보는 것이 정확합니다.

코드 최적화는 프로그램을 어떻게 개선하는가?

코드 최적화(Code Optimization)는 프로그램의 관찰 가능한 의미를 바꾸지 않으면서 실행 시간, 코드 크기, 기억장치 사용량 또는 전력 사용을 개선하는 단계입니다. 모든 지표를 동시에 개선하는 것은 아니며 최적화 목표와 타깃 환경에 따라 결과가 달라집니다.

최적화 기법의미
상수 접기
Constant Folding
컴파일 시 계산할 수 있는 상수식을 미리 계산3 * 412
죽은 코드 제거
Dead Code Elimination
결과에 영향을 주지 않는 코드를 제거사용되지 않는 계산 삭제
공통 부분식 제거동일한 계산을 반복하지 않고 재사용a*b의 중복 계산 제거
강도 감소
Strength Reduction
비용이 큰 연산을 더 저렴한 연산으로 변경조건에 따라 곱셈을 덧셈·시프트로 변경
루프 최적화반복문의 불필요한 계산과 메모리 접근을 줄임루프 불변 코드 이동
인라인 확장함수 호출을 함수 본문으로 치환하여 호출 비용을 줄임작은 함수의 호출 제거

최적화 원칙: 코드의 의미를 유지해야 하며, 최적화된 코드가 항상 실행 시간과 메모리 사용량을 모두 줄이는 것은 아닙니다.

코드 생성은 목적 코드를 어떻게 만드는가?

코드 생성(Code Generation)은 중간 표현을 타깃 명령어 집합에 맞는 어셈블리 또는 기계 코드로 변환하는 단계입니다. 백엔드는 명령 선택, 레지스터 할당, 명령 스케줄링과 주소 지정 방식을 결정합니다.

코드 생성 작업설명
명령 선택IR의 연산을 타깃 CPU 명령으로 대응
레지스터 할당값을 저장할 레지스터를 배정하고 부족하면 메모리로 이동
명령 스케줄링CPU 파이프라인과 의존성을 고려해 명령 순서를 조정
주소 지정변수·배열·메모리에 접근하는 주소 계산 방식을 선택
목적 코드 출력어셈블리 파일 또는 재배치 가능한 목적 파일을 생성

코드 생성과 링크: 코드 생성은 개별 번역 단위의 목적 코드를 만들고, 링커는 여러 목적 파일과 라이브러리를 결합하여 실행 파일 또는 로드 모듈을 만듭니다.

디버깅은 어떤 오류를 찾는 과정인가?

디버깅(Debugging)은 프로그램의 결함을 재현하고 원인을 추적하여 수정하는 과정입니다. 디버거(Debugger)는 중단점, 단계 실행, 변수·메모리·호출 스택 관찰 등의 기능으로 실행 중인 프로그램 내부 상태를 확인하게 합니다.

오류 종류발견 시점·도구
어휘·구문 오류컴파일러의 어휘·구문 분석잘못된 문자, 괄호·세미콜론 누락
의미 오류컴파일러의 의미 분석자료형 불일치, 선언되지 않은 이름
링크 오류링커정의되지 않은 외부 심벌
런타임 오류실행 환경·디버거0으로 나누기, 잘못된 메모리 접근
논리 오류시험·로그·디버거실행은 되지만 계산 결과가 잘못됨

디버거의 대표 기능

  • 중단점에서 실행 중지
  • 한 줄 또는 한 명령씩 단계 실행
  • 변수·레지스터·메모리 값 확인
  • 함수 호출 스택 추적
  • 조건을 만족할 때 실행 중지

디버깅의 기본 순서

  • 오류를 재현
  • 발생 조건과 범위를 축소
  • 가설을 세우고 상태를 관찰
  • 원인을 수정
  • 회귀 시험으로 다른 기능을 확인

시험에서 자주 혼동되는 개념

어휘 분석과 구문 분석

어휘 분석은 문자를 토큰으로 만들고, 구문 분석은 토큰을 문법 구조로 결합합니다.

구문 오류와 의미 오류

구문 오류는 문장 구조가 문법에 어긋난 경우이고, 의미 오류는 문법은 맞지만 자료형·선언·범위 규칙에 어긋난 경우입니다.

하향식과 상향식

하향식은 루트에서 잎으로 좌단 유도하고, 상향식은 잎에서 루트로 우단 유도의 역을 수행합니다.

LL과 LR

LL은 대표적인 하향식 파서이고, LR은 Shift-Reduce 방식의 대표적인 상향식 파서입니다.

의미 분석과 중간 코드 생성

의미 분석은 프로그램의 의미 규칙을 검사하고, 중간 코드 생성은 검사된 트리를 IR로 변환합니다.

코드 생성과 링크

코드 생성은 목적 코드를 만들고, 링크는 목적 파일과 라이브러리를 실행 파일로 결합합니다.

전처리기와 의미 분석기

매크로 확장과 조건부 컴파일은 전처리기의 기능이며 자료형·선언·범위 검사는 의미 분석기의 기능입니다.

컴파일 오류와 디버깅

컴파일러가 찾는 오류 외에도 런타임·논리 오류는 실행과 시험 과정에서 디버거를 이용해 추적합니다.

암기 흐름: ‘문자 → 토큰 → 구문 트리 → 의미 검사 → 중간 코드 → 최적화 → 목적 코드’이며, 목적 코드 이후의 실행 파일 생성은 링커가 담당합니다.

이번 장 핵심 요약

  1. 컴파일러 세부 과정은 어휘 분석·구문 분석·의미 분석·중간 코드 생성·코드 최적화·코드 생성으로 이어집니다.
  2. 전처리는 매크로 확장·파일 포함·조건부 컴파일 등을 본격적인 분석 전에 수행합니다.
  3. 어휘 분석기는 문자 흐름을 토큰으로 분류하여 파서에 전달합니다.
  4. 렉심은 소스에 실제로 나타난 문자열이고 토큰은 그 문자열의 문법적 종류입니다.
  5. 구문 분석기는 토큰이 문맥 자유 문법에 맞는지 검사하고 트리를 만듭니다.
  6. 하향식 파싱은 시작 기호에서 입력을 향해 좌단 유도합니다.
  7. 전통적인 예측 파서는 좌재귀 제거가 필요하며 좌인수분해가 필요할 수 있습니다.
  8. 상향식 파싱은 입력에서 시작 기호를 향해 Shift와 Reduce를 수행합니다.
  9. 상향식 파싱은 우단 유도의 역과 연결됩니다.
  10. 재귀 하강·예측·LL 파서는 하향식이고 Shift-Reduce·LR 파서는 상향식입니다.
  11. 파스 트리는 문법 구조를 자세히 보존하고 AST는 핵심 계산 구조를 단순화합니다.
  12. 의미 분석은 자료형·선언·범위·함수 호출·연산 적합성을 검사합니다.
  13. 매크로 처리는 의미 분석이 아니라 전처리 기능으로 구분합니다.
  14. 심벌 테이블은 식별자의 이름·종류·자료형·범위 등의 정보를 저장합니다.
  15. 중간 표현은 기계 독립적인 형태로 최적화와 코드 생성을 연결합니다.
  16. 코드 최적화는 프로그램의 의미를 보존하면서 성능이나 코드 크기를 개선합니다.
  17. 코드 생성은 IR을 타깃의 어셈블리·기계 코드로 변환합니다.
  18. 링커는 여러 목적 파일과 라이브러리를 결합하여 실행 파일을 만듭니다.
  19. 디버거는 중단점·단계 실행·상태 관찰을 통해 런타임 오류와 논리 오류를 추적합니다.

복습 문제

사용자가 작성한 소스 코드를 실행하면서 오류를 찾기 위한 프로그램은?

디버거입니다.

원시 프로그램을 문자 단위로 스캔하여 의미 있는 토큰으로 분류하는 단계는?

어휘 분석입니다.

컴파일러의 핵심 단계 순서는?

어휘 분석 → 구문 분석 → 의미 분석 → 중간 코드 생성 → 코드 최적화 → 코드 생성입니다.

토큰의 배열이 문법에 맞는지 검사하는 단계는?

구문 분석입니다.

구문 분석의 대표 출력은?

파스 트리 또는 추상 구문 트리입니다.

루트에서 단말 노드 방향으로 문법 규칙을 전개하는 방식은?

하향식 파싱입니다.

입력 토큰에서 시작하여 루트를 만들어 가는 방식은?

상향식 파싱입니다.

하향식 파싱과 연결되는 유도는?

좌단 유도입니다.

상향식 파싱과 연결되는 유도는?

우단 유도의 역입니다.

재귀 하강 파서와 LL 파서는 어느 방식인가?

하향식 파싱 방식입니다.

Shift-Reduce 파서와 LR 파서는 어느 방식인가?

상향식 파싱 방식입니다.

전통적인 예측 파서에서 제거해야 하는 대표 문법 요소는?

좌재귀입니다.

자료형 불일치와 선언되지 않은 식별자를 검사하는 단계는?

의미 분석입니다.

식별자의 자료형·범위·저장 위치 정보를 관리하는 자료 구조는?

심벌 테이블입니다.

기계에 덜 의존적인 내부 코드 표현은?

중간 코드 또는 중간 표현(IR)입니다.

프로그램의 의미를 유지하며 실행 시간이나 코드 크기를 개선하는 단계는?

코드 최적화입니다.

중간 표현을 타깃 기계 명령으로 변환하는 단계는?

코드 생성입니다.

목적 파일과 라이브러리를 실행 파일로 결합하는 도구는?

링커입니다.

매크로 확장·파일 포함·조건부 컴파일을 담당하는 것은?

전처리기입니다.

실행은 되지만 결과가 잘못되는 오류는?

논리 오류입니다.

참고자료

이 글은 사무자동화산업기사 제2과목 프로그래밍 일반의 컴파일러 세부 과정, 어휘·구문·의미 분석, 파싱 방식, 중간 코드·최적화·코드 생성과 디버깅을 이해하고 복습할 수 있도록 재구성한 정리입니다. 교재의 단계와 용어는 시험 대비를 위해 단순화된 경우가 있으며, 실제 컴파일러는 전처리·프런트엔드·중간 표현·최적화·백엔드·링크의 경계를 다르게 구성할 수 있으므로 최신 출제기준과 공식 문서를 함께 확인해야 합니다.


같은 주제의 다른 글

이 글과 같은 카테고리에 있는 이전 글과 다음 글을 확인해보세요.