Search
Duplicate

Makefile을 만들기 위한 A to Z

간단소개
makefile을 처음 작성하기까지 공부한 내용들을 정리했습니다.
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
Makefile
Scrap
태그
9 more properties

1. 프로그램의 실행 순서

왼쪽에서 오른쪽으로 생성이 된다.
1.
2.
3.
4.
5.
생성하는 파일이름
소스파일
목적파일
실행파일
파일 확장자
.c
.i
.S
.O
.exe
이 파일을 만들기 위해 gcc에 달리는 옵션
-E
-c
-o
다음단계로 가기 위한 단계
'전처리기'가 #으로 시작되는 부분들을 치환한다.(헤더파일 삽입, 실행문장 매크로 상수 변환)
'컴파일'을 통해서 .i 파일을 어셈블리어로 된 .s 파일로 만든다. 실제로 만들어지는게 보이지는 않는다.
'어셈블러'가 기계가 이해 가능한 언어 (= 기계어) 로 변경한다.
'링킹'이 이루어진다. 이과정에서 파일간의 연결이 이루어지고, 라이브러리 함수도 연결된다.

(1) 소스파일 → .i 파일 [전처리하기]

한줄 요약 : 전처리까지 진행된다. 사용 옵션 : -E
$ gcc -E file.c $ gcc -E file.c -o file.i
Bash
복사
-o 까지 쓴다면, 전처리까지 진행한결과가 디스크에 저장도 된다.

(2) .i 파일 → .s 파일 [컴파일하기]

한줄요약 : 어셈블리어로 된 파일을 생성한다.

(3) .s 파일 → .o 파일 [어셈블하기]

한줄요약 : 기계가 이해 가능한 언어로 변경한다.
사용옵션 : -c
⇒ 이 옵션을 사용하면, 전처리(.c → .i) 후에 컴파일(.i → .s) 후에 어셈블(.s→ .o) 까지 진행한다.
$ gcc -c file.c
Bash
복사
⇒ c 파일에 대해서 .o 파일이 각각 생성된다, (c와 똑같은 이름으로)

(4) .o 파일 → .exe 파일[링킹하기]

$ gcc file.o
Bash
복사
⇒ 생성된 .o파일에 대해서 실행파일을 생성할 수 있다. (이름은 a.out 로 고정된다)
$ gcc file.o -o file
Bash
복사
⇒ 생성된 .o 파일에 대해서 실행파일을 생성할 수 있다. (이름은 file로 된다.)

주의!!

-c 과정을 생략하게 되면, .o파일이 없이 -o를 하는 것이 제대로 작동하지 않는다. **-c 이후에 -o로 이름 지정하기!

외부 헤더파일 포함시키기

한줄요약 : 만약 표준 디렉토리 아닌 위치에 있는 헤더파일을 가진다면 디렉토리 위치 지정
사용하는 태그 : -I
$ gcc *.c -I 헤더파일이 위치한 상위폴더
Bash
복사

2. 라이브러리의 생성과 확인

라이브러리는 '자주 사용되는 유용한 함수에 대한 오브젝트 파일과 함수의 인덱스'의 모음을 말한다.

A. 만들기

1. .a 파일 만들기 (r)

$ ar r lib.a *.o
Bash
복사
⇒ .o 파일을 가지고 lib.a 파일을 만든다 (새로운 오브젝트 파일이면 추가, 기존 파일이면 치환)
(.o 파일이 있어야 하므로, 당연히 gcc -c *.c 로 오브젝트 파일이 만들어져야 된다.)

2. .a 파일 안에 함수의 인덱스를 추가하기 (s)

$ ar s lib.a
Bash
복사
⇒ lib.a 에 대해서 함수의 인덱스를 추가한다.
= ranlib libmy.a 로도 생성이 가능하다.

B. 라이브러리 파일 확인하기(t)

