BioPlayground

🧬
목록으로

컴파일 과정 — 소스코드에서 실행파일까지

컴파일러와 인터프리터의 차이, 소스코드가 기계어가 되는 과정, 빌드 과정을 단계별로 이해합니다.

입문
|
10
|
검증 완료 (2026-07)
컴파일인터프리터기계어소스코드빌드 과정
진행률0/11 (0%)

컴파일 과정 — 소스코드에서 실행파일까지

이 토픽을 마치면

컴파일러와 인터프리터의 차이를 설명할 수 있고, 소스코드가 기계어로 변환되는 단계를 이해하며, Python/JavaScript/C가 각각 어떤 방식으로 실행되는지 알게 됩니다.


컴퓨터는 소스코드를 읽을 수 없다

우리가 작성하는 코드는 사람을 위한 텍스트입니다.

python
print("Hello, World!")

CPU가 이해하는 것은 기계어 — 0과 1로 된 명령어입니다.

text
10110000 01001000  (mov al, 'H')
11001101 00100001  (int 21h)

소스코드 → 기계어 변환이 필요합니다. 이 변환을 하는 프로그램이 컴파일러인터프리터입니다.


컴파일러 vs 인터프리터

컴파일러인터프리터
변환 시점실행 전에 전체를 한 번에 변환실행하면서 한 줄씩 변환
결과물실행파일 (바이너리)없음 (즉시 실행)
에러 발견컴파일 시점에 전체 에러 표시해당 줄 실행 시점에 에러
실행 속도빠름 (이미 변환 완료)상대적으로 느림
대표 언어C, C++, Rust, GoPython, JavaScript, Ruby

비유하면: 컴파일러는 책 번역 (전체를 먼저 번역한 뒤 출판), 인터프리터는 동시통역 (말하는 즉시 번역)입니다.


컴파일 단계 — C 언어의 경우

C 코드가 실행파일이 되는 과정:

text
소스코드 (.c)
    ↓ 전처리 (Preprocessing)
전처리된 코드
    ↓ 컴파일 (Compilation)
어셈블리 코드 (.s)
    ↓ 어셈블 (Assembly)
목적 파일 (.o)
    ↓ 링크 (Linking)
실행파일 (a.out / .exe)

1. 전처리 (Preprocessing)

c
#include <stdio.h>
#define MAX 100

int main() {
    printf("Max is %d\n", MAX);
}

#include를 실제 헤더 파일 내용으로 교체하고, #define을 값으로 치환합니다. 이 단계는 텍스트 치환입니다.

2. 컴파일 (Compilation)

전처리된 코드를 분석해서 어셈블리 코드로 변환합니다. 문법 오류가 여기서 발견됩니다.

asm
mov    edi, OFFSET FLAT:.LC0
mov    esi, 100
call   printf

3. 어셈블 (Assembly)

어셈블리 코드를 기계어(이진 명령어)로 변환합니다. 결과물은 목적 파일(.o)입니다.

4. 링크 (Linking)

여러 목적 파일과 라이브러리를 하나의 실행파일로 합칩니다. printf의 실제 구현은 C 표준 라이브러리에 있으므로, 링커가 이것을 연결합니다.

bash
# GCC가 이 모든 단계를 한 번에 수행
gcc hello.c -o hello
./hello

Python은 어떻게 실행되는가

Python은 컴파일 + 인터프리터의 하이브리드입니다.

text
소스코드 (.py)
    ↓ Python 컴파일러
바이트코드 (.pyc)
    ↓ Python 가상머신 (PVM)
실행 결과
python
# 바이트코드 확인
import dis
def add(a, b):
return a + b
dis.dis(add)
# LOAD_FAST 0 (a)
# LOAD_FAST 1 (b)
# BINARY_ADD
# RETURN_VALUE

바이트코드는 기계어가 아닙니다. Python 가상머신(PVM)이라는 인터프리터가 바이트코드를 한 줄씩 실행합니다. C처럼 CPU가 직접 실행하는 것보다 느리지만, OS에 관계없이 실행할 수 있습니다.

__pycache__/ 폴더에 .pyc 파일이 생기는 것을 본 적이 있을 것입니다. 이것이 바이트코드 캐시입니다. 소스가 변경되지 않으면 재컴파일 없이 캐시를 사용합니다.


