1.2 프로그램은 다른 프로그램에 의해 다른 형태로 번역된다

지난 1절에서는 "모든 정보는 결국 비트일 뿐"이라는 개념을 정리하였다. 이번 노트에서는 hello.c 라는 텍스트가 실제로 실행되는 프로그램 hello가 되기까지 어떤 과정을 거치는지 살펴본다. 이번에도 원문에서 꼭 기억해야 할 문장만 짧게 인용하고, 나머지는 번역하여 기록하였다.
왜 이 개념이 중요한가
평소 gcc -o app app.c 한 줄이면 끝나던 컴파일이, 사실은 서로 다른 역할을 하는 네 개의 프로그램이
순서대로 작업을 넘겨받는 과정이라는 걸 알아두면 실무에서 큰 도움이 된다. PostgreSQL 확장(extension)
빌드가 실패했을 때, Tomcat이 물고 있는 네이티브 라이브러리(.so)를 찾지 못할 때, systemd 서비스가
"symbol lookup error"를 뱉을 때, 이런 문제들이 정확히 "컴파일 단계"의 문제인지 "링크 단계"의
문제인지 구분할 수 있으면 원인 파악이 훨씬 빨라진다.
네 단계 번역 파이프라인
다음 명령을 실행하면, 실제로는 아래 그림처럼 네 단계를 거쳐 hello.c 가 실행 파일 hello 로 변역된다.
linux> gcc -o hello hello.c

이 네 프로그램(전처리기, 컴파일러, 어셈블러, 링커)을 통틀어 컴파일 시스템이라고 부른다. 각 단계를 하나씩 살펴보자.
1) 전처리 (Preprocessing) — cpp
가장 먼저 실행되는 전처리기(cpp)는 #으로 시작하는 지시문을 처리한다. 예를 들어 hello.c의 #include <stdio.h>는 "stdio.h 파일의 내용을 여기에 그대로 붙여넣어라"는 지시문이다. 이 단계의 결과물은 여전히 사람이 읽을 수 있는 텍스트 파일이며, 보통 .i 확장자를 갖는다.
"The preprocessor (cpp) modifies the original C program according to directives that begin with the '#' character."
번역: "전처리기(cpp)는 '#' 문자로 시작하는 지시문에 따라 원래의 C 프로그램을 수정한다." — Bryant & O'Hallaron,
Computer Systems: A Programmer's Perspective, 1.2절
2) 컴파일 (Compilation) — cc1
컴파일러(cc1)는 hello.i를 어셈블리어로 된 텍스트 파일 hello.s로 번역한다. 어셈블리어는 사람이 읽을 수 있는 형태로 저수준 명령을 표현한 것으로, C든 Fortran 이든 서로 다른 고수준 언어의 컴파일러들이 공통으로 출력하는 "공용어" 역할을 한다.
"Assembly language is useful because it provides a common output language for different compilers for different high level languages."
번역: "어셈블리어는 서로 다른 고수준 언어를 위한 여러 컴파일러가 공통으로 사용할 수 있는 출력 언어를 제공한다는 점에서
유용하다." — Bryant & O'Hallaron, Computer Systems: A Programmer's Perspective, 1.2절
3) 어셈블리 (Assembly) — as
어셈블러(as)는 사람이 읽을 수 있는 hello.s를 실제 기계어 명령으로 바꾸고, 이를 재배치 가능 목적 파일(relocatable object file)이라는 바이너리 형태로 hello.o에 저장한다. "재배치 가능"이라는 말은, 이 시점에는 아직 메모리 상의 최종 주소가 정해지지 않아서 나중에(링크할 때) 다른 코드와 합쳐지며 주소가 재조정될 수 있다는 뜻이다. hello.o를 텍스트 에디터로 열면 사람이 읽을 수 없는 이진 데이터로 보인다.
4) 링크 (Linking) — ld
hello.c는 printf 함수를 호출하지만, printf의 실제 코드는 우리가 작성하지 않았습니다. 그 코드는 이미 컴파일되어 있는 별도의 목적 파일(C 표준 라이브러리의 일부인 printf.o)에 들어 있다. 링커(ld)는 우리의 hello.o와 이런 라이브러리 목적 파일들을 하나로 병합해서, 최종적으로 실행 가능한 hello 파일을 만들어낸다.
"The printf function resides in a separate precompiled object file called printf.o, which must
somehow be merged with our hello.o program. The linker (ld) handles this merging."
번역: "printf 함수는 printf.o라는 별도의 미리 컴파일된 목적 파일 안에 들어 있으며, 이는 우리의 hello.o 프로그램과
어떻게든 병합되어야 한다. 링커(ld)가 이 병합 작업을 처리한다."
실습해보기
이 네 단계는 이론으로만 존재하는 게 아니라 여러분의 터미널에서 직접 확인할 수 있다.
linux> gcc -save-temps -o hello hello.c
이 옵션을 붙이면 gcc가 각 단계의 중간 산출물(hello.i, hello.s, hello.o)을 지우지 않고 현재 디렉터리에 그대로 남겨둔다. 그 외에도 다음 명령들이 각 단계의 결과물을 들여다보는 데 유용하다
.
● gcc -E : 전처리 결과(.i)만 출력
● gcc -S : 어셈블리 결과(.s)만 생성
● objdump -d hello.o : 목적 파일의 기계어를 다시 어셈블리로 보여줌
● ldd hello : 실행 파일이 실행 시점에 필요로 하는 동적 라이브러리(.so) 목록 확인 — Tomcat/PostgreSQL의 "라이브러리를 못 찾는다" 문제를 진단할 때 자주 쓰는 명령
'CS 공부 > CSAPP' 카테고리의 다른 글
| [CSAPP] 1장 1절 컴퓨터 시스템 둘러보기 (0) | 2026.10.24 |
|---|