본문 바로가기
IT 공부/컴퓨터 하드웨어 및 구조

C언어 컴파일 과정

by exdus3156 2023. 11. 15.

1. C언어 컴파일

C언어를 처음 배울 때 C언어의 컴파일을 상세하게 공부하지 않았다. 사실 알고 싶었으나 설명이 워낙 어려워 그냥 그려려니 하고 넘어갔던 것 같다. 그러나 공부를 하면 할수록 하드웨어에 가까운 C언어를 익힌다는 것이 얼마나 중요한지 체감하게 되었다. (소프트웨어의 원형이 결국 C언어 스타일의 프로그램으로 귀결되더라.)

특히 window10과 같은 GUI 환경에서 Visual Studio 툴로 컴파일을 하게 되는데, 이 컴파일은 컴파일하자마자 즉시 실행 파일을 만들어준다. 그래서 C언어 컴파일 과정을 눈으로 익히기가 쉽지 않다.

이후 리눅스 환경에서 디폴트로 설치되어 있는 gcc 컴파일러 프로그램을 통해 C언어 소스코드의 컴파일 과정을 천천히 할 수 있게 되면서 신기해했던 경험이 있다. 아주 간략하게 정리하려고 한다. 

(여담이지만 확실히 리눅스와 같은 환경에서 프로그램을 작성하고 돌리는 경험이 뭔가 운영체제나 소프트웨어의 본질에 대해 이해되는 느낌이라 좋은 것 같다.)

 

2. 전처리 단계

전처리란 말 그대로 처리하기 전에 먼저 해야할 것들을 미리 하는 과정을 말한다. C언어 코드는 문법적으로 전처리 문법들이 몇 가지 있다. #include를 통해 헤더파일(함수 원형이 선언된 파일)을 통째로 소스코드에 복사한다. 혹은 #define 처럼 매크로 함수나 변수를 바꿔주기도 한다.

사실상 전처리는 전통적인 의미의 컴파일이 아니라, 문서 편집기에 가깝다고 보면 된다.

아래는 아주 간단한 C언어 파일이다.

 

(오랜만에) 가상머신으로 리눅스를 돌렸다. vim 편집기를 이용해 Hello World를 출력하고, 콘솔에 입력되는 문자열을 다시 출력하는 프로그램을 만들었다.

이제 전처리를 해본다. 아래의 옵션으로 gcc 컴파일러가 전처리만 할 수 있도록 만든다.

gcc -E hello.c -o hello.i

아래와 같이 전처리가 된 것을 확인할 수 있다. #include 의 stdio.h 헤더파일을 그대로 들고와 복사한다. gcc 컴파일러는 표준 라이브러리의 위치를 참고할 수 있도록 디폴트로 특정 경로를 미리 설정해 해당 라이브러리의 파일을 가져올 수 있다.

 

3. 컴파일

소스파일을 본격적으로 컴파일한다. 처음부터 바로 기계어로 번역하지 않고 먼저 어셈블리어로 컴파일한다. 오류가 있다면 컴파일 오류를 뱉어낸다. 소스코드가 외부의 함수를 호출할 경우, 문법적으로 오류가 아니라면 특별히 오류를 뱉어내지 않는다. 왜냐하면 링킹 과정에서 연결될 것이기 때문이다.

gcc -S hello.i -o hello.s

위와 같이 어셈블리어로 번역된 것을 확인할 수 있다. 바로 기계어로 거치지 않고 어셈블리어로 중간 단계를 거치는 이유는 기계어보단 추상화 수준이 높아 이해하기가 편하며, 실제로 어셈블리어를 작성하여 프로그래머가 직접 CPU를 제어하는 경우도 있다. 실제 프로그램이 어떻게 동작하는지 직접 관찰할 수 있다는 메리트도 있다고 한다.

 

4. 어셈블

어셈블러는 여전히 문자열로 구성된 텍스트 파일에 불과하다. 이것을 실제로 CPU에서 동작할 수 있는 기계어로 바꿔야 한다. 이 과정이 어셈블이다.

gcc -o hello.o hello.s

hello.o 라는 오브젝트 코드가 완성되었으며, 읽기 편하라고 16진법을 사용했지만 실제 코드는 0과1로 이루어져 있는 기계어다.

 

5. 링킹

표준 라이브러리의 경우 -o 옵션을 통해 바로 링킹이 자동으로 수행된다. 위 명령어로 사실 링킹과 컴파일을 동시에 수행했는데 이는 표준 라이브러리만 사용했기 때문이다. 링킹은 gcc 컴파일러의 디폴트 경로를 이용해 표준 라이브러리의 오브젝트 코드들을 연결한다. 

원래의 코드가 printf()와 같은 함수를 호출하는데, 바로 이 부분을 위해 두 파일을 연결해 하나의 실행 파일로 엮는 것이다.

리눅스 배포판은 대부분 gcc 컴파일러와 c표준 라이브러리가 내장되어 있어 따로 다운받을 필요는 없다. (다운 받은 적이 한 번도 없다.)