Make(Makefile) 사용 가이드
원문 : https://modoocode.com/311
안녕하세요. 이번 강좌 부터는 C++ 언어 자체와는 직접 관련은 없지만 실제로 C++ 을 프로그래밍 하기 위해서 필요한 지식들과 툴들에 대해서 이야기 하고자 합니다. 그 첫 번째 타자로 Makefile 만들기가 되겠습니다.
아무래도 리눅스 환경에서 프로그래밍을 하신 분들은 아시겠지만, 보통 프로그램을 컴파일 할 때 make 라는 프로그램을 사용합니다. 윈도우에서 비주얼 스튜디오를 사용하신다면 컴파일 버튼을 누르면 알아서 컴파일 되는 것과는 달리, 쉘 상에서 컴파일을 하려면 어떤 파일들을 컴파일 하고, 어떠한 방식으로 컴파일 할 지 직접 컴파일러에게 알려줘야 합니다.
물론 매번 명령어를 치면 문제가 없겠지만 프로젝트의 크기가 커지고 파일들이 많아진다면 매번 그렇게 친다는 것이 불가능에 가까워집니다. 이 문제를 해결하기 위해서 리눅스에서는 make 라는 프로그램을 제공하는데, 이 프로그램은 Makefile 라는 파일을 읽어서 주어진 방식대로 명령어를 처리하게 합니다. 덕분에 많은 수의 파일들을 명령어 한 번으로 컴파일 할 수 있습니다.
이번 글에서는 make 프로그램이 어떠한 방식으로 작동되고, 프로그램들을 우리가 원하는 방식으로 컴파일 하기 위해서는 어떠한 방식으로 Makefile 을 작성해야 하는지 살펴보도록 하겠습니다.
소스 코드에서 완성된 프로그램까지
먼저 make 가 어떠한 방식으로 작동하는지 알기 전에 컴파일러를 통해 프로그램을 빌드하게 되면 어떠한 방식으로 소스 코드에서 하나의 프로그램이 완성되는지 살펴보도록 합시다.
컴파일 (Compile)
가장 먼저 여러분의 머리 속으로 떠오르는 과정은 바로 컴파일(Compile) 일 것입니다. 이 컴파일이라는 과정은 여러분의 소스 코드를 컴퓨터가 이해할 수 있는 어셈블리어로 변환하는 과정입니다.
예를 들어서 아래와 같이 foo.h 에 정의된 foo 함수와 bar.h 에 정의된 bar 함수를 main 함수에서 호출하는 간단한 프로그램을 살펴봅시다.
foo.h
int foo();foo.cc
#include <iostream>
#include "foo.h"
int foo() {
std::cout << "Foo!" << std::endl;
return 0;
}bar.h
bar.cc
main.cc
예를 들어서 main.cc 를 컴파일 하기 위해서는 아래와 같이 하면 됩니다.
만일 clang 을 사용하고 싶다면 g++ 자리에 clang 을 넣으시면 됩니다. clang 과 g++ 은 사용 명령어가 거의 똑같습니다.
g++ 에 전달하는 -c 명령어는 인자 다음에 주어지는 파일을 컴파일 해서 목적 파일(object file) 을 생성하라는 의미 입니다.
실제로, 성공적으로 컴파일 하였다면 아래와 같이 main.o 라는 파일이 생성된 것을 알 수 있습니다.
이 main.o 는 main.cc 를 컴파일 한 어셈블리 코드가 담겨있는 파일 입니다. 실제로 main.o 를 뜯어보면
와 같이 main.cc 의 어셈블리 코드가 잘 들어있음을 알 수 있습니다.
위 어셈블리가 뭔지 이해가 전혀 안가도 Make 가 뭔지 이해하는데 전혀 상관 없습니다. 그냥 '목적파일에는 소스 코드를 컴파일 한 어셈블리가 들어 있구나' 정도로 이해하시면 됩니다.
한 가지 재미있는 점은 컴파일러가 main.cc 를 컴파일 할 때 다른 파일들을 참조하지 않았다는 점입니다. 실제로 main.o 의 어셈블리를 슬쩍 보면 foo 나 bar 에 관한 내용이 하나도 없음을 알 수 있습니다.
따라서 main.o 자체로는 우리의 프로그램을 만들 수 없습니다. 왜냐하면 main.o 에는 foo 라는 함수를 호출해라! 라는 내용만 있지, foo 는 어디에 있고 이러이러한 방식으로 동작한다 에 관한 이야기는 없기 때문이죠.
눈썰미가 좋으신 분들은 위 어셈블리의 callq 자리에 함수 이름 대신에 이상한 값이 들어있음을 알 수 있습니다.실제로 작동하는 foo 에 관한 정보를 얻기 위해서는 foo.cc 를 컴파일 해서 만들어진 foo.o 가 필요하고, 마찬가지로 bar 에 관한 정보를 얻기 위해서는 bar.cc 를 컴파일 해서 만든 bar.o 가 필요합니다.
따라서 실제 프로그램을 제작하기 위해서는 이 각각의 main.o, foo.o, bar.o 를 하나로 합치는 과정이 필요하겠죠. 이와 같은 과정을 아래 그림과 같이 링킹(Linking) 이라고 합니다.

