[C] C #include

3 minute read

Published:

In this post, basic concepts of using #include in C is introduced.

#include

C에는 Java처럼 package 개념이 없다. 대신 heder file과 preprocessor description을 쓴다. (Java의 import는 namespace 단위로 클래스를 참조한다.)

// main.c file
#include <stdio.h>
#include "myheader.h"

int main(String[] args){
  int i = myfunc(3, 4);
  ...
}

C의 #include는 해당 파일의 텍스트를 현재 소스코드에 붙여넣는 전처리 동작이다.

  • 처럼 `<>` 로 include된 파일의 경우, 시스템 include 경로나, 시스템 라이브러리 경로에서 헤더 파일을 찾는다. (보통 `/usr/include` 또는 컴파일러 기본 include path)
  • “myheader.h” 처럼 "" 로 include된 파일의 경우, 현재 소스 코드가 위치한 디렉토리부터 탐색, 없으면 시스템 include 경로에서도 찾는다.

headef file 에는 (일반적으로) 함수의 선언이 들어있다.

// myheader.h file
int myfunc(int, int);

그러면 preprocessor 실행후, main.c는 아래와 같은 모습일 것이다.

// main.c file
#include <stdio.h> // 대체 과정 생략
int myfunc(int, int); // by preprocessor

int main(String[] args){
  int i = myfunc(3, 4);
  ...
}

main.c를 컴파일하면 오브젝트 파일(.o)에는 myfunc라는 함수 호출 symbol 정보만 있다. 그리고 링커가 myfunc.c를 컴파일한 myfunc.o 안에서 함수 구현을 찾아 연결한다. 아래 gcc 명령어를 수행하면 해당 과정이 자동으로 단계적으로 이루어지고 최종적으로 실행파일 main이 생긴다.

gcc main.c myfunc.c -o main

include guard

일반적으로 headerfile는 위처럼 간단하게 작성하지 않고 몇 가지 규칙을 따라 아래와 같이 작성한다.

// myheader.h
#ifndef MYHEADER_H     // Include Guard 시작
#define MYHEADER_H

// 필요한 #include
#include <stdio.h>

// 매크로 정의
#define PI 3.1415926535

// 타입 정의
typedef struct {
    double x;
    double y;
} Point;

// 함수 원형(선언)
double add(double a, double b);
double distance(Point p1, Point p2);

#endif  // MYHEADER_H
  1. Include Guard (#ifndef ... #define ... #endif)

    • 동일한 헤더가 여러 번 include되어도 중복 선언을 방지한다.

    • 대체로 #pragma once 도 많이 쓴다.

  2. 필요한 다른 헤더 포함

    • 해당 선언이 의존하는 타입이나 상수가 있으면 #include 로 가져온다.
  3. 선언부만 포함

    • 함수 원형, 전역 변수 extern 선언, 매크로, typedef, 구조체를 정의한다.

    • 구현부(함수 body) 는 .c 파일에 작성한다.

  4. 관례

    • 파일명: 라이브러리/모듈명 + .h (math_utils.h)

    • 동일한 이름의 .c 파일이 실제 구현을 가짐 (math_utils.c).

.h 사용 이유

header file을 별도로 두지 않고 myfunc.c 안에 함수의 선언과 바디를 모두 작성하고, include guard를 이용하고, main.c에서 include 하면 안될까? 기능적으로 동작할 수는 있지만 다음의 문제점들이 발생한다.

  • 여러 소스 파일에서 동일한 test.c를 include하면 multiple definition (중복 정의) 문제 발생
    • 예를 들어 main.c, test.c 두 군데서 #include "myfunc.c"를 하면, 두 오브젝트 파일(main.o, test.o) 안에 각각 add() 함수 정의가 생긴다.
    • 링킹할 때 “multiple definition of add” 에러가 발생한다.
    • 가드는 한 번의 전처리 안에서 중복을 막아줄 뿐**, **빌드 전체에서 함수 정의가 두 번 생기는 문제는 해결하지 못한다.
  • 분리 컴파일 장점이 사라짐

    • C의 강점 중 하나가 “수정된 파일만 다시 컴파일”할 수 있다는 건데,.c를 include하면 결국 전부 하나로 합쳐져 버리니 매번 전체 재컴파일이 필요하다. 정석적인 방법은 아래의 흐름을 따를 것이다.

      project/
       ├─ main.c
       ├─ math.c
       └─ math.h
      // math.h
      int add(int a, int b);
      // math.c
      #include "math.h"
      int add(int a, int b) {
          return a + b;
      }
      // main.c
      #include <stdio.h>
      #include "math.h"
          
      int main() {
          printf("%d\n", add(2, 3));
          return 0;
      }
      
      gcc -c math.c   # math.o 생성 (한번만 빌드)
      gcc -c main.c   # main.o 생성
      gcc main.o math.o -o main
          
      # 이후에 main.c를 수정하면
      gcc -c main.c   # main.o만 새로 빌드
      gcc main.o math.o -o main
      
  • 인터페이스와 구현 분리 원칙 위반

    • .h는 “이 함수가 있다”라는 약속(선언),

    • .c는 그 약속의 구현.

    • .c를 include하면 다른 모듈에서 구현까지 다 들여다보게 되므로 재사용성/캡슐화가 깨진다.

Tags:

Categories:

Leave a Comment