2월 20, 2024

C 컴파일 과정 compilation process

 C 언어가 컴파일 되는 과정, 즉 우리가 C로 작성한 코드가 컴파일되서 실행파일이 되기까지의 과정을 살펴보자.

한 마디로 요약하면 아래의 그림과 같다.

hello.c라는 코드를 작성하고 그것이 실행파일로 만들어지기까지의 과정이다. 위 그림을 따라가면서 한 단계씩 자세히 알아보자.

 

1. c 코드 작성 => hello.c

#include <stdio.h>

int main(){
 printf("hello, world\n");
 return 0;
}

 

예를 들어 프로그래머가 위와 같은 코드를 작성하였다고 해보자. 

"hello, world"라고 출력되는 아주 간단한 코드이다.



2. Preprocessing => hello.i

위에서 작성한 hello.c 코드를 C 컴파일러가 쉽게 인식할 수 있도록 modify (수정) 하는 과정이라고 할 수 있다.

예를 들어, #include 부분과 같이 #으로 시작하는 부분 등을 C 컴파일러가 쉽게 인식할 수 있도록 수정하는 역할을 한다. #include 부분은 header와 관련있기 때문에 header file의 내용을 읽은 뒤에, 필요한 부분에 대해 복사를 해준다. C의 문법에 따라 #define의 경우 해당 내용으로 치환을 해주기도 한다.

또한 주석은 컴파일 시에 큰 의미가 없기 때문에 이 단계에서 제거를 한다. 이러한 과정을 Preprocessing이라고 부르며 이 과정이 완료되면 .i로 끝나는 hello.i의 코드가 생성이 된다.



3. Compile => hello.s

2번의 Preprocessing까지 완료되면 compiler는 hello.i text file을 hello.s의 text file로 번역해준다. 여기서 hello.s.은 low level 언어인 assembly-language program이라고 볼 수 있다. assembly language란 기계가 알아들을 수 있는 instruction으로 이루어진 언어라고 생각하면 편하다. 

위에서 우리가 작성한 hello.c가 이 단계에 오면 아래와 같이 바뀐다.

main:
    subq $8, %rsp
    movl $.LCO, %edi
    call puts
    movl $0, %eax
    addq $8, %rsp
    ret

(출처: Computer Systems: A Programmer's Perspective by Randal E.Bryant & David R.O'Hallaron)



4. Assembler => hello.o

위에서 생성한 hello.s를 바탕으로 assembler는 목적파일 (object file)을 생성한다. 여기서 object file이란 사람이 알아볼 수 없는 형태의 기계어 코드를 의미한다. (machine-language instructions)

이러한 목적파일 hello.o는 binary file이기 때문에 만약 hello.o를 메모장과 같은 text editor로 열어보면 외계어처럼 나오게 된다.



5. Linker => hello

이제 마지막 단계인 실행파일을 생성하는 단계이다. 위 단계에서 만든 hello.o파일은 실행할 수 없는 기계어 코드이기 때문에 이를 실행파일로 만들어주기 위해 linking의 단계를 거치는 것이다. 다시 맨 처음 hello.c 코드를 살펴보면 printf 부분이 있는데 printf 함수는 미리 컴파일되어 있는 object file인 printf.o에 존재한다. 따라서 실행파일을 만들기 위해서는 hello.o와 함께 printf.o의 object file도 합쳐져야 한다. 이렇게 결합이 되면 실행가능한 실행파일이 생성이 되는 것이다. C는 분할 컴파일이 가능하기 때문에 표준 라이브러리들이 printf.o와 같이 여러 개의 object 파일들을 모아서 하나의 파일로 만들어 둔 다는 것이 특징적이다.