파일 분할 (File Division)

Jaemyeong Lee·2024년 8월 4일

게임 서버1

목록 보기
26/220

이 Step에서 다루는 것

  • C++에서 “파일을 나눈다”는 게 단순히 파일을 쪼개는 게 아니라,
    컴파일 단위(.cpp) + 전처리(#include) + 링킹(연결) 을 이해해야 하는 이유
  • 선언(Declaration)과 정의(Definition)를 분리하는 기본 규칙
  • 헤더를 “가볍게” 유지하는 실전 원칙(빌드 속도/의존성/충돌)
  • extern / 전방 선언 / 헤더 가드로 발생하는 문제를 예방하는 방법

학습 목표

  • #include가 “붙여넣기”에 가깝다는 것을 설명할 수 있다.
  • “선언은 있는데 정의가 없다”가 왜 링크 에러로 이어지는지 설명할 수 있다.
  • 가장 흔한 컴파일/링커 에러를 보고 원인을 1~2분 안에 좁힐 수 있다.

파일 분할이 필요한 이유

이유설명
가독성·유지보수한 파일에 모든 코드 → 관리 어려움
협업파일별로 분리 → Git 등에서 충돌 감소
컴파일 시간변경된 파일만 재컴파일 → 빌드 속도 개선
모듈화기능별 분리 → 확장·객체지향 설계 용이
  • C# 등은 파일만 추가하면 되지만, C++은 선언·정의 분리 등으로 상대적으로 복잡함.

선언 vs 정의

구분선언 (Declaration)정의 (Definition)
역할"이게 있다"고 알림실제 구현
위치.h (헤더).cpp (소스)
메모리사용 안 함사용함

핵심 감각:

  • 선언: “이런 함수/변수가 있어요” (컴파일러가 호출 형태를 알 수 있음)
  • 정의: “진짜 내용은 여기 있어요” (링커가 실제 구현을 찾아 연결)

컴파일·링킹 흐름

소스(.cpp) → [전처리] → [컴파일] → [어셈블] → 목적파일(.obj)
                                                 ↓
[링킹] → 실행파일(.exe)

핵심 3줄

  • .cpp는 독립적으로 컴파일됩니다. (컴파일 단위 = Translation Unit)
  • #include는 전처리 단계에서 해당 파일 내용을 그 자리로 복사/붙여넣기 합니다.
  • 링커는 여러 .obj를 모아, “선언만 있고 아직 비어있는 호출”을 실제 정의에 연결합니다.

전처리 관점으로 보면:

// GameCoding.cpp
#include "Helper.h"
int main() { Test2(); }

↓ (전처리 후, 개념적으로)

// GameCoding.cpp
// --- Helper.h 내용이 여기에 그대로 들어옴 ---
void Test2();
// ------------------------------------------
int main() { Test2(); }

#include의 역할

  • #include "Helper.h"Helper.h 내용을 그 위치에 복사.
  • #include <iostream> → 표준 라이브러리 헤더 포함.
  • 반복 include는 헤더 가드가 없다면 “같은 선언/정의가 여러 번” 들어와서 에러/빌드 지연을 유발합니다.

헤더 가드 (#pragma once / include guard)

  • #pragma once: 해당 헤더가 한 번만 포함되도록. (현대적 방식)
  • Include guard (전통):
    #ifndef HELPER_H
    #define HELPER_H
    // ... 내용 ...
    #endif
  • 매크로 이름에 __(더블 언더스코어)는 구현 예약 영역인 경우가 많아 피하는 게 안전합니다.

헤더 파일 작성 원칙

  • 헤더는 최대한 가볍게 유지합니다.
    • 헤더를 포함하는 모든 .cpp가 그 내용을 함께 “붙여넣기”로 가져가기 때문입니다.
  • 헤더에는 보통 아래만 둡니다:
    • 함수/클래스/구조체 선언
    • 필요한 최소한의 타입 선언(전방 선언 포함)
  • 가능하면 피하기:
    • 헤더에서 using namespace std; (전역 오염)
    • 헤더에서 불필요한 <iostream> 같은 무거운 include

실전 규칙(암기):

  • “헤더는 최소 포함, 구현은 cpp에서 include”

extern (전역 변수 분리)

전역 변수도 함수와 같은 문제가 생깁니다.

  • 헤더에 int GTest;를 써버리면, 그 헤더를 include하는 모든 .cpp정의가 복사되어
    “중복 정의” 링크 에러가 날 수 있습니다.

그래서 전역 변수는 다음 규칙을 씁니다.

  • 선언(헤더): extern int GTest; → “다른 곳에 정의가 있다”
  • 정의(딱 1개의 cpp): int GTest = 0; → 실제 메모리 할당

Helper.h – 선언 파일(가볍게)

#pragma once
void Test(int value);
void Test2();
extern int GTest;
  • extern int GTest: GTest는 정의되지 않았으며, 다른 파일에서 정의될 것임을 의미.

Helper.cpp – 정의(구현) 파일

#include "Helper.h"
#include <iostream>

using namespace std;

int GTest = 0;  // 실제 메모리 공간 할당(정의는 여기 1번만)

void Test(int value) {
    (void)value;
    Test2();
}

void Test2() {
    cout << "Hello World" << '\n';
}

GameCoding.cpp – main 파일

#include "Helper.h"

int main() {
    GTest = 100;
    Test2();
    return 0;
}
  • #include "Helper.h"선언을 포함하고, 정의는 Helper.cpp에 있으므로 링커가 연결합니다.

전방 선언 (Forward Declaration)

  • 클래스/구조체를 정의 전에 이름만 알림.
  • 포인터·참조만 사용할 때는 전방 선언으로 include 의존성 줄일 수 있음.
class MyClass;                     // 전방 선언
void doSomething(MyClass* obj);   // 정의 없이 포인터 사용 가능
  • MyClass의 내부 멤버를 접근하거나 크기를 알아야 하면(값으로 들고 있으면) 정의가 필요합니다.
    • 그때만 #include "MyClass.h"를 추가합니다.

자주 나오는 에러

에러원인
선언되지 않음#include 누락, 또는 선언 누락
LNK: 확인할 수 없는 외부 기호선언만 있고 정의(구현) 없음
LNK: 중복 정의헤더에 정의를 넣었거나 전역/함수를 여러 cpp에서 정의
시그니처 불일치선언과 정의의 매개변수·리턴 타입/네임스페이스가 다름

디버깅 루틴(링커 에러가 났을 때)

  1. 에러 메시지에서 “심볼 이름”을 본다
  2. 그 심볼이 “선언만 있고 정의가 없는지” 확인한다
  3. “정의가 있는데도 못 찾는다”면:
    • cpp가 프로젝트에 포함되어 빌드되는지
    • 함수 시그니처(매개변수/리턴/네임스페이스)가 완전히 같은지
  4. “중복 정의”면:
    • 헤더에 정의를 넣었는지(전역 변수/함수/전역 객체)
    • extern 규칙(선언은 헤더, 정의는 cpp 1개)을 지켰는지

체크 질문 (스스로 답해보기)

  • #include는 “참조”인가 “붙여넣기”인가?
  • “선언만 있고 정의가 없는 상태”가 왜 컴파일 단계가 아니라 링크 단계에서 터질까?
  • 전역 변수는 왜 extern이 필요한가?

profile
李家네_공부방

0개의 댓글