[C/C++] make 사용법

Eugene CHOI·2022년 4월 12일
1

C/C++

목록 보기
9/9

cli환경에서 gcc를 이용하여 컴파일 및 빌드를 해 보신 분들이라면 수십개의 파일을 gcc 명령어만으로 빌드하는게 얼마나 어려운지 아실 것입니다.
그러한 수고스러움을 덜어주기 위한 스크립트가 makefile입니다.
shell 명령어들을 우리가 원할 때 원하는 조건으로 쉽게 실행할 수 있게 해 주는 도구가 make입니다.
make를 실행하면 해당 working directory에서 자동으로 makefile을 찾아 실행합니다.
makefile의 file이름은 세 가지가 가능합니다.
세 개의 파일이 모두 있다면 숫자가 낮은 순서대로 우선순위를 가집니다.

  1. GNUmakefile
  2. makefile
  3. Makefile

makefile의 script에서 한 줄 주석 기호는 #입니다.


Rule Block

기본적인 빌드 규칙 블록 작성법은 다음과 같습니다.

target: dependancy
	command
  • target은 생성하고자 하는 목적물을 의미합니다.
  • dependancy는 target을 만들기 위하여 필요한 요소를 의미합니다.
  • command는 일반 shell 명령어이고 반드시 Tab 문자를 통하여 들여쓰기가 있어야 한다. @ 문자를 앞에 추가하면 명령 실행줄이 shell에 표시되지 않습니다.

makefile의 실행법은 다음과 같습니다. 물론 make가 깔려 있어야합니다.

$ make "target"

target을 주어주지 않으면 자동으로 가장 위의 target 하나만 실행합니다.

Example1

main.o:
	gcc -c main.c

이렇게 작성된 makefile을 실행하려면 make 또는 make main.o라고 입력하면 됩니다.

Example2

app.out: main.o lib.o com.o
	gcc -o app.out main.o lib.o com.o
main.o:
	gcc -c main.c
lib.o:
	gcc -c lib.c
com.o:
	gcc -c com.c

makefile script는 위에서 아래로 실행되고, 단 하나의 target만 실행하고 끝납니다.
따라서 app.out만 실행되고 끝나야 합니다.
하지만 app.out을 만드려고 봤더니 main.o, lib.o, com.o 파일이 dependancy로 잡혀 있습니다.
따라서 아래쪽에서 각 dependancy의 target을 같이 실행하여 자동으로 dependancy를 해결합니다.

(참조) gcc 컴파일러의 -c 옵션은 link를 수행하지 않고 바이너리 파일만 만들라는 의미입니다.

Example3

main.o:
	gcc -c main.c
lib.o:
	gcc -c lib.c
com.o:
	gcc -c com.c
app.out: main.o lib.o com.o
	gcc -o app.out main.o lib.o com.o

그럼 만약 순서가 바뀌어 있다면 어떻게 될까요? main.o target만 실행되고 종료되게 됩니다.
실행 순서를 바꾸거나 make app.out으로 target을 명시해주는 방법이 있습니다.


Incremental build

만약 빌드한 적이 있는 소스 코드에 대해서 다시 make를 시도하면 변경된 소스코드에 dependancy가 있는 대상만 추려서 다시 빌드하는 기능입니다.
그렇기 때문에 빌드 도중 오류가 발생하여 중단되어도 이미 빌드를 했던 부분에 대해서 다시 수행하지 않고 중단된 시점부터 재시작합니다.
따라서 target 뒤에 dependancy를 명시하여 어떤 의존성을 가지고 있는지 알려주는것이 좋은 방법입니다.


Built-In Rule

자주 사용하는 빌드 규칙은 굳이 기술하지 않아도 자동으로 처리해줍니다.

사실 Examle2처럼 모두 기술하지 않고 gcc 명령어 하나만 적어도 자동으로 생성합니다.

app.out: main.o lib.o com.o
	gcc -o app.out main.o lib.o com.o

하지만 위와 같이 작성하는 경우 소스파일 dependancy가 명시 되어 있지 않기 때문에 파일의 변경을 추적하지 못합니다.
따라서 다음과 같이 dependancy도 같이 명시해주면 좋습니다.

 app.out: main.o lib.o com.o
	gcc -o app.out main.o lib.o com.o

