Language/C

📌 Chapter 17 - 선행처리기와 다중 소스 파일 - Makefile

e-cko 2025. 6. 24. 14:49
반응형
반응형

6. Makefile을 활용한 자동 컴파일 시스템

  • Makefile은 여러 개의 소스 파일로 구성된 C 프로젝트를 컴파일할 때, 컴파일 과정을 자동화하고 효율적으로 관리하기 위한 설정 파일이다.
  • 수많은 소스 파일과 헤더 파일이 있을 때, 어떤 파일이 변경되었는지 검사해서 변경된 부분만 다시 컴파일한다.
  • 따라서 전체 프로젝트를 다시 컴파일하지 않아도 되어, 시간을 크게 절약할 수 있다.
  • make라는 명령어를 사용해 Makefile의 규칙을 따라 작업한다.
  • 대규모 프로젝트에서 필수적으로 사용하는 도구이며, 유지보수와 협업에 매우 유리하다.
  • 프로젝트 소스가 많아지면, 모든 소스 파일을 매번 컴파일하는 것은 매우 비효율적이다.
  • Makefile은 파일의 최종 수정 시간을 비교하여, 변경된 파일과 그 의존성만 컴파일한다.
  • 이를 통해 컴파일 시간을 단축하고, 개발 효율을 높인다.
  • , 복잡한 컴파일 옵션과 명령어를 매번 터미널에 입력할 필요 없이 Makefile에 적어두고 실행하면 되므로 편리하다.

1) Makefile의 기본 구조와 용어 이해

  • Makefile은 타겟(target), 의존성(dependencies), 명령어(commands) 3가지 요소로 구성된다.

타겟: 의존성1 의존성2 ...

           명령어

  • 타겟(target)
    ·
    생성하려는 파일이나 결과물의 이름이다. 보통 컴파일 결과인 실행 파일 또는 오브젝트 파일(.o)이다.
  • 의존성(dependencies)
    ·
    타겟을 만들기 위해 필요한 파일들이다. 소스 파일(.c), 헤더 파일(.h), 혹은 다른 오브젝트 파일 등이 포함된다.
  • 명령어(commands)
    ·
    타겟을 생성하기 위해 실행할 쉘 명령어이다.
    ·
    매우 중요한 점: 명령어 앞에는 반드시 탭(Tab) 문자가 있어야 한다! 스페이스(공백)가 아닌 탭을 사용해야 한다.

2) 기본 예제

makefile

hello: hello.o

           gcc -o hello hello.o

 

hello.o: hello.c

           gcc -c hello.c

  • Makefile hello라는 실행 파일을 만들기 위한 규칙이다.
  • hello 타겟은 hello.o에 의존한다.
  • hello.o hello.c에 의존한다.
  • 명령어를 통해 .c 파일을 .o 파일로 컴파일하고, .o 파일을 연결하여 실행 파일을 만든다.

3) 변수 사용과 재사용

Makefile 내에 변수를 사용하면, 컴파일러 종류나 옵션 변경 시 유지보수가 쉽다.

makefile

CC = gcc

CFLAGS = -Wall -g

OBJ = main.o calc.o

 

main: $(OBJ)

           $(CC) $(CFLAGS) -o main $(OBJ)

 

main.o: main.c calc.h

           $(CC) $(CFLAGS) -c main.c

 

calc.o: calc.c calc.h

           $(CC) $(CFLAGS) -c calc.c

 

clean:

           rm -f *.o main

  • CC 변수는 컴파일러 이름, 기본적으로 gcc를 지정한다.
  • CFLAGS는 컴파일 옵션이다. -Wall은 모든 경고를 켜고, -g는 디버깅 정보를 포함시킨다.
  • OBJ 변수에는 컴파일할 오브젝트 파일 목록이 들어간다.
  • 변수는 $()로 불러 쓴다.
  • clean은 프로젝트 빌드 결과물을 삭제하는 규칙이다.

4) Makefile 사용 방법

  • 터미널에서 프로젝트 디렉터리로 이동 후,

make

명령어를 입력하면, Makefile에 정의된 규칙에 따라 필요한 컴파일 작업이 자동으로 수행된다.

  • 모든 오브젝트 파일과 실행 파일을 삭제할 때는,

 

make clean

명령어를 입력하면 된다.


5) 상세 동작 과정

1️ make는 먼저 가장 위에 나온 타겟(보통 실행 파일)을 찾는다.
2️
그 타겟에 의존하는 파일들을 검사해 최신 상태인지 확인한다.
3️
변경된 파일이 있으면, 해당 타겟을 만드는 명령어를 실행한다.
4️
의존성도 재귀적으로 검사해 최신 상태가 아닐 경우 그에 맞는 컴파일 작업을 한다.


6) 자주하는 실수와 주의사항 ⚠️

  • 명령어 앞에 탭 문자 대신 스페이스(공백)를 넣으면 에러가 발생한다. 반드시 탭 사용!
  • 헤더 파일이 바뀌었을 때, 관련된 .o 파일을 재컴파일하도록 의존성을 정확하게 지정해야 한다.
  • 변수 이름과 규칙 이름에 띄어쓰기나 특수문자를 넣으면 안 된다.
  • 큰 프로젝트일수록 의존성 관리가 까다로우므로, 자동화 도구나 IDE의 도움을 받을 수도 있다.

7) 실제 프로젝트 예시

프로젝트 구조

project/

├── main.c

├── calc.c

├── calc.h

└── Makefile

  • main.c는 프로그램 시작점이다.
  • calc.c, calc.h는 계산 기능을 구현한 모듈이다.
  • Makefile은 위 예시와 같이 작성한다.

💻 예제 코드

// main.c

#include <stdio.h>

#include "calc.h"

 

int main() {

    int a = 5, b = 3;

    printf("Sum: %d\n", add(a, b));

    printf("Difference: %d\n", subtract(a, b));

    return 0;

}

 

// calc.h

#ifndef CALC_H

#define CALC_H

 

int add(int x, int y);

int subtract(int x, int y);

 

#endif

 

// calc.c

#include "calc.h"

int add(int x, int y) {

    return x + y;

}

int subtract(int x, int y) {

    return x - y;

}

 

# Makefile

CC = gcc

CFLAGS = -Wall -g

OBJ = main.o calc.o

main: $(OBJ)

           $(CC) $(CFLAGS) -o main $(OBJ)

main.o: main.c calc.h

           $(CC) $(CFLAGS) -c main.c

calc.o: calc.c calc.h

           $(CC) $(CFLAGS) -c calc.c

clean:

           rm -f *.o main

 

🖨️ 실행 결과

$ make

gcc -Wall -g -c main.c

gcc -Wall -g -c calc.c

gcc -Wall -g -o main main.o calc.o

 

$ ./main

Sum: 8

Difference: 2

 

$ make clean

rm -f *.o main


마무리 정리
1️
Makefile C 프로젝트의 컴파일 과정을 자동화하는 필수 도구이다.
2️
타겟, 의존성, 명령어로 구성되며, 명령어는 탭 문자로 시작해야 한다.
3️
변수 설정으로 컴파일러, 옵션, 파일 목록을 관리하면 유지보수가 편리하다.
4️
변경된 파일만 다시 컴파일하여 빌드 시간을 절약한다.
5️
make clean으로 빌드 파일을 손쉽게 정리할 수 있다.

 

반응형