외부 정의와 잠정 정의
외부 정의와 잠정 정의 (External and tentative definitions)
번역 단위(전처리기가 끝난 뒤 모든 #include가 들어 있는 소스 파일)의 최상위에서, C 프로그램은 선언의 나열이에요. 이 선언들은 외부 또는 내부 연결을 가진 함수와 객체를 선언하죠. 이런 선언을 *외부 선언(external declaration)*이라고 불러요. 어떤 함수의 안에도 있지 않고 바깥에 나타나기 때문이에요.
extern int n; // external declaration with external linkage
int b = 1; // external definition with external linkage
static const char *c = "abc"; // external definition with internal linkage
int f(void) // external definition with external linkage
{
int a = 1; // non-external
return b;
}
static void x(void) // external definition with internal linkage
{
}
외부 선언으로 선언된 객체는 정적 저장 기간을 가져요. 그래서 auto나 register 지정자는 못 쓰죠. 단 C23부터는 auto를 타입 추론에 쓸 수는 있어요. 외부 선언이 도입하는 식별자는 파일 스코프를 가져요.
출처: cppreference
본문
잠정 정의 (Tentative definitions)
*잠정 정의(tentative definition)*는 초기화자가 없는 외부 선언으로, 저장 클래스 지정자가 없거나 static 지정자가 붙은 선언이에요.
잠정 정의는 정의로 작동할 수도, 작동하지 않을 수도 있는 선언이에요. 같은 번역 단위 안에서 나중이나 앞에서 실제 외부 정의를 찾으면, 잠정 정의는 그냥 선언으로만 작동해요.
int i1 = 1; // definition, external linkage
int i1; // tentative definition, acts as declaration because i1 is defined
extern int i1; // declaration, refers to the earlier definition
extern int i2 = 3; // definition, external linkage
int i2; // tentative definition, acts as declaration because i2 is defined
extern int i2; // declaration, refers to the external linkage definition
같은 번역 단위 안에 정의가 하나도 없으면, 잠정 정의가 실제 정의로 작동하면서 객체를 빈 초기화해요.
int i3; // tentative definition, external linkage
int i3; // tentative definition, external linkage
extern int i3; // declaration, external linkage
// in this translation unit, i3 is defined as if by "int i3 = 0;"
extern 선언은 이전 선언이 연결을 확립했다면 식별자의 연결을 바꾸지 않아요. 하지만 잠정 정의는 같은 식별자의 다른 선언과 연결이 어긋날 수 있어요. 같은 식별자에 대한 두 선언이 스코프 안에 있고 연결이 다르면, 동작은 정의되지 않아요.
static int i4 = 2; // definition, internal linkage
int i4; // Undefined behavior: linkage disagreement with previous line
extern int i4; // declaration, refers to the internal linkage definition
static int i5; // tentative definition, internal linkage
int i5; // Undefined behavior: linkage disagreement with previous line
extern int i5; // refers to previous, whose linkage is internal
내부 연결을 가진 잠정 정의는 완전한 타입이어야 해요.
static int i[]; // Error, incomplete type in a static tentative definition
int i[]; // OK, equivalent to int i[1] = {0}; unless redeclared later in this file
단일 정의 규칙 (One definition rule)
각 번역 단위는 내부 연결(static 전역)을 가진 모든 식별자에 대해 외부 정의를 0개 또는 1개 가질 수 있어요.
내부 연결을 가진 식별자가, C99부터는 비-VLA,sizeof, _Alignof(C11부터, C23까지), alignof(C23부터), typeof(C23부터)가 아닌 어떤 표현식에서든 쓰이면, 그 번역 단위에 그 식별자에 대한 외부 정의가 정확히 하나 있어야 해요.
전체 프로그램은 외부 연결을 가진 모든 식별자에 대해 외부 정의를 0개 또는 1개 가질 수 있어요.
외부 연결을 가진 식별자가, C99부터는 비-VLA,sizeof, _Alignof(C11부터, C23까지), alignof(C23부터), typeof(C23부터)가 아닌 어떤 표현식에서든 쓰이면, 전체 프로그램 어딘가에 그 식별자에 대한 외부 정의가 정확히 하나 있어야 해요.
참고 (Notes)
- (C99부터) 서로 다른 번역 단위의 인라인 정의는 단일 정의 규칙에 묶이지 않아요. 인라인 함수 정의의 자세한 내용은
inline문서를 참고하세요. - 파일 스코프 선언에서
extern키워드의 의미는 저장 기간과 연결에서 설명해요. - 선언과 정의의 구분은 정의 문서에서 설명해요.
- 잠정 정의는 C89 이전의 여러 방식으로 내부 연결 식별자를 전방 선언하던 관행을 표준화하려고 만들어진 것이에요.
더 알아보기
- 저장 기간과 연결 문서를 함께 보면
extern과static의 역할이 명확해져요. inline문서에서 인라인 함수 정의가 단일 정의 규칙을 어떻게 피해 가는지 확인할 수 있어요.- cppreference의 외부 및 잠정 정의 원문에서 결함 보고 기록까지 볼 수 있어요.