우리가 작성한 C 코드를 실행하기 위해서는 컴파일링을 해줘야 한다.
C문법로 작성된 텍스트 형식의 파일은 컴파일링시 구체적으로 어떤 단계를 거쳐서 컴퓨터가 해석 가능한 파일로 변환될까?
#include <studio.h>
int main(void)
{
printf("hello, world\n");
}
위 코드에서 main
이라는 함수는 프로그램의 시작점으로써 실행 버튼을 클릭하는 것과 같고, printf
는 출력을 담당하는 함수이다.
printf
함수를 사용하기 위해서는 studio.h
라이브러리가 필요하다.
studio.h
는 c언어로 작성된 헤더파일로 printf
함수의 프로토타입을 가지고 있어 Clang
컴파일러가 프로그램을 컴파일할 때 printf
가 무엇인지 알려주는 역할을 한다.
코드를 clang hello.c
로 컴파일하고 ./a.out
명령으로 프로그램을 실행할 때 이 과정은 컴퓨터가 이해하는 0과 1로 가득찬 파일 a.out
을 생성하여 실행 가능하게 한다.
만약 a.out
과 다른 이름(hello)으로 컴파일을 하고 싶다면 아래와 같이 명령행 인자를 추가해야주면 된다.
clang -o hello hello.c
이 때 우리는 cs50 라이브러리를 사용했었다.
clang -o hello hello.c -lcs50
이는 clang
에게 CS50 라이브러리에 있는 모든 0과 1들을 여기에 연결하라는 의미이다.
이전에 배웠던 make
프로그램을 이용하면 이 모든 컴파일 과정을 자동으로 더 간단하게 처리할 수 있다.
make
나 clang
을 사용해서 프로그램을 실행할 때에는 아래의 네 단계를 거친다.
>
- 전처리
- 컴파일링
- 어셈블링
- 링킹
전처리(Precompile)
컴파일의 첫 번째 단계인 전처리는 전처리기에 의해 수행된다. #
으로 시작되는 C 소스 코드는 전처리기에게 실질적인 컴파일이 이루어지기 전에 무언가를 실행하라고 알려준다.
예를 들어, #include
는 전처리기에게 다른 파일의 내용을 포함시키라고 알려주는 것이다. 프로그램의 소스 코드에 #include
와 같은 줄을 포함하면, 전처리기는 새로운 파일을 생성하는데 이 파일은 여전히 C 소스 코드 형태이며 stdio.h
파일의 내용이 #include
부분에 포함된다.
컴파일(Compile)
그 다음 단계는 컴파일로, 컴파일러라고 불리는 프로그램이 C 코드를 어셈블리어라는 저수준 프로그래밍 언어로 컴파일한다.
어셈블리는 C보다 연산의 종류가 훨씬 적지만, 여러 연산들이 함께 사용되면 C에서 할 수 있는 모든 것들을 수행할 수 있다. C 코드를 어셈블리 코드로 변환시켜줌으로써 컴파일러는 컴퓨터가 이해할 수 있는 언어와 최대한 가까운 프로그램으로 만들어 준다. 컴파일이라는 용어는 소스 코드에서 오브젝트 코드로 변환하는 전체 과정을 통틀어 일컫기도 하지만, 구체적으로 전처리한 소스 코드를 어셈블리 코드로 변환시키는 단계를 말하기도 한다.
어셈블(Assemble)
그 다음 단계는 어셈블로, 어셈블리 코드를 오브젝트 코드로 변환시키는 것이다. 컴퓨터의 중앙처리장치가 프로그램을 어떻게 수행해야 하는지 알 수 있는 명령어 형태인 연속된 0과 1들로 바꿔주는 작업이다.
이 변환작업은 어셈블러라는 프로그램이 수행하는데, 소스 코드에서 오브젝트 코드로 컴파일 되어야 할 파일이 딱 한 개라면, 컴파일 작업은 여기서 끝이 난다. 그러나 그렇지 않은 경우에는 링크라 불리는 단계가 추가된다.
링크(Link)
만약 프로그램이 (math.h
나 cs50.h
와 같은 라이브러리를 포함해) 여러 개의 파일로 이루어져 있어 하나의 오브젝트 파일로 합쳐져야 한다면 링크라는 컴파일의 마지막 단계가 필요하다.
링커는 여러 개의 다른 오브젝트 코드 파일을 실행 가능한 하나의 오브젝트 코드 파일로 합쳐준다.
예를 들어, 컴파일을 하는 동안에 CS50 라이브러리를 링크하면 오브젝트 코드는 GetInt()
나 GetString()
같은 함수를 어떻게 실행할 지 알 수 있게 된다.
이 네 단계를 거치면 최종적으로 실행 가능한 파일이 완성된다.
생각해보기
만약 컴파일링 과정을 거치지 않기 위해 바로 머신코드로 우리가 원하는 프로그램을 작성하려고 한다면 어떤 문제가 있을까?
정답
직접 머신코드로 프로그램을 작성하는 것은 복잡하고 시간이 많이 소요된다. 또한, 유지보수와 디버깅이 매우 어려워진다.