JavaScript는 어떻게 실행되는가

JavaScript 엔진(V8, SpiderMonkey)은 JIT(Just-In-Time) 컴파일을 사용합니다.

text
소스코드 (.js)
    ↓ 파싱
AST (추상 구문 트리)
    ↓ 인터프리터
바이트코드 실행 (느림)
    ↓ JIT 컴파일러 (자주 실행되는 코드 감지)
기계어로 변환 (빠름)

처음에는 인터프리터로 빠르게 실행을 시작하고, "이 함수가 1,000번 호출되었네?"라고 감지하면 해당 부분만 기계어로 컴파일합니다. 실행 중에 컴파일하므로 "Just-In-Time"입니다.


Java — 컴파일과 인터프리트의 정석 하이브리드

Java는 Python과 비슷하지만 좀 더 복잡합니다.

text
소스코드 (.java)
    ↓ javac (컴파일)
바이트코드 (.class)
    ↓ JVM (Java Virtual Machine)
    ├── 인터프리터 (처음)
    └── JIT 컴파일러 (자주 실행되는 코드)
    ↓
기계어 실행
java
// Hello.java
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
bash
javac Hello.java # 바이트코드 (.class) 생성
java Hello # JVM이 바이트코드 실행

"Write once, run anywhere" — Java 바이트코드는 JVM이 있는 어디서든 실행됩니다. Windows에서 컴파일한 .class 파일이 Linux JVM에서도 동작합니다.


정적 타입 vs 동적 타입

컴파일과 밀접한 개념이 타입 시스템입니다.

c
// C: 정적 타입 — 컴파일 시 타입 확인
int x = 42;
x = "hello";  // 컴파일 에러!
python
# Python: 동적 타입 — 실행 시 타입 확인
x = 42
x = "hello" # OK — 실행 중에 타입이 바뀜
정적 타입동적 타입
타입 확인 시점컴파일 시실행 시
에러 발견실행 전실행 중
타입 명시필수 (int x)불필요 (x = 42)
실행 속도빠름상대적으로 느림
대표 언어C, Java, TypeScriptPython, JavaScript, Ruby

실전에서의 의미

text
왜 Python이 C보다 느린가?
→ C: CPU가 기계어를 직접 실행
→ Python: 가상머신이 바이트코드를 한 줄씩 해석하며 실행

왜 TypeScript를 쓰는가?
→ JavaScript에 정적 타입을 추가 → 실행 전에 에러를 잡음
→ TypeScript → (tsc 컴파일) → JavaScript → (V8 실행)

왜 Docker 이미지가 OS별로 다른가?
→ 컴파일된 바이너리는 CPU 아키텍처(x86, ARM)에 종속
→ macOS용 바이너리는 Linux에서 실행 불가

핵심 정리

개념정리
컴파일러전체 소스를 미리 기계어로 변환. 빠른 실행
인터프리터한 줄씩 해석하며 실행. 빠른 개발
바이트코드중간 형태. 가상머신이 실행 (Python, Java)
JIT실행 중에 자주 쓰는 부분만 기계어로 컴파일 (JavaScript)
정적 타입컴파일 시 타입 확인 (C, TypeScript)
동적 타입실행 시 타입 확인 (Python, JavaScript)

빌드 도구와 트랜스파일러

현대 개발에서는 "순수한" 컴파일/인터프리트 외에도 다양한 변환 도구가 있습니다.

text
TypeScript → (tsc) → JavaScript → (V8) → 실행
JSX/React  → (Babel) → JavaScript → (V8) → 실행
Sass/SCSS  → (sass) → CSS → 브라우저 렌더링

트랜스파일러는 같은 수준의 언어로 변환합니다. 컴파일러가 고수준→저수준 변환이라면, 트랜스파일러는 고수준→고수준 변환입니다. TypeScript→JavaScript, ES6→ES5가 대표적입니다.


"Python은 인터프리터 언어"는 반만 맞습니다 — 바이트코드로 컴파일한 뒤 인터프리트합니다. "JavaScript는 인터프리터 언어"도 반만 맞습니다 — V8은 JIT 컴파일을 합니다. 현대 언어들은 컴파일과 인터프리트를 혼합해서 개발 편의성과 실행 성능을 모두 추구합니다.