링킹 (Linking)
링킹이 이름 그대로 링크 하는 작업 인 이유는 실제로 서로 다른 파일에 흩어져 있던 함수나 클래스들을 한 데 묶어서 링크해주는 작업이기 때문이죠. 이 과정에서 main 함수 안에 foo 함수가 어디에 정의되어 있는지 위치를 찾고 제대로 된 함수를 호출할 수 있게 됩니다. 사실 이 링킹에 관해서 이야기만 해도 한참 할 수 있는데, 일단 이 글의 목표는 빌드 파일 만들기 이므로 여기서 줄이겠습니다.
아무튼 이 링킹 과정은 아래와 같이 컴파일러에 목적파일을 전달함으로써 수행됩니다.
여기서 -o 옵션은 그 뒤에 링킹 후에 생성할 실행 파일 이름을 이야기 합니다. 위 경우 main 이라는 실행 파일을 만들었고 만약에 이를 따로 지정하지 않는다면 그냥 ./a.out 이라는 파일을 디폴트로 생성하게 됩니다.
실제로 main 파일을 실행하게 된다면
실행 결과
와 같이 잘 나옵니다.
그렇다면 왜 make 를 쓰는지?
일단 위와 같이 main 실행 파일을 생성하기 위해서 입력한 쉘 명령어를 쭉 나열해보자면;
와 같습니다. 물론 파일 개수가 작다면 매 번 입력하는 것이 크게 문제가 되지는 않습니다만, 프로젝트 크기가 커지면 쳐야할 명령어가 더 많아지게 되서 복잡하겠지요 (특히 파일들이 다른 디렉토리에 있는다면 말이죠.)
물론 쉘 스크립트를 조금 아시는 분이라면
그냥 위 명령어들을 순차적으로 실행하는 쉘 스크립트를 짜면 되는거 아닌가?
라고 물으실 수 있습니다. 그런데 이 방식으로 한다면 한 가지 문제점이 있는데, 예를 들어서 여러분이 main.cc 를 수정하였다고 해봅시다.
그렇다면 실제로는
딱 이 두 명령어만 실행하면 됩니다. 왜냐하면 main 파일을 바꾼다고 해서 foo 나 bar 의 컴파일 된 내용이 바뀌지 않기 때문이죠. 하지만 위 처럼 단순하게 쉘 스크립트를 짜게 된다면 파일 하나만 바꿔도 전체 모든 파일들을 컴파일 하게 되므로 컴파일 시간이 매우 길어지게 됩니다. 하지만 make 를 사용하면 일부 파일을 수정할 경우 필요한 명령만 빠르게 컴파일을 할 수 있도록 도와줍니다.
자 그렇다면 거두절미하고 make 의 사용법과 make 를 위한 Makefile 을 어떻게 작성하는지 알아보겠습니다.
make
make 는 간단히 말하자면 주어진 쉘 명령어들을 조건에 맞게 실행하는 프로그램 이라고 볼 수 있습니다. 이 때 어떠한 조건으로 명령어를 실행할지 담은 파일을 Makefile 이라고 부르며, make 를 터미널 상에서 실행하게 된다면 해당 위치에 있는 Makefile 을 찾아서 읽어들이게 됩니다.
그렇다면 Makefile 에는 어떠한 방식으로 조건을 기술할까요?
Makefile 은 기본적으로 위와 같이 3 가지 요소로 구성되어 있습니다.
target
make 를 실행할 때 make abc 과 같이 어떠한 것을 make 할 지 전달하게 되는데 이를 타겟(target) 이라고 부릅니다. 만일 make abc 를 하였을 경우 타겟 중에 abc 를 찾아서 이에 대응되는 명령을 실행해줍니다.
실행할 명령어(recipes)
주어진 타겟을 make 할 때 실행할 명령어들의 나열입니다. 한 가지 중요한 점은 recipe 자리에 명령어를 쓸 때 반드시 탭 한 번으로 들여쓰기를 해줘야만 합니다. 보통 요즘의 편집기의 경우 (예를 들어 VSCode), 자동으로 탭을 스페이스로 바꿔주는 옵션이 활성화 되어 있을 텐데 make 가 Makefile 을 제대로 읽어들이기 위해서는 반드시 탭을 사용해야만 합니다!
필요 조건들(prerequisites)
주어진 타겟을 make 할 때 사용될 파일들의 목록 입니다. 다른 말로 의존 파일(dependency) 이라고도 합니다. 왜냐하면 해당 타겟을 처리하기 위해 건드려야 할 파일들을 써놓은 것이기 때문입니다.
만일 주어진 파일들의 수정 시간 보다 타겟이 더 나중에 수정되었다면 해당 타겟의 명령어를 실행하지 않습니다. 왜냐하면 이미 이전에 타겟이 만들어져있다고 간주하기 때문이죠.
예를 들어서 타겟이 main.o 이고, 명령어가 g++ -c main.cc 라면, 필요 조건들은 main.cc, foo.h, bar.h 가 되겠죠. 왜냐하면 이들 파일들 중 하나라도 바뀐다면 main 을 새로 컴파일 해야 하기 때문이죠. 반면에 main.o 의 생성 시간이 main.cc, foo.h, bar.h 들의 마지막 수정 시간 보다 나중이라면, 굳이 main.o 를 다시 컴파일 할 필요가 없습니다.
그렇다면 위 내용을 바탕으로 Makefile 을 간단하게 구성해보겠습니다.
주의 사항
위 Makefile 을 그대로 복사해서 실행한다면 아마 실행이 안될 것입니다. 왜냐하면 탭이 스페이스로 바뀌어서 그런데요, 제대로 Makefile 을 만드시기 위해서는 g++ 앞에 오는 스페이스 두 번을 탭으로 바꾸어서 저장해보세요.
우리의 목표는 실행 파일인 main 을 만드는 것이기 때문에 make main 을 실행해보면
그리고 실제로 main 이 잘 만들어짐을 알 수 있습니다.
여러분이 한 번도 컴파일 하지 않은 상태에서 make main 을 실행하면 make 파일은 다음과 같은 순서로 명령어를 처리합니다.
make main이니까 Makefile 에서 타겟이main인 녀석을 찾아보자.오.
main의 필요한 파일들이foo.o bar.o main.o이네? 이들 파일을 어떻게 만드는지 각각의 파일 이름으로된 타겟들을 찾아보자.foo.o의 경우 필요한 파일이foo.cc네? 아직foo.o가 없으니까 주어진 명령어를 실행해야 하겠군!마찬가지로
bar.o, main.o도 컴파일자 이제 마지막으로
g++ foo.o bar.o main.o -o main를 실행해야지
만약에 여러분이 make main 을 한 번 실행한 상태에서 foo.cc 만 수정하였다고 해봅시다. 그렇다면 아래와 같이 필요한 부분만 재컴파일 됩니다.
make main이니까 Makefile 에서 타겟이main인 녀석을 찾아보자.오.
main의 필요한 파일들이foo.o bar.o main.o이네? 이들 파일을 어떻게 만드는지 각각의 파일 이름으로된 타겟들을 찾아보자.foo.o의 경우 필요한 파일이foo.cc네? 그런데foo.o의 생성 시간 보다foo.cc의 수정 시간이 더 나중이군! 주어진 명령어를 다시 실행해야겠어bar.o의 경우 필요한 파일이bar.cc인데,bar.o의 생성 시간이bar.cc의 수정 시간 보다 나중이네. 굳이 명령어들을 실행하지 않아도 되겠어main.o도 마찬가지로 다시 안바꿔도 되겠군!main의 필요한 파일들 중foo.o가 바뀌었으니 내 명령어들도 다시 실행해야겠어.
따라서 딱 필요한 g++ -c foo.cc 와 g++ foo.o bar.o main.o -o main 만이 실행됩니다.
변수
재미있게도 Makefile 내에서 변수를 정의할 수 있습니다.
위 경우 CC 라는 변수를 정의하였는데, 이제 Makefile 내에서 CC 를 사용하게 된다면 해당 변수의 문자열인 g++ 로 치환됩니다. 이 때 변수를 사용하기 위해서는 $(CC) 와 같이 $() 안에 사용하고자 하는 변수의 이름을 지정하면 됩니다. 예를 들어서
는 사실
과 정확히 같은 명령이 됩니다.
참고로 정의하지 않는 변수를 참조하게 된다면 그냥 빈 문자열로 치환됩니다.
변수를 정의하는 두 가지 방법
주의 사항
이 부분은 TMI 이므로 바쁘신 분들은 그냥 넘어가셔도 됩니다.
Makefile 상에서 변수를 정의하는 방법으로 = 를 사용해서 정의하는 방법과 := 를 사용해서 정의하는 방법이 있습니다. 이 둘은 살짝 다릅니다.
= 를 사용해서 변수를 정의하였을 때, 정의에 다른 변수가 포함되어 있다면 해당 변수가 정의되기 될 때 까지 변수의 값이 정해지지 않습니다. 예를 들어서
C 의 경우 B 의 값을 참조하고 있고, B 의 경우 A 의 값을 참조하고 있습니다. 하지만 B = 를 실행한 시점에서 A 가 정의되지 않았으므로 B 는 그냥 빈 문자열이 되어야 하지만 =로 정의하였기 때문에 A 가 실제로 정의될 때 까지 B 와 C 가 결정되지 않습니다. 결국 마지막에 A = a 를 통해 A 가 a 로 대응되어야, C 가 a 로 결정됩니다.
반면에 := 로 변수를 정의할 경우, 해당 시점에의 변수의 값만 확인 합니다. 따라서 위 경우 B 는 그냥 빈 문자열이 되겠지요.
대부분의 상황에서는 = 나 := 중 아무거나 사용해도 상관 없습니다. 하지만
만일 변수들의 정의 순서에 크게 구애받고 싶지 않다면
=를 사용하는 것이 편합니다.A =와 같이 자기 자신을 수정하고 싶다면:=를 사용해야지 무한 루프를 피할 수 있습니다.
만 명심하면 됩니다.
그렇다면 이들 변수를 사용해서 Makefile 을 조금 더 깔끔하게 바꾸어보겠습니다.
make 를 실행해보면
와 같이 잘 나옵니다.
CC 와 CXXFLAGS 는 Makefile 에서 자주 사용되는 변수로 보통 CC 에는 사용하는 컴파일러 이름을, CXXFLAGS 에는 컴파일러 옵션을 주는 것이 일반적 입니다. 참고로 이는 C++ 의 경우 이고, C 의 경우 CFLAGS 에 옵션을 줍니다.
우리의 경우 g++ 컴파일러를 사용하며, 옵션으로는 Wall (모든 컴파일 경고를 표시) 과 O2 (최적화 레벨 2) 를 주었습니다.
사실 -Wall 은 이름과는 다르게 모든 경고를 표시하지 않습니다.
PHONY
Makefile 에 흔히 추가하는 기능으로 빌드 관련된 파일들 (.o 파일들)을 모두 제거하는 명령을 넣습니다.
실제로 make clean 을 실행해보면 생성된 모든 목적 파일과 main 을 지워버림을 알 수 있습니다.
그런데, 만약에 실제로 clean 이라는 파일이 디렉토리에 생성된다면 어떨까요? 우리가 make clean 을 하게 되면, make 는 clean 의 필요 파일들이 없는데, clean 파일이 있으니까 clean 파일은 항상 최신이네? recipe 를 실행 안해도 되겠네! 하면서 그냥 make clean 명령을 무시해버리게 됩니다.
실제로 디렉토리에 clean 이라는 파일을 만들어놓고 실행해보면 위와 같이 이미 clean 은 최신이라며 recipe 실행을 거부합니다.
이와 같은 상황을 막기 위해서는 clean 을 PHONY 라고 등록하면 됩니다.
Phony 는 '가짜의, 허위의' 이라는 뜻입니다.아래와 같이 말이지요.
이제 make clean 을 하게 되면 clean 파일의 유무와 상관 없이 언제나 해당 타겟의 명령을 실행하게 됩니다.
패턴 사용하기
우리의 경우 파일이 3 개 밖에 없어서 다행이였지만 실제 프로젝트에는 수십~ 수백 개의 파일들을 다루게 될 것입니다. 그런데, 각각의 파일들에 대해서 모두 빌드 방식을 명시해준다면 Makefile 의 크기가 엄청 커지겠지요.
다행이도 Makefile 에서는 패턴 매칭을 통해서 특정 조건에 부합하는 파일들에 대해서 간단하게 recipe 를 작성할 수 있게 해줍니다.
일단 먼저 비슷하게 생긴 위 두 명령들을 어떻게 하면 하나로 간단하게 나타낼 수 있는지 보겠습니다.
먼저 %.o 는 와일드카드로 따지면 마치 *.o 와 같다고 볼 수 있습니다. 즉, .o 로 끝나는 파일 이름들이 타겟이 될 수 있겠지요. 예를 들어서 foo.o 가 타겟이라면 % 에는 foo 가 들어갈 것이고 bar.o 의 경우 % 에는 bar 가 들어갈 것입니다.
따라서 예를 들어 foo.o 가 타겟일 경우
가 됩니다. 참고로 패턴은 타겟과 prerequisite 부분에만 사용할 수 있습니다. recipe 부분에서는 패턴을 사용할 수 없습니다. 따라서 컴파일러에 foo.cc 를 전달하기 위해서는 Makefile 의 자동 변수를 사용해야 합니다.
$< 의 경우 prerequisite 에서 첫 번째 파일의 이름에 대응되어 있는 변수 입니다. 위 경우 foo.cc 가 되겠지요. 따라서 위 명령어는 결과적으로
가 되어서 이전의 명령어와 동일하게 만들어냅니다.
Makefile 에서 제공하는 자동 변수로는 그 외에도 아래 그림과 같이 $@, $<, $^ 등등이 있습니다.

