처음 Makefile을 접했을 때 친절한 설명글들을 보고도 이해하지 못했고 동료들에게 물어 물어가며 무작정 외워 작성했다.
push_swap과제에서도 가장 마지막에 공부하기로 미뤄두고
gcc push_swap.c check_error.c first_stack.c count.c find.c ft_split.c is_utils.c make_utils.c pivot.c push.c rev_rot.c rotate.c sort_few.c sort.c swap.c utils.c
이 기나긴 컴파일 명령어를 메모장에 복사해뒀다가 테스트할 때마다 붙여넣는 멍청한 짓을 계속했다😅 하지만 이제는 제대로 작성할 수 있다!
파일을 관리하는 프로그램이다. 컴파일과 수정된 파일에 대한 재컴파일을 용이하게 해준다. makefile을 작성해두면 gcc를 계속 해주지 않고 make 명령어를 통해 바로 실행파일을 만들 수 있다.
컴파일이 되는 과정을 알아야 makefile을 이해할 수 있다.
main.c 와 add.c 두 파일이 있다고 가정해보자
#include <stdio.h>
int add(int a, int b);
int main(void)
{
int a = 3;
int b = 2;
int result;
result = add(a, b);
}//main.c 파일의 내용
int add(int a, int b)
{
return (a + b);
}//add.c 파일의 내용
파일의 내용은 각각 위와 같다.
main을 실행하려면 gcc main.c add.c만 하면 된다고 생각할 것이다.
사실 gcc를 실행하면 그냥 바로 실행되는 것이 아니라
1️⃣ 컴퓨터가 알아들을 수 있는 오브젝트파일(*.o)파일로 변경 ➡️ 2️⃣ 오브젝트파일을 한데 묶어줌(link) ➡️ 3️⃣ a.out 같은 실행파일 생성
순서로 컴파일이 된다. 별도의 옵션이 없는 gcc 명령어는 위에 생성된 파일을 사용 후 실행파일만 남기고 삭제하기 때문에 깔끔하게 컴파일이 되는 것처럼 보인다.
gcc옵션
gcc -c 옵션: *.c 파일을 이용해 *.o파일을 생성
gcc -o test test.c : test.c파일로 test라는 이름의 실행파일 생성
gcc -o test test.o : test.o파일로 test라는 이름의 실행파일 생성
위 예시 코드를 편리하게 컴파일해주는 makefile을 만들어보자
test : main.o add.o
gcc -o test main.o add.o
main.o : main.c
gcc -c main.c
add .o : add.c
gcc -c add.c
위 코드들을 컴파일해 test라는 실행파일을 만들고 싶어 이 과정을 간단하게 해주는 makefile을 작성해보았다. make를 치면
gcc ~~~를 치는 과정없이 make만 쳤을뿐인데 컴파일 과정을 거쳐 실행파일 test와 *.o 오브젝트 파일들이 생성된 것을 볼 수 있다.
makefile이 어떻게 작동하는지 알았으니 이제 자세히 뜯어보자
저 코드가 어떻게 작동하는 것일까?
먼저 makefile의 구성요소로는 target, command, prerequisite(전제 조건)이 있다.
🔆 한 줄 요약
make target을 치면 target에 해당하는 command가 실행되는 것이고 command를 실행하기 위해 필요한 재료는 prerequisite에 있다.
make를 치는 순간 makefile안에 작성된 코드 위에서부터 실행이 시작된다.
위 코드에서는 test를 실행 ➡️ 하려고 봤더니 전제 조건(prerequisite)으로 main.o가 있네? 이거 먼저 실행 ➡️ 또 전제 조건으로 add.o가 있네? 이거 실행 ➡️ 전제 조건 다 실행했네? 이제 command실행
이 순서로 작동한다.
만약 순서를 바꿔서
main.o : main.c
gcc -c main.c
add .o : add.c
gcc -c add.c
test : main.o add.o
gcc -o test main.o add.o
이렇게 작성한다면?
make를 치는 순간 main.o에 해당하는 command실행
끝
서로 연관되어 이어지지 않으면 그것만 실행하고 끝나게 되는 특성을 가지고 있다.
순서에 구애받지 않고 작성하기 위해 'all'이라는 것을 사용할 수 있다.
all : test
main.o : main.c
gcc -c main.c
add .o : add.c
gcc -c add.c
test : main.o add.o
gcc -o test main.o add.o
all을 먼저 적어주면 all의 전제 조건인 test를 먼저 찾아서 실행한다. all을 이용하면 더 편안한 makefile을 만들 수 있다.
만약 *.c파일이 많으면? main.o:~~ ,add.o:~~ , check.o:~~.... 10개만 넘어가도 코드가 길어지고 복잡하게 느껴질 것이다.
아래는 내 push_swap과제의 Makefile코드 일부이다.
NAME = push_swap
CC = CC
CFLAGS = -Wall -Wextra -Werror
RM = rm -rf
SRCS = check_error.c count.c find.c first_stack.c \
ft_split.c is_utils.c make_utils.c pivot.c \
push_swap.c push.c rev_rot.c rotate.c \
sort_do.c sort_few.c sort.c swap.c utils.c
OBJS = ${SRCS:.c=.o}
#여기까지가 매크로설정부분
all : $(NAME)
%.o : %.c
$(CC) $(CFLAGS) -c $^ -o $@
$(NAME) : $(OBJS)
$(CC) $(CFLAGS) -o $(NAME) $(OBJS)
clean :
$(RM) $(OBJS)
fclean : clean
$(MAKE) clean
$(RM) $(NAME)
re : all fclean
$(MAKE) fclean
$(MAKE) all
.PHONY : all clean fclean re
위의 간단했던 makefile과 느낌이 많이 다르지만 겁먹지말자!
일단 매크로란 사용자가 정의한 변수에 특정한 것들을 정의해놓는다는 것이다.
makefile에서는 매크로는 대문자로 정의하므로
#여기까지가 매크로설정부분 이라고 쓰인 위의 모든 대문자들(CC, CFLAGS, RM...)들은 매크로라고 보면 되고, 설정된 매크로들은 $(매크로명) 형식으로 사용가능하다.
매크로를 설정해놓으면 어떤것이 좋을까?
코드의 가운데 줄을 보면 쉽게 이해할 수 있을 것이다.
$(NAME) : $(OBJS)
$(CC) $(CFLAGS) -o $(NAME) $(OBJS)
위 makefile의 가운데 위치한 이 부분을 매크로 없이 쓰면 수많은 오브젝트 파일들을 다 적어주어야 한다. 바로 아래코드처럼 말이다.
push_swap : check_error.o count.o find.o first_stack.c \
ft_split.o is_utils.o make_utils.o pivot.o \
push_swap.o push.o rev_rot.o rotate.o \
sort_do.o sort_few.o sort.o swap.o utils.o
cc -Wall -Wextra -Werror -o push_swap ft_split.o is_utils.o \
make_utils.o pivot.o push_swap.o push.o rev_rot.o rotate.o \
sort_do.o sort_few.o sort.o swap.o utils.o
놀랍게도 위의 코드와 아래의 코드는 똑같이 작동한다. 매크로를 사용하지 않으면 길게 적어야한다는 것 외에도 다른 문제가 있을 수 있다. 만약 check_error.o 의 파일이름이 check_error_error.o로 바뀐다면? 다 찾아서 고쳐줘야한다.
$(NAME) : $(OBJS)
$(CC) $(CFLAGS) -o $(NAME) $(OBJS)
이 코드는 더 짧게 아래처럼 줄여 쓸 수 있다.
$(NAME) : $(OBJS)
$(CC) $(CFLAGS) -o $@ $^
%.o : %.c
$(CC) $(CFLAGS) -c $^ -o $@
맨 마지막줄에 있는 .PHONY는 특이한 역할을 한다. target으로 작성한 이름과 같은 이름의 파일이 있을 경우 makefile이 제대로 작동하지 않을 수 있는데 .PHONY에 target의 이름을 등록해놓으면 이런 오작동을 방지한다.
makefile은 굉장히 많은 옵션과 기교를 사용할 수 있다. 위에서 설명한 makefile의 원리와 기본 작성법만 알면 여러 옵션을 응용해 자유롭게 makefile을 만들 수 있을 것이다!
makefile manual을 참조하면서 내 makefile을 완성해보자.