$ ar t lib.a
Bash
복사
⇒ .a 파일의 내부 오브젝트 파일을 표시한다.

C. 다른옵션들이랑 같이 사용되는 옵션들

라이브러리의 상세 옵션 추가적으로 표시 (v)

파일크기와 갱신시각등의 상세 정보가 출력된다.
$ ar tv lib.a $ ar xv lib.a $ ar rv lib.a
Bash
복사
→ 보통 같이 쓰인다.(순서대로 자세히 내용조회, 파일추출하고 내용 자세히 보여주기, 생성해주고 내용보여주기)

새로운 라이브러리를 만들때 주의 표시를 표시하지 않도록 하기(c)

→ 그래서 보통 같이 쓰인다.
$ ar rc lib.a
Bash
복사

기존의 아카이브 내용보다 새로운 것들만 대체해준다(u)

$ ar u lib.a
Bash
복사

ar 옵션들 정리하기

1. 아카이브의 정보를 조회하기

$ ar tv lib.a
Bash
복사
lib.a 에 있는 오브젝트 파일들을 확인하되, 자세히 내용들을 확인한다.

2. 특정 오브젝트 파일 제거하기

$ ar d lib.a this.o
Bash
복사
lib.a에 있는 'this.o'라는 오브젝트 파일을 제거한다.

3. 새로운 오브젝트 파일을 추가해서 넣어준다.

$ ar rcus lib.a a.o b.o c.o
Bash
복사
r(만들되), c(만드는 warning 표시를 무시하고), u(기존의 것은 놔두고 대체만 하며), s(인덱스를 추가해서 빠르게 접근할 수 있도록 한다.)

4. 아카이브의 내용물을 모두 분리하기

ar xv lib.a
Bash
복사
아카이브에 들어있는파일들을 extract(x) 하되, verbose(v)로 자세히 내용을 본다.

3. makefile의 규칙

(1) 구성

<Target>: <Depend> ?... [[;] <Command>] <탭문자><Command>
Bash
복사
→ 이와 같은 순서로 작성된다.
Target : 생성하고자 하는 목적물
Depend : Target을 만들기 위해서 필요한 요소
Command : 일반 Shell 명령어 (앞에 꼭 Tab 해서 써야함)
Command 는 Depend의 파일 생성/변경 시간과 Target의 생성/변경시간과 비교해서 Target이 더 최근일 경우에만 실행됨(예외 존재)

예시

