수성컴전자방입니다. C언어나 C++로 코딩을 하고 빌드할 때 Visual Studio를 못 쓴다면 보통 gcc나 g++를 많이 쓰실 것입니다. 그런데 파일이 여러 개로 나뉘어 있는 경우에는 명령어 하나 하나 입력해 가면서 컴파일 하고 링킹 하기가 어렵습니다. 그래서 Make라는 것을 이용합니다. 오늘을 Make 사용법을 알려드리겠습니다.
목차
1. gcc, g++, Make 설치
2. 예제 소스코드
3. makefile 작성(기초적인 방법)
4. makefile에서 변수 사용하기
5. makefile에서 wildcard 사용하기
6. make clean 구현하기(리눅스 한정)
7. 빌드 후 *.o 자동 삭제 구현하기(리눅스 한정)
8. Visual Studio Code에서 makefile 사용하기
9. 글 마무리
10. 참고 자료
1번 문단과 3~7번 문단은 Ubuntu 22.04가 설치된 가상환경에서 진행하고, 8번 문단은 Windows 11 PC에서 진행하겠습니다. 2번 문단은 완전 공용입니다. 착오 없으시길 바랍니다. 운영체제별 차이점에 대해서 따로 설명 드리겠습니다.
1. gcc, g++, Make 설치
- Ubuntu에서 gcc 설치 명령어: sudo apt install gcc
- Ubuntu에서 g++ 설치 명령어: sudo apt install g++
- Ubuntu에서 make 설치 명령어: sudo apt install make
- Windows에서 gcc, g++, make를 사용하려면 MinGW-w64 하나만 설치하시면 됩니다.(관련글(https://sooseongcom.com/post/MinGW-w64-HowToInstall))
1.1. C언어 컴파일을 하려면 gcc가 설치되어 있어야 하고, C++ 컴파일을 하려면 g++가 설치되어 있어야 합니다. 독자 여러분들도 이 정도는 이미 준비하셨겠지만, 여기서 한 번 알려드립니다.
1.2. (리눅스 사용자 한정)Make를 설치합니다.
2. 예제 소스코드
2.1. main.c
#include <stdio.h>
#include "header.h"
int main(void)
{
int a=1, b=2, add;
double ave;
point dot={1, 2};
add=sum(a, b);
printf("Sum is %d\n", add);
ressum(&a, &b, &add);
printf("Sum is %d\n", add);
ave=average(a, b);
printf("Average is %f\n", ave);
resaverage(&a, &b, &ave);
printf("Average is %f\n", ave);
dot.x++; dot.y++;
printf("점을 x방향으로 1, y방향으로 1 평행이동: (%f, %f)", dot.x, dot.y);
return 0;
}
2.2. func1.c
int sum(int a, int b)
{
return a+b;
}
void ressum(int* a, int* b, int* result)
{
*result=*a+*b;
return;
}
2.3. func2.c
double average(int a, int b)
{
return (double)(a+b)/2;
}
void resaverage(int* a, int* b, double* result)
{
*result=(double)(*a+*b)/2;
return;
}
2.4. header.h
#ifndef HEADER
#define HEADER
typedef struct _point{
double x;
double y;
} point;
int sum(int a, int b);
void ressum(int* a, int* b, int* result);
double average(int a, int b);
void resaverage(int* a, int* b, double* result);
#endif
makefile 소스코드는 3~7번 문단에서 보여드리겠습니다.
3. makefile 작성(기초적인 방법)
자, 이제 makefile 작성 방법을 차근차근 배워 봅시다.
파일명은 makefile입니다. 확장명은 따로 없습니다.
2번 문단대로 main.c, func1.c, func2.c, header.h가 있는 상황이라고 가정하고 예제 코드를 보여드리겠습니다.
run: main.o func1.o func2.o
gcc -g -o run main.o func1.o func2.o
main.o: main.c header.h
gcc -g -c main.c
func1.o: func1.c header.h
gcc -g -c func1.c
func2.o: func2.c header.h
gcc -g -c func2.c
(참고: Windows로 진행하실 경우 ‘run’ 대신 ‘run.exe’를 쓰시기 바랍니다. 리눅스에서는 보통 실행파일의 확장명을 쓰지 않습니다.)
하나하나 설명 드리겠습니다.
[Line 1]
run: main.o func1.o func2.o
가장 먼저 등장하는 이름이 run이죠? 그렇기 때문에 일단 Line 1~2를 실행합니다.
Make는 재귀적으로 동작합니다. 콜론(:) 뒤를 보시면 main.o func1.o func2.o가 있죠? 이 파일들은 각각 Line 4, 7, 10의 target입니다(target이 무엇인지는 후술합니다). 그러면 일단 Line 4부터 봅시다.
[Line 4]
main.o: main.c header.h
콜론 뒤에 main.c header.h가 있습니다. 이것은 main.c와 header.h 중에 수정된 파일이 있으면 Line 5를 실행하겠다는 뜻입니다.
[Line 5]
gcc -g -c func1.c
평범한 gcc 명령어입니다. func1.c를 컴파일(-c)하되, 디버깅이 가능한 옵션(-g)을 주었습니다.
Line 4~5와 같은 원리로 Line 7~8, Line 10~11도 작동합니다. 이 모든 것이 진행된 다음 Line 1~2를 다시 보겠습니다. Line 1에서 콜론 뒤에 있는 항목들을 다 확인했으니 Line 2로 갑니다.
[Line 2]
gcc -g -o run main.o func1.o func2.o
역시 다중 파일 컴파일을 해 보셨다면 평범하게 느끼실 명령어입니다. main.o, func1.o, func2.o를 링크하여 run이라는 실행파일을 만듭니다.
이제 make를 실행해 보겠습니다.
왼쪽과 같이 파일들이 있는 상황입니다. 이 파일들이 있는 경로로 터미널을 실행한 뒤 make 명령을 실행합니다.
- Linux에서: make
- Windows에서: mingw32-make
운영체제 별로 명령어 차이가 있으니 주의하시기 바랍니다. 이 글에서 따로 언급 안 해도 Linux에서는 make이고, Windows에서는 mingw32-make입니다.
컴파일과 링킹이 모두 완료되어 run 파일이 생성된 것을 보실 수 있습니다. 터미널에서 ./run (Windows에서는 .\run.exe)을 입력하여 실행하실 수 있습니다.
소스 코드 수정 없이 make를 한 번 더 실행하면 빌드가 진행되지 않습니다. 수정 사항이 있어야 빌드가 진행됩니다.
★★★makefile의 기초적인 작성 방법은 아래와 같다고 보시면 됩니다.★★★
<Target>: <prerequisites>
<Recipe>
이러한 것을 ‘Rule’이라고 합니다.
- target: 빌드 대상 이름입니다. 보통 이 rule에서 생성되는 파일의 이름(과 확장명)으로 합니다.
- prerequisite: target을 만들기 위해 input으로 사용되는 파일입니다.
- recipe: 이 rule이 작동할 때 실행될 명령어들입니다. 위의 예제에서는 한 줄씩만 썼지만, 사실 여러 줄에 걸쳐서 여러 명령어들을 쓸 수 있습니다.
이번 문단에서는 이 정도를 알아 가시면 됩니다.
4. makefile에서 변수 사용하기
이제 본격적으로 make의 기능을 활용해 봅니다. 변수를 정의해서 써 보겠습니다. 일단 예제 코드 드립니다.
CC=gcc
OBJS=main.o func1.o func2.o
FLAG=-g
EXEC=run
$(EXEC): $(OBJS)
$(CC) $(FLAG) -o $@ $(OBJS)
main.o: main.c header.h
$(CC) $(FLAG) -c main.c
func1.o: func1.c header.h
$(CC) $(FLAG) -c func1.c
func2.o: func2.c header.h
$(CC) $(FLAG) -c func2.c
이 코드는 3번 문단의 코드와 같은 기능을 하는 코드입니다. 차이점은 변수를 사용했다는 것뿐입니다. Line 1~4가 변수 지정 부분이고, Line 6부터 make가 실행된다고 보시면 됩니다.
[Line 1~4 변수 지정]
변수 정의는 평범하게 변수명 = 내용 으로 하시면 됩니다.
[Line 6~16]
변수는 $로 시작합니다. 우리가 직접 정의한 변수를 사용하려면 $(변수명)으로 사용합니다. 예를 들어 예제에서 $(EXEC)을 쓴 것은 run을 쓴 것과 같습니다.
우리가 정의한 것 외에 이미 정의되어 있는 ‘자동 변수(Automatic Variable)’가 있습니다. 몇 가지만 알아보겠습니다.
- $@: 해당 rule의 target
- 예를 들어 예제의 Line 7에 있는 $@는 $(EXEC)을 의미하고, 이는 곧 Line 4에 정의한 대로 run을 의미합니다.
- $<: 해당 rule의 첫 번째 prerequisite
- $^: 해당 rule의 prerequisites 전체
- $?: 해당 rule의 prerequisites 전체 중 변경된 것들
make를 실행한 모습입니다.
5. makefile에서 wildcard 사용하기
파일이 여러 개일 때 그 파일명을 모두 makefile에 작성하기 귀찮습니다. 이것을 wildcard로 해결할 수 있습니다. 이름은 거창해 보이지만 사실 *입니다.
CC=gcc
EXEC=run
FLAG=-g
$(EXEC): *.o
$(CC) $(FLAG) -o $@ *.o
*.o: *.c *.h
$(CC) $(FLAG) -c *.c
[Line 5]
$(EXEC): *.o
main.o func1.o func2.o를 다 쓰지 않고 *.o로 표현했습니다. *.o는 Line 8의 target이므로 Line 8을 보겠습니다.
[Line 8]
*.o: *.c *.h
이 rule 역시 소스코드들을 열거하지 않고 *.c *.h로 간단하게 표현했습니다. 그러므로 makefile이 있는 폴더 내에 있는 c code나 header file 중 하나라도 수정된 것이 있으면 이 rule을 실행합니다.
[Line 9]
$(CC) $(FLAG) -c *.c
이 부분은 실행했을 때 gcc -g -c *.c
로 동작합니다.
[Line 6]
$(CC) $(FLAG) -o $@ *.o
이 부분은 실행했을 때 gcc -g -o run *.o
로 동작합니다.
make를 실행한 모습입니다.
6. make clean 구현하기(리눅스 한정)
빌드한 과정에서 생성된 object code들(*.o)과 결과물(run)을 삭제하는 기능을 구현해 보겠습니다. 리눅스 명령어 rm을 사용합니다.
CC=gcc
EXEC=run
FLAG=-g
$(EXEC): *.o
$(CC) $(FLAG) -o $@ *.o
*.o: *.c *.h
$(CC) $(FLAG) -c *.c
clean:
rm *.o
rm $(EXEC)
5번 문단의 소스 코드에서 Line 11~13만 추가되었습니다.
make 명령을 실행하면 맨 처음 target인 $(EXEC), 즉 run부터 보게 됩니다. 이는 *.o를 호출합니다. 그런데 *.o는 clean을 호출하지 않습니다. 그러므로 clean이 실행되지 않습니다! 그러면 어떻게 clean을 사용할 수 있을까요?
바로 make 명령어를 입력할 때 make clean으로 실행하는 것입니다!!
make clean을 실행하기 전입니다.
make clean을 실행한 모습입니다. object code들과 실행파일이 모두 삭제되었습니다.
사실 Windows Powershell도 rm 명령어가 있긴 한데 Windows Powershell은 rm 명령어에 *을 사용할 수가 없어서 이 문단을 적용할 수 없습니다.
7. 빌드 후 *.o 자동 삭제 구현하기(리눅스 한정)
빌드하면 실행파일만 생성되도록 해 보겠습니다. 구현 방법은 빌드 후에 object code를 모두 삭제하는 것입니다. 그런데 이 기능을 쓰면 보기에는 예쁠지 몰라도 코딩 과정에서 make를 실행할 때마다 처음부터 컴파일 해야 하므로 추천하지는 않습니다.(본인 상황에 맞게 사용하세요.)
CC=gcc
EXEC=run
FLAG=-g
$(EXEC): *.o
$(CC) $(FLAG) -o $@ *.o
rm *.o
*.o: *.c *.h
$(CC) $(FLAG) -c *.c
[Line 5]
$(EXEC): *.o
콜론 뒤의 *.o를 호출합니다.
[Line 9]
*.o: *.c *.h
콜론 뒤의 *.c *.h를 호출합니다. makefile에 *.c, *.h가 target인 rule이 없으므로 폴더 내에서 *.c, *.h 중 수정된 파일이 있으면 Line 10을 실행합니다.
[Line 10]
$(CC) $(FLAG) -c *.c
이 부분은 실행했을 때 gcc -g -c *.c
로 동작합니다.
[Line 6]
$(CC) $(FLAG) -o $@ *.o
이 부분은 실행했을 때 gcc -g -o run *.o
로 동작합니다.
[Line 7]
rm *.o
이 명령어가 그대로 실행됩니다. 해당 폴더 내에서 확장명이 .o인 파일이 모두 삭제됩니다.
make 실행 전 모습입니다.
make를 실행한 모습입니다. 실행파일만 생겼습니다.
8. Visual Studio Code에서 makefile 사용하기
8.1. 소스코드가 있는 폴더에 makefile을 넣습니다.
8.2. C code를 연 상태에서 F5를 누릅니다.
8.3. C++ (GDB/LLDB)를 클릭합니다.
8.4. C/C++: gcc.exe 활성 파일 빌드 및 디버그를 클릭합니다.
8.5. 오류가 발생합니다. 중단을 클릭합니다.
8.6. .vscode 폴더 안에 tasks.json이 생겼습니다. 이 파일을 수정합니다.
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: gcc.exe 활성 파일 빌드",
"command": "mingw32-make", //Linux는 "make"
"args": [
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "디버거에서 생성된 작업입니다."
}
],
"version": "2.0.0"
}
Linux에서 사용하는 경우 Line 6에 “mingw32-make” 대신 “make”를 쓰시기 바랍니다.
8.7. .vscode 폴더 안에 아래의 내용으로 launch.json을 만듭니다.
{
// IntelliSense를 사용하여 가능한 특성에 대해 알아보세요.
// 기존 특성에 대한 설명을 보려면 가리킵니다.
// 자세한 내용을 보려면 https://go.microsoft.com/fwlink/?linkid=830387을(를) 방문하세요.
"version": "0.2.0",
"configurations": [
{
"name": "gcc.exe - 활성 파일 빌드 및 디버그",
"type": "cppdbg",
"request": "launch",
"program": "${fileDirname}\\run.exe", //Linux는 "${fileDirname}/run"
"args": [],
"stopAtEntry": false,
"cwd": "${fileDirname}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"miDebuggerPath": "C:\\Program Files\\mingw64\\bin\\gdb.exe",
"setupCommands": [
{
"description": "gdb에 자동 서식 지정 사용",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
],
"preLaunchTask": "C/C++: gcc.exe 활성 파일 빌드"
}
]
}
Linux는 Line 11에서 run.exe 대신 run을, “\\” 대신 “/”을 씁니다.
8.8. F5를 누르면 빌드가 잘 진행됩니다.
(출력 결과가 안 보인다면 터미널 탭을 클릭하세요.)
VS Code에서 Breakpoint를 설정했다면 디버깅도 가능합니다.
9. 글 마무리
제 글을 읽어 주셔서 감사합니다. 다음에 만나요!
10. 참고 자료
1) 이승준. 2018. “[Make 튜토리얼] Makefil 예제와 작성 방법 및 기본 패턴”, TUWLAB. (2023. 01. 06. 방문). https://www.tuwlab.com/ece/27193
2) 슬기야놀자. 2021. “[VSCode / C / C++] VSCode에서 Makefile 사용하기”, 슬기야 코딩하자. (2023. 01. 06. 방문). https://urakasumi.tistory.com/281
3) GNU. n. d. “GNU make 4.4, 10.5.3, 10.5.4”, GNU make 문서. (2023. 01. 06. 방문). https://www.gnu.org/software/make/manual/html_node/index.html#SEC_Contents
4) Herdsman 외 1명. 2020. “why my makefile works with asterisk as wildcard, but do not with percentage?”, stackoverflow. (2023. 01. 06. 방문). https://stackoverflow.com/questions/59804167/why-my-makefile-works-with-asterisk-as-wildcard-but-do-not-with-percentage
5) 윤정도. 2022. “[Linux/Makefile] 2. 패턴 규칙”, 무과금 액티브. (2023. 01. 06. 방문). https://blog.naver.com/reversing_joa/222840966671