a.c (a.h), b.c (b.h), main.c 파일 생성
a.c, a.h, b.c, b.h, main.c 파일 내용은 각각 아래와 같다.
a.c
#include <stdio.h>
void foo()
{
printf("foo\n");
}
a.h
void foo();
b.c
#include <stdio.h>
void bar()
{
printf("bar\n");
}
b.h
void bar();
main.c
#include "a.h"
#include "b.h"
int main()
{
foo();
bar();
return 0;
}
object 파일 생성 (컴파일)
$gcc -c -o a.o a.c
$gcc -c -o b.o b.c
$gcc -c -o main.o main.c
헤더파일들은 main.c 파일에 포함되어 있으므로 명시해주지 않아도 된다.
여기서 -c 옵션은 링크를 하지 않고 컴파일만 하겠다는 의미이다. 이 옵션을 생략하면 main 함수를 찾을 수 없다는 오류가 출력된다.
실행파일 생성 (링크)
$gcc -o a.out main.o a.o b.o
Object파일들을 한데 묶는 링크 과정을 수행한다. 명령은 gcc지만, gcc 내부적으로 링커(ld)를 실행해서 실행 파일(a.out)을 생성한다.
실행파일 실행
$ ./a.out
foo
bar
Makefile을 사용하는 이유는 위와 같은 복잡한 과정을 생략할 수 있어서이기도 하지만, Makefile이 제공하는 강력한 기능 중 하나인 Incremental build 를 사용하기 위해서다.
Incremaental build 란?
반복적인 빌드 과정에서 변경된 소스코드에 의존성(Dependency)이 있는 대상들만 추려서 다시 빌드하는 기능이다.
예를 들어, 위의 빌드 예제에서 main.c의 한 줄만 바꾸고 다시 빌드를 할 때, main.o 컴파일(gcc -c -o main.o main.c)과 app.out링크(gcc -o a.out main.o a.o b.o)만 수행하는 경우가 이에 해당한다.
Makefile 은 대상(Target), 의존 관계(Dependency), 명령(Recipe)의 세 가지로 이뤄진다.
<target> : <dependency>
(tab)<Recipe>
대상 : 빌드 대상 이름. 명령에 의해 생성되는 결과 파일, 오브젝트 파일이나 실행 파일
의존 관계 : 대상을 만들 때 의존되는 파일들. 여기에 나열된 대상들을 먼저 만들고 빌드 대상을 생성한다. 의존 파일이 변경됐다면 대상을 만들 도록 명령을 내릴 것이다.
명령 : 빌드 대상을 생성하는 명령. 여러 줄로 작성할 수 있으며, 의존 관계에 있는 파일들이 변경됐거나 대상 파일이 없을 때 명령이 실행된다. 쉘에서 쓸 수 있는 명령을 사용할 수 있다.
명령을 쓸 때 반드시 tab키로 띄어준 후에 써야한다.
a.out : a.o b.o main.o
gcc -o a.out a.o b.o main.o
a.o : a.c
gcc -c -o a.o a.c
b.o : b.c
gcc -c -o b.o b.c
main.o : main.c
gcc -c -o main.o main.c
clean:
rm *.o a.out
$ ls
a.c a.h b.c b.h main.c Makefile
$ make
gcc -c -o a.o a.c
gcc -c -o b.o b.c
gcc -c -o main.o main.c
gcc -o a.out a.o b.o main.o
$ ls
a.c a.h a.o a.out b.c b.h b.o main.c main.o Makefile
실행 파일인 a.out 이 make 명령을 통해 새로 생성된 것을 알 수 있다.
a.out 실행
$ ./a.out
foo
bar
$ make clean
rm *.o a.out
$ ls
a.c a.h b.c b.h main.c Makefile
make clean 명령으로 확장자가 .o인 파일과 a.out 파일이 삭제된 것을 확인할 수 있다.
변수를 사용하면 Makefile을 보다 깔끔하고 확장성 있게 작성할 수 있다. 변수들 중에는 Make 내부에서도 함께 사용하는 내장 변수나(CFLAGS 등), 확장성을 용이하게 해 주는 자동 변수($@, $< 등)도 있다.
다음은 위의 예제를 빌드하기 위한 같은 목적의 Makefile이다.
CC=gcc
CFLAGS=-g -Wall
TARGET=a.out
OBJS=a.o b.o main.o
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS)
a.o : a.c
$(CC) -c -o a.o a.c
b.o : b.c
$(CC) -c -o b.o b.c
main.o : main.c
$(CC) -c -o main.o main.c
clean:
rm $(OBJECT) $(TARGET)
이제 좀 제대로 된 Makefile 같다. 여기서 정의한 각 변수의 의미는 다음과 같다.
CC
: 컴파일러CFLAGS
: 컴파일 옵션OBJS
: 중간 산물 Object 파일 목록TARGET
: 빌드 대상(실행 파일) 이름그 외에 자주 사용되는 내장 변수는 다음과 같다.
LDFLAGS
: 링커 옵션LDLIBS
: 링크 라이브러리참고로, Make에서 내부적으로 정의되어 있는 변수들은 다음 명령으로 확인할 수 있다. 주석이 같이 달려서 출력되기 때문에 필요한 기능을 바로 찾기 편리하다.
make -p
위 예제 Makefile의 7번째 줄을 보면 Recipe 중간에 정의한 적이 없는 변수인 $@
이 포함되어 있는 것을 알 수 있다. $@
은 현재 빌드 규칙 블록의 Target 이름을 나타내는 자동 변수다.
자동 변수는 위치한 곳의 맥락에 맞도록 치환된다. 즉, 7번재 줄의 $@는 Recipe를 실행할 때 $(TARGET)값으로 치환된다. 이렇게 하면 Target 이름을 수정할 때 Recipe까지 일일이 찾아서 수정하는 수고를 덜 수 있다.
Make에서 지원하는 자동 변수들 중 자주 사용하는 것들은 다음과 같다.
$@
: 현재 Target 이름
$^
: 현재 Target이 의존하는 대상들의 전체 목록
$?
: 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
$%
: 대상의 이름 (해당 규칙 대상이 아카이브 인 경우)
For example, if the target is foo.a(bar.o) then ‘@’ is foo.a. ‘$%’ is empty when the target is not an archive member.
Archive files are files containing named sub-files called members; they are maintained with the program ar and their main use is as subroutine libraries for linking. 즉, 파일을 묶어서 하나로 만든 것.
사용 가능한 자동 변수들의 전체 목록과 설명은 이 사이트에서 확인할 수 있다.
CC=<컴파일러>
CFLAGS=<컴파일 옵션>
LDFLAGS=<링크 옵션>
LDLIBS=<링크 라이브러리 목록>
OBJS=<Object 파일 목록>
TARGET=<빌드 대상 이름>
all: $(TARGET)
clean:
rm -f *.o
rm -f $(TARGET)
$(TARGET): $(OBJS)
$(CC) -o $@ $(OBJS)
정말 깔끔하게 정리 잘해주셨네요 덕분에 확실하게 개념 다지고 갑니다