[KNK] chapter 15 : 대규모 프로그램 작성법
1. 소스파일과 헤더파일
소스파일이란 C 프로그램의 실행가능한 코드가 포함된 파일로, 통상적으로 .c 확장자를 갖는다. 프로그램은 여러 개의 소스 파일로 나뉠 수 도 있는데, 이러한 경우 서로 관련성이 높은 함수와 변수를 한 파일에 넣어주게 된다. 이렇게 하나의 프로그램을 여러 소스 파일로 나눠줄 경우, 1) 유관한 함수들과 변수들을 한 파일로 모아주면서 프로그램의 구조를 명확하게 해주고, 2) 각 소스파일을 따로 컴파일 시킬 수 있어 잦은 수정시 시간을 절약할 수 있으며 3) 함수들을 다른 프로그램에서 재사용할 수 있게 된다는 장점이 있다. 참고로 소스파일이 여러개인 경우에도 반드시 하나의 main함수를 가져야 하는데, 이 main함수는 해당 프로그램의 시작점을 의미하게 된다.
여러개의 소스파일이 하나의 프로그램을 구성한다면, 다른 소스파일에 정의된 함수나 변수를 어떻게 사용할 수 있는지의 문제가 남는다. 이를 해결해주는 것이 chapter 14에서 공부했던 #include 지시자이고, 이 지시자에 의해 추가되는 파일이 헤더파일이다. 헤더파일은 통상적으로 .h 확장자를 갖고, 다음과 같은 것들을 포함하여 이들이 공유될 수 있도록 한다.
- 매크로 정의와 형정의
- 함수원형
- 변수 선언
매크로 정의와 형정의의 공유는 그렇다 치고, 함수원형의 공유는 왜 하는 걸까? 이전에 공부한 바와 같이 한 소스파일이 다른 소스파일에 정의된 함수를 호출하려면 호출이전에 컴파일러가 해당 함수의 원형을 본적이 있어야 한다. 그렇지 않다면 컴파일러는 반환형을 int로 추정하는 등 프로그램이 제대로 작동할 수 없기 때문이다. 이를 위해 함수의 원형을 헤더파일에 넣고 함수를 호출하는 곳에 해당 헤더파일을 추가해주는 것이다. 참고로 해당 함수의 실제 구현은 링크 단계에서 결합될 것이다.
변수선언의 공유 또한 함수원형의 공유와 마찬가지의 방식으로 이루어진다. 즉 소스파일에 정의부를 포함시키고 헤더파일에 선언부를 포함시키는 것이다. 여기서 변수의 선언은 변수의 이름과 타입을 컴파일러에게 알려주는 것이고, 변수의 정의는 변수를 실제로 메모리에 할당하는 것을 말한다. int i; 의 경우 변수 i를 정수형으로 선언함과 동시에 메모리 공간을 할당하여 정의하게 된다. 변수 i를 정의하지 않고 선언만 하려면 extern 키워드를 넣어 extern int i; 로 하면 된다. 이때 extern 키워드는 컴파일러에게 변수 i는 프로그램의 다른곳에 정의되어 있으니 따로 공간을 할당할 필요가 없다고 알려주게 된다.
#include 지시자를 통해 헤더파일을 추가하는 경우 동일한 헤더파일을 중복하여 추가되는 경우가 발생할 수 있다. 다음 그림과 같은 경우이다.
이러한 경우 반드시 컴파일 오류가 발생하는 것은 아니다. 변수선언 및 함수 원형을 여러번 포함하는 것은 문제를 일으키지 않는다. 다만 파일이 형정의를 갖고 있다면 컴파일 오류가 발생하게 된다. 컴파일러는 두개의 동일한 형이 존재한다고 판단하면 중복 정의 오류를 발생시키기 때문이다. 이를 방지하기 위해 아래 코드와 같이 #ifndef ~ #endif 지시자를 활용하여 헤더파일을 보호해줄 수 있다.
#ifndef BOOLEAN_H
#define BOOLEAN_H
#define TRUE 1
#define FALSE 0
typedef int Bool;
#endif
2. 다중 파일 프로그램의 빌드
(1) 기본적인 단계
우선 각 소스파일들은 독립적으로 컴파일되어야 한다. 헤더파일의 경우 이를 추가할 소스코드에서 자동으로 컴파일되므로 별도로 컴파일할 필요가 없다. 각 소스코드가 컴파일되면 목적파일들이 생성되고, 이들은 링커에 의해 연결되어 실행 가능한 파일을 생성한다.
(2) makefile
규모가 큰 프로그램의 경우 명령줄에 일일히 소스파일을 적기 어려울 것이다. 이를 위해 UNIX는 makefile이라는 개념을 만들었다고 한다. makefile은 프로그램의 구축을 위한 정보를 가진 파일로, 파일간의 종속성 또한 명시해준다. makefile이 생성되었으면 make기능을 사용하여 프로그램을 구축할 수 있다.
교재에 나온 makefile의 예시를 살펴보면 다음과 같다.
justify: justify.o word.o line.o
gcc -o justify justify.o word.o line.o
justify.o: justify.c word.h line.h
gcc -c justify.c
word.o: word.c word.h
gcc -c word.c
line.o: line.c line.h
gcc -c line.c
위 파일에는 네가지 파트가 존재하는데 이 각 파트들을 rule이라고 부른다. 각 rule의 첫줄에 타겟 파일을 명시하고 그 뒤에는 타겟파일이 종속되어 있는 파일들을 명시하고 있다. 이 파일들이 수정되어 타겟파일을 재구축해야 할 경우가 있을 수 있는데, 둘째줄에 이 재구축의 방식을 명시해주고 있다.
3. 기타 알게된 것들
- C 표준에서 소스파일이라함은 프로그래머가 작성해준 모든 파일을 의미한다고 한다. 즉 .c와 .h 모두 소스파일인 것이다. 다만 저자는 본 서에서 소스파일은 .c만을 의미한다고 명시하였다.
- GCC를 포함한 대부분의 C 컴파일러는 프로그램을 컴파일할 때 매크로의 값을 정할 수 있는 -D 옵션을 제공한다. 이를 통해 프로그램의 파일을 수정하지 않고 매크로의 값을 수정할 수 있다. 사용예시는 다음과 같다.
gcc -DVERSION=2 -o my_program my_program.c (-D옵션은 이처럼 띄어쓰기 없이 사용한다)