$@: 타겟 이름에 대응됩니다.$<: 의존 파일 목록에 첫 번째 파일에 대응됩니다.$^: 의존 파일 목록 전체에 대응됩니다.$?: 타겟 보다 최신인 의존 파일들에 대응됩니다.$+:$^와 비슷하지만, 중복된 파일 이름들 까지 모두 포함합니다.
하지만 애석하게도 위 패턴으로는
를 표현하기에는 부족합니다. 왜냐하면 의존 파일 목록에 main.h 가 없고 foo.h 와 bar.h 가 있기 때문이죠. 사실 곰곰히 생각해보면 이 의존파일 목록에는 는 해당 소스 파일이 어떠한 헤더파일을 포함하냐에 결정되어 있습니다. main.cc 가 foo.h 와 bar.h 를 include 하고 있기 때문에 main.o 의 prerequisite 로 main.cc 외에도 foo.h 와 bar.h 가 들어가 있는 것입니다.
물론 매번 이렇게 일일히 추가할 수 있겠지만, 소스 파일에 헤더 파일을 추가할 때 마다 Makefile 을 바꿀 수는 없는 노릇이니까요. 하지만 다행이도 컴파일러의 도움을 받아서 의존파일 목록 부분을 작성할 수 있습니다.
자동으로 prerequisite 만들기
컴파일 시에 -MD 옵션을 추가해서 컴파일 해봅시다.
그렇다면 main.d 라는 파일을 생성합니다. 파일 내용을 살펴보면;
놀랍게도 마치 Makefile 의 target: prerequisite 인것 같은 부분을 생성하였습니다. 그렇습니다. 컴파일 시에 -MD 옵션을 추가해주면, 목적 파일 말고도 컴파일 한 소스파일을 타겟으로 하는 의존파일 목록을 담은 파일을 생성해줍니다.
참고로 main.cc, foo.h, bar.h 까지는 이해가 가는데 왜 생뚱맞은 /usr/include/stdc-predef.h 이 들어가 있냐고 물을 수 있는데, 이 파일은 컴파일러가 컴파일 할 때 암묵적으로 참조하는 헤더 파일이라고 보시면 됩니다. 아무튼 이 때문에 컴파일러가 생성한 의존 파일 목록에는 포함되었습니다.
문제는 이렇게 생성된 main.d 를 어떻게 우리의 Makefile 에 포함할 수 있냐 입니다. 이는 생각보다 간단합니다.
위 include main.d 는 main.d 라는 파일의 내용을 Makefile 에 포함하라는 의미 입니다.
그렇다면 아싸리
부분을 아예 컴파일러가 생성한 .d 파일로 대체할 수는 없을까요? 물론 있습니다!
$(OBJS:.o=.d) 부분은 OBJS 에서 .o 로 끝나는 부분을 .d 로 모두 대체하라는 의미 입니다. 즉, 해당 부분은 -include foo.d bar.d main.d 가 되겠죠. 참고로 foo.d 나 bar.d 가 include 될 때 이미 있는 %.o: %.cc 는 어떻게 되냐고 물을 수 있는데 같은 타겟에 대해서 여러 의존 파일 목록들이 정해져 있다면 이는 make 에 의해 모두 하나로 합쳐집니다. 따라서 크게 걱정하실 필요는 없습니다.
덧붙여 include 에서 -include 로 바꾸었는데, -include 의 경우 포함하고자 하는 파일이 존재하지 않아도 make 메세지를 출력하지 않습니다.
맨 처음에 make 를 할 때에는 .d 파일들이 제대로 생성되지 않은 상태이기 때문에 include 가 아무런 .d 파일들을 포함하지 않습니다. 물론 크게 문제 없는 것이 어차피 .o 파일들도 make 가 %.o: %.cc 부분의 명령어들을 실행하면서 컴파일을 하기 때문에 다음에 make 를 하게 될 때에는 제대로 .d 파일들을 로드할 수 있겠죠.
최종 정리
아래와 같이 간단한 프로젝트 구조를 생각해봅시다.
모든 소스 파일은 src 에 들어가고 빌드 파일들은 obj 에 들어갑니다. 종종 헤더 파일들을 따로 include 에 빼는 경우가 있는데 저는 굳이 라이브러리를 만드는 경우가 아니라면 별로 선호하지 않습니다. (굳이 다른 디렉토리에 넣을 이유가 뭔지 모르겠습니다.)
아무튼 이와 같은 구조에서 항상 사용할 수 있는 만능 Makefile 은 아래와 같습니다.
주의 사항
복사한 후에 $(CC) 와 rm 앞에 스페이스 두 개를 꼭 TAB 으로 치환해주세요! 안 그러면 make 가 읽지 못합니다.
추가된 부분만 간단히 부연 설명을 하자면
먼저 SRC_DIR 안에 있는 모든 파일들을 SRCS 로 읽어들이려 하고 있습니다. wildcard 는 함수로 해당 조건에 맞는 파일들을 뽑아내게 되는데, 예를 들어서 foo.cc, bar.cc, main.cc 가 있을 경우 $(wildcard $(SRC_DIR)/*.cc) 의 실행 결과는 ./src/foo.cc ./src/bar.cc ./src/main.cc 가 될 것입니다.
여기서 우리는 foo.cc bar.cc main.cc 로 깔끔하게 경로를 제외한 파일 이름만 뽑아내기 위해 notdir 함수를 사용합니다. notdir 은 앞에 오는 경로를 날려버리고 파일 이름만 깔끔하게 추출해줍니다.
따라서 이 부분에서 OBJS 는 foo.o bar.o main.o 가 될 것입니다.
이제 이 OBJS 를 바탕으로 실제 .o 파일들의 경로를 만들어내고 싶습니다. 이를 위해서는 이들 파일 이름 앞에 $(OBJ_DIR)/ 을 붙여줘야 겠지요. 이를 위해선 patsubst 함수를 사용하면 됩니다.
patsubst 함수는 $(patsubst 패턴,치환 후 형태,변수) 의 같은 꼴로 사용합니다.
따라서 위 경우 $(OBJS) 안에 있는 모든 %.o 패턴을 $(OBJ_DIR)/%.o 로 치환해라 라는 의미가 될 것입니다. 아무튼 덕분에 OBJECTS 에는 이제 ./obj/foo.o ./obj/bar.o ./obj/main.o 가 들어가게 됩니다.
그 뒤에 내용은 앞의 글을 잘 따라 오신 분들이라면 잘 이해 하실 수 있으리라 믿습니다.
헤더 파일들을 따로 뽑는 경우
만약에 헤더 파일들만 따로 뽑는다면 아래와 같은 파일 구조를 가지겠죠.
이 경우 Makefile 을 아래와 같이 간단히 수정하면 됩니다.
사실 기존과 별 차이 없고 그냥 컴파일러 옵션에 -Iinclude/ 를 추가해주면 됩니다. 여기서 include 는 헤더파일 경로 입니다.
멀티 코어를 활용해서 Make 속도를 올리자
한 가지 팁으로 그냥 make 를 실행하게 되면 1 개의 쓰레드만 실행되어서 속도가 꽤나 느립니다. 특히 GCC 나 커널을 컴파일 할 경우 한 두 시간은 그냥 걸리지요. 만일 여러분의 컴퓨터가 멀티 코어 CPU 를 사용한다면 (아마 대부분 그럴 것이라 생각합니다) make 를 여러 개의 쓰레드에서 돌릴 수 있습니다. 이를 위해서는 인자로 -j 뒤에 몇 개의 쓰레드를 사용할 지 숫자를 적어서 전달하면 됩니다.
예를 들어서
을 하면 make 가 8 개의 쓰레드에 나뉘어서 실행됩니다. 아마 make 속도가 월등하게 향상되는 것을 보실 수 있을 것입니다. 통상적으로 코어 개수 + 1 만큼의 쓰레드를 생성해서 돌리는 것이 가장 속도가 빠릅니다.
만약에 내 컴퓨터의 코어 개수를 모른다면 리눅스 터미널의 경우
으로 하면 $(nproc) 이 알아서 내 컴퓨터의 현재 코어 개수로 치환됩니다.
Last updated