Makefile
test: test.o ar rc test.a test.o test.o: test.c cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c
Bash
복사
make test 를 입력하게 되면 다음과 같이 출력이 되며,
1.
test.o에 써져 있던 것이 먼저,
2.
그다음으로 test에 해당하는 내용이 실행된다.
$ make test <출력 결과> cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c ar rc test.a test.o
Bash
복사
만약 다시 make test를 입력한다면 이미 갱신되었다는 말과 함께 실행되지 않는다.
make test <출력 결과> make: `test'는 이미 갱신되었습니다.
Bash
복사
⇒ 이것은 Depend의 파일생성 시간과 Target의 파일 생성시간을 비교했을 때,
test.a(Target)의 파일 생성시간이 test.o(depend)의 파일 생성시간보다 더 이후에 만들어졌기 때문이다.
⇒ 이경우, touch로 간단히 test.c의 변경날짜가 변경된다면 make test 가 진행된다.
touch로 생성시간 변경후 진행
make test <출력 결과> cc -O2 -Wall -Werror -fomit-frame-pointer -c -o test.o test.c ar rc test.a test.o
Bash
복사

(2) 가짜 target과 PHONY

dependency(prerequisite)가 없는 상태의 target과 command만 작성할수도 있다.

clean

가장 대표적으로 사용되는 것은 'clean'이라는 매크로 이며, 통상적으로 target 파일과 목적 파일을 모두 삭제하는 역할을 해준다.
clean: rm -rf $(OBJS) $(TARGET)
Bash
복사
→ 이때 만약 clean 이라는 이름의 실행파일이 존재한다면, 원하지 않는 결과가 나올수도 있으므로
clean은 가짜 target임을 명시해주는 것이 좋다.

all

all이라는 타겟은 보통 실제로 all이라는 파일을 생성하는 것이 아니고 단지 무엇이 최종 'Target'인지 알려주는 역할을 한다.
all: lib.a
Bash
복사
make all을 하는 경우, all에 해당하는 depend인 lib.a가 해당되는 모든 명령이 이루어질 수 있다.

.PHONY

.PHONY 라는 타겟은 새로운 타겟을 의미하며, make 구문에서 예약된 target 중 하나로,
여기에 명시된 Depend는 모두 가짜 Target으로 보게 한다.
.PHONY : all clean
Bash
복사

(3) 가장 먼저 나오는 target이 make 시에 실행된다.

make를 입력할때, .으로 시작하지 않는 target중 가장 상단에 있는 target이 실행된다.
CC = cc LD = ld RM = rm -f CFLAGS = -O2 -Wall -Werror -fomit-frame-pointer -c LDFLAGS = -lc -m elf_i386 -dynamic-linker /lib/ld-linux.so.2 STARTUP = /usr/lib/crt1.o /usr/lib/crti.o /usr/lib/crtn.o .PHONY: all clean all: test clean: $(RM) test.o test test: test.o $(LD) $(LDFLAGS) -o $@ $(STARTUP) $^ .c.o: $(CC) $(CFLAGS) -o $@ $<
Bash
복사
출처 : https://www.joinc.co.kr/w/Site/C/Documents/minzkn_make
이와 같은 makefile이 있는 경우에는 make 를 입력할 때,
.c로 시작하는 target을 제외한 target중 가장 상위에 있는 'all'이 실행된다.

(4) 내부 매크로

내부 매크로는 매크로를 연산 처리하는데 사용되는 매크로를 말한다.
$@ : 목표 이름
$* : 목표 이름에서 확장자가 없는 이름
$< : 첫 번째 depend의 파일 이름
$? : 목표 파일 보다 더 최근에 갱신된 파일 이름
$^: 현재 Target이 의존하는 대상들의 전체 목록
$?: 현재 Target이 의존하는 대상들 중 변경된 것들의 목록
$% : 대상의 이름 (해당 규칙 대상이 아카이브 인 경우)
→ 보통 로 된 매크로를 많이 사용하는듯 하다.
예시
foo.o: foo.cc foo.h $(CC) $(CXXFLAGS) -c $<
Makefile
복사
→ 이경우 첫번째 dependency 인 foo.cc가 $< 자리에 간다고 생각하면 된다.
예시 - 라이브러리를 만들때
NAME = every.a CC = gcc CFLAGS = -Wall -Wextra -Werror SRCS = a.c b.c OBJ = $(SRCS:.c=.o) .c.o: $(SRCS) $(CC) $(CFLAGS) -c $< all: $(NAME) $(NAME): $(OBJ) ar rsc $(NAME) $(OBJ) clean: rm -f $(OBJ) fclean: clean rm -f $(NAME) re: fclean all .PHONY: all clean fclean re
Makefile
복사
TARGET : 실행파일 이름
CC : 컴파일러 지정
CFLAGS : 컴파일러의 플래그 지정
make : Makefile 실행 → 첫번째 target인 make all이 실행됨
make clean : 오브젝 파일 삭제
make fclean : 오브젝 파일과 실행 파일 삭제
make re : make fclean 실행 후 다시 make

4. make 시 나타났던 에러

commands commence before first target. Stop
이유 : \ 를 사용하고 줄 바꿈을 진행했을 때 \뒤에 띄어쓰기가 있어서 제대로 인식되지 않았다.
해결 : \ 하고 바로 줄바꿈이 이루어지도록 변경했다.

참고한 자료들