main.o: main.c
lib.o: lib.c
com.o: com.c

Phony Targets

일반적으로 target은 목적 결과물의 파일 이름입니다. 하지만 꼭 파일 이름을 작성할 필요가 없습니다.
예를 들어 make를 이용해 수십개의 object 파일을 생성했다고 했을 때, 코드 수정 없이 다시 깨끗하게 처음부터 다시 빌드하고 싶다면 모든 object 파일을 지워야 합니다.

그럼 다음과 같이 dependancy 없이 rule block을 작성하고 target은 내가 원하는 이름으로 지정하면 됩니다.

clean:
	rm *.o

하지만 만~~~약에 clean이라는 이름의 파일이 있다면 Incremental build에 의해서 이 target의 command는 실행되지 않을겁니다.
그럴 땐 다음과 같이 Phony Target을 만들어주면 됩니다.
진짜 목적물을 만들기 위한게 아니라 단순 스크립트를 실행하기 위한 가짜 목적물을 만들겠다는 것을 의미합니다.

.PHONY: clean
clean:
	rm *.o

Macro

일반 변수를 만드는 것과 비슷합니다. 등호(=)를 기준으로 왼쪽에는 Label을 오른쪽에는 내용을 적습니다.

CC = gcc
RM = rf -f
FLAGS = --O0 --Wall -pthread

Macro를 사용할 때는 $ 기호를 붙이고 소괄호( )로 감싸줍니다.

 app.out: main.o lib.o com.o
	$(CC) $(FLAGS) -o app.out main.o lib.o com.o

위 코드는 Macro에 의해 다음과 같이 치환됩니다.

 app.out: main.o lib.o com.o
	gcc --O0 --Wall -pthread -o app.out main.o lib.o com.o
Symbolmean
=지연된 값을 변수에 할당. 변수를 사용할 때마다 값을 읽어온다.
:=::=와 같다. 값을 한 번만 읽어와서 저장하여 사용한다.
?=변수가 정의되어 있지 않으면 :=와 같은 역할을 하고, 변수가 이미 있다면 아무 것도 하지 않는다.
+=이미 있는 변수라면 공백을 하나 붙이고 값을 추가한다. 없는 변수라면

Automatic Variable

variablemean
$@현재 Target의 이름
$*현재 Target의 확장자를 제외한 이름
$^현재 Target의 중복을 제외한 전체 dependancy
$+현재 Target의 중복을 포함한 전체 dependancy
$?현재 Target의 의존하는 대상들 중 변경된 대상 목록
$<현재 Target의 dependancy의 가장 왼쪽 항목(확장자 규칙에서만 사용)

ex) 변경 사항이 있는 대상에 대해서만 .c 파일에서 .o 파일 생성

.c.o :
	gcc -c $<

Suffix Rule

특정 확장자에 대해서 특정 command를 수행하도록 rule을 지정할 수 있습니다.

.SUFFIXES를 통하여 주의 깊게 처리할 파일 확장자를 등록할 수 있습니다.

.SUFFIXES: .c .o

아래 script는 .c파일을 이용하여 .o파일을 만들어 낼 수 있으니, .o파일을 찾을 수 없다면 아래 command를 실행해서 .c파일로부터 .o파일을 만들라는 의미입니다.

.c.o :
	gcc -c $< -o $@

그렇다면 이 네 줄을 이용하여 자동으로 .c파일을 .o파일로 변경 후 app.out 파일을 만들도록 할 수 있습니다. 하지만 역시 의존성을 해결하려면 직접 기술해야 합니다.

.SUFFIXES: .c .o

app.out: com.o lib.o main.o

.c.o:
	$(CC) -c $< -o $@

Text Function

subst

patsubst

wildcard

addsuffix

eval


Error

cc: command not found

makefile의 맨 위에 다음 한 줄을 추가합니다.

CC=gcc

makefile:2: *** missing separator. Stop.

command는 항상 tab으로 시작해야 합니다.
올바로 tab을 쳤다면 editor에서 자동으로 tab을 space로 바꾸었는지 확인합니다.

profile
Hi, my name is Eugene CHOI the Automotive MCU FW developer.

0개의 댓글