상세 컨텐츠

본문 제목

Makefile 만드는 방법

잡글/정보글

by deulee 2023. 8. 14. 09:55

본문

Makefile에 대해서 알아보기 전에 프로그램의 빌드가 어떻게 이루어지는지 먼저 알아보도록 하자.

1. 프로그램의 빌드 과정

애초에 소스코드는 인간이 이해하고 작성하기 위해서 만들어진 언어로 작성된거지 컴퓨터가 이를 알리가 없다. 그래서 빌드 과정을 통해 컴퓨터에서 이해할 수 있는 기계어로 해석하고 실행 가능한 파일로 만드는 과정을 빌드(Build)라고 한다.

 

source -> build -> exe 식으로 말이다.

 

C와 C++이나 대부분의 언어는 다음과 같은 빌드 과정을 가진다.

 

프리프로세싱 -> 컴파일링 -> 어셈블링 -> 링킹

  1. 프리프로세싱 (Preprocessing): 소스 코드 파일을 컴파일하기 전에 전처리 단계가 진행된다. 전처리기는 '#include' 문을 처리하여 헤더 파일을 포함하고, 매크로를 확장하며, 조건부 컴파일 지시문을 처리한다. 결과적으로, 전처리된 소스 코드가 생성된다.

    'clang++' 컴파일러를 사용하여 전처리된 소스 코드를 확인하기 위해서는 '-E' 옵션을 사용하면 된다.

    예시) 'clang++ -E input.cpp -o preprocessed_output.cpp'

    결과로 나오는 페이지는 한번 확인해보는 것도 괜찮을것 같다.

  2. 컴파일링 (Compiling): 전처리된 소스 코드를 컴파일러에게 전달하여 '저수준 언어'로 변환하는 단계이다. 이때 문법 오류를 체크하고 '중간 어셈블리 코드''목적 코드'를 생성한다. 이 단계에서 발생하는 오류를 컴파일 에러라고 한다.
    • 목적 코드 작성법:

      'clang++' 컴파일러를 사용하여 목적 코드를 확인하기 위해서는 '-c' 옵션을 사용하면 된다.

      예시) 'clang++ -c input.cpp -o output.o'

    • 중간 어셈블리 코드 생성 방법:

      'clang++' 컴파일러를 사용하여 중간 어셈블리 코드를 생성하기 위해서는 '-S' 옵션을 사용하면 된다.

      예시) 'clang++ -S input.cpp -o output.s'
  3. 어셈블링 (Assembling): 어셈블러는 컴파일된 중간 어셈블리 코드를 기계어로 번역한다. 중간 어셈블리 코드는 해당 아키텍처의 명렁어 집합으로 변환된다.
    • 사실 어셈블링 또한 기계어로 변환하는 과정을 거치기 때문에 '컴파일 단계'라고 뭉퉁그려서 말할 수 있다. 여기에서 목적 코드를 만드는 과정이 포함된다.
  4. 링킹 (Linking): 프로그램이 여러 개의 소스 파일로 구성되어 있거나, 외부 라이브러리를 사용할 경우, 링커가 이들을 하나로 묶어 실행 가능한 '바이너리 파일'을 생성한다. 이 단계에서 위에서 만들어진 '목적 코드'들을 한데로 모은다라고 생각하면 된다.

이렇게 간단하게 프로그램의 빌드 과정에 대해 설명했는데 나중에 한번 자세하게 다루는 내용을 작성해보도록 하겠다.

 


2. Makefile 만들기

'make'는 파일을 관리해주는 유틸리티로, 수정된 소스파일의 'recompile'을 용이하게 해주는 기능이다.

 

이말은 즉, 반복되는 컴파일 속에서 수정된 파일과 의존성(Dependency)이 있는 대상들만 추려서 컴파일을 다시 진행하는 것을 의미한다.

 

다음은 Makefile의 구조다.

<target> : <dependency>
(tab)<Recipe>
  • target : 빌드 대상 이름, 명령에 의해 생성되는 결과 파일을 의미함
  • dependency : 타겟을 만들 때 사용되는 파일들. 여기에 나열된 타겟들을 먼저 생성한다.
  • Recipe : 빌드 대상을 생성하는 명령으로 Shell에서 사용하는 명령어들을 쓸 수 있다.

 

예시 Makefile을 먼저 보도록 하자.

NAME = phonebook

CC = clang++

RM = rm -rf

WFLAGS = -Wall -Wextra -Werror

CFLAGS = $(WFLAGS)

SRCS = personinfo.cpp \
		phonebook.cpp \
		main.cpp

OBJS = ${SRCS:.cpp=.o}

${NAME}: ${OBJS}
	$(CC) $(CFLAGS) $(OBJS) -o $(NAME)

all: ${NAME}

clean:
	${RM} ${OBJS}

fclean: clean
	${RM} ${NAME}

re: fclean all

.PHONY: all clean fclean re

위의 Makefile은 단순 예시 중 하나이며 하나하나 설명해보도록 하겠다.

 

우선 'NAME', 'CC', 'RM' 그리고 'WFLAGS' 등은 '변수'를 선언한 것이다. Makefile은 이렇듯 변수를 사용할 수 있고 확장성을 용이하게 해 주는 '자동 변수' ($@, $< 등)도 있다.

 

https://www.gnu.org/software/make/manual/html_node/Automatic-Variables.html

 

Automatic Variables (GNU make)

10.5.3 Automatic Variables Suppose you are writing a pattern rule to compile a ‘.c’ file into a ‘.o’ file: how do you write the ‘cc’ command so that it operates on the right source file name? You cannot write the name in the recipe, because the

www.gnu.org

 

그리고 변수들을 이용하여 'target''dependency' 그리고 'recipe'에 맞게 작성하고 bash에서 다음과 같은 명령을 사용하면 완성이다.

make
make all
make clean
make fclean
make re

이 외에도 본인의 편의상 마음대로 작성할 수 있다.