선언과 정의 구조
선언(declaration)
컴파일러에게 어떤 이름(identifier) 의 존재와 그 타입(type) 을 알려주는 행위이다. 선언만으로는 메모리 공간이 할당되지 않으며, 실제 동작 내용도 포함되어 있지 않다.
int counter; // 변수의 선언
int add(int a, int b); // 함수의 선언
선언의 위치
선언은 반드시 호출보다 앞서서 나와야 한다. 컴파일러는 컴파일 시점에서 모든 식별자와 정확한 타입을 알고 있어야만 올바르게 작동한다. 따라서 선언은 파일 상단에 위치시켜야 한다.
int x = sum(2, 3); // error: 컴파일러는 해당 라인을 읽는 시점에서 sum 함수의 존재를 모른다.
int sum(int a, int b) {
return a + b;
}
정의(definition)
선언이 가리키는 대상의 실제 구현이나 메모리를 제공하는 행위이다. 정의는 변수일 경우 메모리 공간을 할당하며, 함수일 경우 구체적인 동작을 포함한다.
int counter = 0; // 변수의 정의: 실제 메모리 공간 할당
int add(int a, int b) { // 함수의 정의: 실행 가능한 코드 제공
return a + b;
}
ODR
프로그램 전체에서 하나의 이름에 대한 정의는 정확히 한 번만 존재해야 하며, 이를 C++에서는 ODR(One Definition Rule) 이라고 한다.
// a.cpp
int count = 0;
// b.cpp
int count = 0; // error: multiple definitions of 'count'
위 코드처럼 두 곳에서 동일한 이름을 정의하면 ODR 위반이 발생하고, 링크 과정에서 충돌 오류가 발생한다.
모든 정의는 선언이다.
모든 정의는 동시에 선언 이기도 하다. 정의는 선언이 해야 할 모든 일을 포함하고, 거기에 구체적인 구현이나 메모리 할당이 추가된 형태이다.
int count = 0; // 선언과 동시에 정의
int add(int a, int b) { // 선언과 동시에 정의
return a + b;
}
즉, 정의는 항상 선언의 일종이다. 정의 ⊂ 선언
헤더 파일과 소스 파일
C++에서 프로젝트를 구성할 때는 일반적으로 헤더 파일(.h)과 소스 파일(.cpp)을 구분해서 사용한다.
project/
├── main.cpp
├── utils.h ← 선언 (인터페이스)
└── utils.cpp ← 정의 (구현)
헤더 파일(.h)
헤더 파일은 선언을 모아두는 파일이다. 헤더 파일은 여러 소스 파일에서 #include로 공유되어 사용된다.
소스 파일(.cpp)
소스 파일은 정의(실제 동작)를 구현하는 곳이다. 함수의 본체, 전역 변수 정의, 로직 구현 등이 이곳에 위치한다. 보통 하나의 .cpp 파일은 대응되는 .h 파일을 #include 해서 사용한다.
분리 이유
- 헤더 파일을 통해 다른 파일에서 함수나 클래스의 형식만 알고 쓸 수 있다.
- 구현이 바뀌어도 인터페이스가 유지되면, 재컴파일 범위를 줄일 수 있다.
- 여러 파일에서 같은 선언을 공유할 수 있어, 일관성 유지가 쉬워진다.