Separate Compilation
분할 컴파일
- 프로그램을 구성하는 함수들을 별개의 파일에 넣는 개발방법을 의미한다. (C++에서의 권고사항이기도 하다.)
- 대규모 프로젝트에서 프로그램의 관리를 용이하게 하는 개발방법이다.
- 분할된 파일들은 개별적으로 컴파일되어, 하나의 최종 실행 프로그램으로 링크된다.
- 코드를 분할함으로써, 프로그램 개발 후 수정사항이 생겼을 때 수정한 부분에 해당하는 파일만 다시 컴파일하는 것이 가능해진다.
- Embarcadero C++ Builder, Microsoft Visual C++, Apple Xcode, Freescale CodeWarrior와 같은 대부분의 IDE(Intergrated Development Environment, 통합 개발 환경)들에서 Project 메뉴를 통해 이러한 기능을 제공하고 있다.
* Unix / Linux 시스템의 \(\texttt{make}\) 명령
- Unix와 Linux 시스템에서 분할 컴파일 개발에 사용되는 명령어이다.
- 개발하려는 프로그램이 어떤 파일들에 연관되는지, 언제 마지막으로 수정되었는지 등을 추적한다.
- 마지막으로 컴파일된 후에 변경된 소스 파일들이 발견되면 \(\texttt{make}\) 명령을 통해 프로그램을 재구성할 수 있다.
* C++ 표준에서는 보다 넓은 보편성을 위해 File(파일)이라는 용어 대신 Translation Unit(번역 단위)이라는 용어를 사용한다.
- Translation Unit은 개별적으로 컴파일할 수 있는 프로그램 단위로, Compilation Unit와 같은 의미이다.
소스코드를 분리하는 일반적인 방법
- 소스코드는 크게 세 가지 파일에 나눠서 작성된다.
\(\texttt{1. HeaderFile.h}\)
\(\to\) 함수 원형, 구조체 선언, 클래스 선언, 템플릿 선언, 인라인 함수 정의, \(\texttt{#define}\) 이나 \(\texttt{const}\) 키워드를 통한 기호 상수 선언
* 인라인 함수는 ODR(단일 정의 원칙)에 예외이기 때문에 헤더파일에 포함 가능한것이고, 일반 함수 정의부는 모든 파일에 걸쳐서 한 번만 정의되어야 하므로 헤더파일에 포함하면 안된다. (헤더파일은 2회 이상 포함될 가능성이 높기 때문!)
\(\texttt{2. FunctionDefinition.cpp}\)
\(\to\) 해당 프로그램에서 사용되는 사용자 정의 함수 구현부 (Function Definition)
\(\texttt{3. MainFunction.cpp}\)
\(\to \texttt{main( )}\) 함수
* 파일명은 프로그래머의 기호에 따라 얼마든지 자유롭게 설정할 수 있다. (위 파일명들은 단순한 예시이다.)
※ 함수 정의, 변수 선언부들은 헤더파일에 들어가서는 안된다.
- 헤더 파일은 여러 소스 파일들에 \(\texttt{include}\)될 수 있는데, 여러 파일에 포함되게 되면 한 프로그램내에 같은 함수 정의나 같은 변수 선언이 중복되기 때문이다.
\(\texttt{#include}\) Directive (\(\texttt{#include}\) 지시문)
- \(\texttt{#include}\) 지시문이 파일을 포함하는 방법은 크게 두 가지로 나뉜다.
1. Angle Brackets(홑화살괄호, < >)으로 파일명을 묶는 방법은 컴파일러가 표준 헤더 파일들이 들어 있는 호스트 시스템의 파일 시스템 영역에서 해당 파일을 우선적으로 검색하게 한다.
2. Double Quotation Marks(큰 따옴표, " ")로 파일명은 묶는 방법은 컴파일러가 현재 작업중인 파일 디렉토리나 소스 코드 디렉토리에서 해당 파일을 우선적으로 검색하게 한다. 만약, 해당 파일을 찾지 못했다면 Standard Location(표준 위치)로 검색 영역을 넓히게 된다.
\(\texttt{#ifndef, #endif}\) Directive (\(\texttt{#ifndef, #endif}\) 지시문)
- 헤더파일을 여러 번 포함시키는 것을 방지하기 위해 C나 C++에서 사용되는 표준 기법이다.
- 특정 소스파일에서 해더파일을 포함시킬 때, 두 번이상 중복시켜 포함시키는 것에 주의해야 하는데, 프로그래머의 실수로 두 번이상 포함시켰을 경우의 해결책으로 \(\texttt{#ifndef}\) 지시문이 존재한다.
- 사실, 프로그래머가 \(\texttt{#include}\) 지시문을 통해 실수로 두 번이상 포함시키는 경우보다는, 다른 헤더파일에서 이미 포함하고 있는 헤더파일을 멋모르고 포함시키는 경우가 대부분이고, 이는 프로그래머가 인지하기 어려운 문제이다.
- \(\texttt{#ifndef}\) 지시문은 "If Not Defined"을 의미하며, \(\texttt{#endif}\) 구문이 후미에 반드시 뒤 따라야 하는 문법 구조를 가지고 있다.
// HeaderF.h 파일의 내부
#ifndef HEADERF_H_ // HEADERF_H_ 라는 상수가 정의되어 있지 않다면,
#define HEADERF_H_ // HEADERF_H_ 상수를 지금부터 정의한다
...
#endif // #ifndef 지시문의 종결을 의미한다
- \(\texttt{#ifndef}\) 지시문에 의해, 한 번이라도 \(\texttt{HEADERF_H_}\) 상수가 정의된 적이 있다면, \(\texttt{#endif}\) 지시문 이전까지의 구문들은 실행되지 않는다.
(이와 같은 방법이 헤더파일을 포함시키는 작업 자체를 예방하지는 못하지만, 해당 영역에 있는 구문이 두 번 이상 실행되는것을 막는다(정확히는, 무시한다.).)
- 여기서, 기호상수 \(\texttt{HEADERF_H_}\)는 기호상수의 역할보다는, 해당 헤더파일이 한 번이라도 정의된 적이 있는지를 판별하는 토큰의 역할을 한다.
- "헤더이름(대문자)_H_"와 같이 토큰 역할을 하는 기호상수의 이름을 정의하는 관용표현이 존재한다.
Multiple Library Linking (다중 라이브러리 링크)
- 서로 다른 컴파일러는 각각의 Name Decoration(Name Mangling, 이름 장식, 본 블로그 포스트 참조) 방식을 가지고 있는데, 이 이름들의 차이로 인해 한 컴파일러가 만든 함수 호출을 다른 컴파일러가 만든 함수 정의에 대응하려는 링커의 작업을 방해하게 된다. (같은 함수라 하더라도 다른 이름 장식 기법을 통해 통해 만들어진 암호화된 이름이 서로 상이하므로)
- 따라서, 컴파일된 모듈을 링크시킬 때에는 각각의 Object File(목적 파일)이나 라이브러리들이 같은 컴파일러로 만들어진 것인지를 반드시 확인해야 한다.
- 소스코드 파일을 갖고 있다면, 본인이 사용하는 컴파일러를 사용해서 다시 컴파일함으로써 이러한 링크 에러를 해결할 수 있다.