C언어 - Makefile에 대하여

resister_boy·2022년 11월 18일
0

프로그래밍 개념

목록 보기
1/3
post-thumbnail

make란 무엇일까?

GNU make를 개발한 GNU에서는 make를 다음과 같이 소개하고 있습니다.

The make utility automatically determines which pieces of a large program need to be recompiled, and issues commands to recompile them.

make 유틸리티는 다시 컴파일 해야하는 대형 프로그램 부분을 자동으로 판별하고 이를 다시 컴파일하는 명령을 실행합니다.

규모가 큰 프로그램을 개발할 때는 하나의 파일을 여러 개로, 모듈을 나누어 진행하게 됩니다. 만일 make를 사용하지 않을 경우 여러 개의 소스 코드를 하나씩 빌드해야 해야 합니다. 즉, 소스 코드를 오브젝트 코드로 변환하고 링크해서 실행 파일을 만들어야 한다는 것입니다.

여기까지만 하더라도 비효율적이지만, 만일 하나의 파일에 문제가 생겨 다시 컴파일을 해야한다면 어떨까요? 이 모든 작업을 처음부터 다시 해야만 합니다. 이때 사용하는 것이 바로 make입니다.

make는 여러 개의 모듈화된 소스 코드 파일을 빌드할 때 사용하는 프로그램 빌드 도구입니다. make는 여러 파일들 사이의 의존성과 각 파일에 필요한 명령을 정의함으로써 프로그램을 컴파일할 수 있으며, 최종 프로그램, 즉 실행 파일을 만드는데 필요한 명령어를 서술할 수 있는 표준 문법을 제공하고 있습니다.

Incremental Build

Incremental Build는 make가 제공하는 강력한 기능 중 하나입니다. Incremental Build는 반복적인 빌드 과정에서 변경된 소스코드의 의존하고 있는 대상들만 다시 빌드하는 기능으로, 쉽게 말해 make 명령어를 사용하여 프로그램을 다시 빌드할 경우 makefile에 정의된 전체 명령어를 실행하는 것이 아니라, 수정된 코드와 연관된 명령어만 다시 실행하는 것이라고 할 수 있습니다.

makefile은 무엇일까?

makefile은 각 파일 사이의 의존성과 필요한 명령이 정의된 파일입니다. 즉, 최종 프로그램이 만들기 위해 필요한 명령어가 서술된 파일입니다. 대상(Target), 의존 관계(Dependency), 명령어(Recipe)로 구성됩니다.

<Target> : <Dependency>
(tab) <Command>

Target

명령어에 의해 생성되는 파일이며, 컴파일을 통해 만들어진 오브젝트 파일 또는 링킹을 통해 만들어진 실행 파일이 여기에 해당됩니다.

Dependency

Target을 생성할 때 필요한 파일, 다시 말해 Target이 의존하는 파일을 의미하며, 오브젝트 파일을 생성할 때 필요한 소스 파일 또는 실행 파일을 생성할 때 필요한 오브젝트 파일이 해당됩니다. 만일 Dependency(소스 파일 또는 오브젝트 파일)가 변경되었을 경우 Incremental Build 기능에 따라 변경된 Dependency에 의존하는 Target은 자동으로 다시 빌드됩니다.

Recipe

빌드 대상을 생성하는 명렁어를 의미하며, 여러 줄로 작성할 수 있고 ShellScript를 사용할 수도 있습니다. 명령어는 반드시 tab으로 들여쓰기를 한 후에 작성되어야 합니다.

makefile 작성하기

// <Target> : <Dependency>
//   <Command>

// 첫 번째 블록은 a.o b.o main.o(Dependency)를 통해 a.out(Target)을 생성합니다.
// a.out은 a.o b.o main.o에 의존합니다.
// a.out을 생성하기 위해 gcc -o a.out a.o b.o main.o 명령어가 실행됩니다.
a.out : a.o b.o main.o 
	gcc -o a.out a.o b.o main.o


// 두 번째 블록은 a.c(Dependency)를 통해 a.o(Target)을 생성합니다.
// a.o는 a.c에 의존합니다.
// a.o을 생성하기 위해 gcc -c -o a.o a.c 명령어가 실행됩니다.
a.o : a.c
	gcc -c -o a.o a.c

// 세 번째 블록은 b.c(Dependency)를 통해 b.o(Target)을 생성합니다.
// b.o는 b.c에 의존합니다.
// b.o을 생성하기 위해 gcc -c -o b.o b.c 명령어가 실행됩니다.
b.o : b.c
	gcc -c -o b.o b.c

// 네 번째 블록은 main.c(Dependency)를 통해 main.o(Target)을 생성합니다.
// main.o는 main.c에 의존합니다.
// main.o을 생성하기 위해 gcc -c -o main.o main.c 명령어가 실행됩니다.
main.o : main.c
	gcc -c -o main.o main.c

// 다섯 번째 블록에서는 makefile에서 macro를 정의합니다.
// 아래의 경우 make clean을 사용하면 make를 통해 만들어진 오브젝트 파일과 실행파일 a.out이 제거됩니다.
clean:
	rm *.o a.out

위 코드가 작성된 makefile이 존재하는 디렉토리 내에서 make를 실행합니다. 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

변수를 활용한 makefile 작성

makefile에서도 변수를 사용할 수 있습니다. make 표준문법에서는 make에서 사용할 수 있는 내장변수와 자동변수를 제공하고 있습니다.

make 내장변수

make가 제공하는 내장변수는 [내장변수]=[변수명]과 같은 방식으로 정의하고, $()를 사용하여 호출할 수 있습니다.

CC: 컴파일러

CFLAGS: 컴파일옵션

OBJS: 최종 실행파일을 만들 때 함께 링킹할 오브젝트 파일들

TARGET: 빌드 대상, 즉 실행파일

LDFLAGS: 링커 옵션

LDLIBS: 링크 라이브러리

make 내장변수의 선언과 사용

CC=gcc
CFLAGS=-g -Wall
TARGET=a.out
OBJECTS=a.o b.o main.o

$(TARGET) : $(OBJS) 
	$(CC) -o $(TARGET) $(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 $(OBJS) $(TARGET)

make 자동변수

make가 제공하는 자동변수는 특별히 선언할 필요없이 사용할 수 있습니다. 또한 사용할 때에도 $[자동변수]와 같은 방식으로 ()없이 사용할 수 있습ㄴ디ㅏ.

$@: Target ex) a.out

$^: 모든 Dependency ex) a.o b.o main.o

$<: 첫 번째 Dependency ex) a.o

$?: 변경된 Dependency ex) main.o

$*: Target의 이름 ex) a

make 자동변수의 선언과 활용

CC=gcc
CFLAGS=-g -Wall
TARGET=a.out
OBJECTS=a.o b.o main.o

$@ : $^ 
	$(CC) -o $@ $^

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 $@ $^

Reference

https://ko.wikipedia.org/wiki/Make_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)

https://love-every-moment.tistory.com/47

https://velog.io/@woodstock1993/Makefile

https://bowbowbow.tistory.com/12

https://www.gnu.org/software/make/manual/make.html

profile
좋은 제품을 만드는 사람

0개의 댓글