앞선 포스트에서 빌드의 7번째 단계 후, 각 코드 별로 해석 유닛( TU )를 생성한다고 했습니다.
물론, 이 TU가 생성이 되기 위해선, 흔히 생각하는 C++상에서의 문법 오류가 없어야 합니다.
하지만 우리가 잘 인지하지 못하는 TU에 적용되는 중요한 규칙이 있습니다.
바로 각 TU에 존재하는 모든 변수, 함수, 클래스, enum,
템플릿 등등의 정의( Definition )은 유일해야하고,
inline이 아닌 모든 함수의 변수들의 정의는 전체 프로그램에서 유일해야 한다.라는
유일 정의 규칙( One Definition Rule - ODR ) 입니다.
그럼 C++에서 이야기 하는 정의 란 무엇일까요?
우리는 종종 정의와 선언을 혼동해서 사용하곤 합니다.
하지만 C++에서 이 둘은 엄연히 다른 개념입니다.
먼저 선언( Declaration )이란 TU에 새로운 이름을 도입하거나,
기존에 선언된 이름을 재선언 하는 것입니다.
예를 들어
int f();
의 경우, f
라는 함수를 선언했습니다.
그리고 정의는 선언을 포함하는 개념으로, 선언된 개체를 완전히 정의함을 뜻합니다.
예를 들어,
int a;
의 경우 a
라는 `int 변수를 정의한 것입니다.
하지만 extern
지정자가 들어간 선언의 경우 명시적으로 초기화 되지 않는다면 선언입니다.
extern const int a; // a를 선언하였지만 정의하지 않음
extern const int b = 1; // b를 정의함
위의 경우는, a는 선언, b는 정의 입니다.
또한, 클래스 정의 내부에 inline이
아닌 static
멤버의 경우 정의입니다.
struct S {
int n ; // S::n 정의
static int i; // S::i를 선언하지만 정의는 아님
inline static int x; // S::x 를 정의
}; // S를 정의
int S::i; // S::i를 정의
각 TU에 존재하는 모든 변수, 함수, 클래스, enum, 템플릿 등등의 정의( Definition )는
유일해야 하고,inline
이 아닌 모든 함수의 변수들의 정의는
전체 프로그램에서 유일해야 한다.
이 말은 즉, TU안에 같은 선언은 여러개 있어도 괜찮다는 의미 입니다. 실제로
int f(); // f 의 선언
int f(); // f 의 선언
int f(); // f 의 선언
위 코드는 문제가 없습니다. 왜냐하면, int f()
는 f
의 선언이지
정의가 아니기 때문입니다. 하지만
int f()
{
return 0;
}
int f()
{
return 0;
}
위와 같은 경우 컴파일을 하였다면, 다음과 같은 오류가 발생하게 됩니다.
test.cc:5:5: error: redefinition of ‘int f()’
5 | int f() {
| ^
test.cc:1:5: note: ‘int f()’ previously defined here
1 | int f() {
| ^
또 ODR이 내포하는 다른 뜻은,
inline이 아닌 모든 함수의 변수들의 정의는 전체 프로그램에서 유일해야한다.
즉, inline
으로 정의되지 않는 모든 함수들과 변수들의 경우 프로그램을 구성하는 모든 TU에 정의가 단 하나 있어야합니다.
원래 inline
키워드가 처음 도입되었을 때의 의미는, 컴파일러에게
이 함수를 호출하는 문장을 그냥 이 함수의 내용으로 치환시켜도 된다 라는 의미였습니다.
하지만 현대의 C++컴파일러는 굉장히 똑똑해졌기 때문에,
우리가 굳이 inline
이라고 명시하지 않아도
만일 인라인하는 것이 효율이 좋다면 인라인을 시키고,
반대로 인라인 함수가 인라인을 안했을 때, 효율이 좋다면 인라인화를 하지 않습니다.
따라서 현대의 `inline 키워드는 다음과 같은 의미를 나타낸다고 보시는게 낫습니다.
이 함수는 여러개의 TU에 정의되어 있어도 괜찮음!!
참조 : 모두의 코드 : 씹어먹는 C++
글이 잘 정리되어 있네요. 감사합니다.