이름 공간과 조회
이름 공간과 조회 (Lookup and name spaces)
C 프로그램에서 식별자(identifier)를 만나면, 그 식별자를 도입한 선언 중 현재 스코프에 있는 것을 찾는 **조회(Lookup)**가 일어나요. 그런데 잠깐, 같은 이름을 구조체 태그로도 쓰고 변수 이름으로도 쓸 수 있지 않나요? C는 이게 가능해요. 이유는 식별자가 서로 다른 **이름 공간(Name space)**이라는 범주에 속할 수 있기 때문이에요. 같은 식별자라도 범주가 다르면 동시에 스코프에 있어도 돼요.
본문
C 프로그램에서 식별자를 만나면 조회를 수행해서, 그 식별자를 도입했고 현재 **스코프(scope)**에 있는 선언을 찾아요. C는 같은 식별자에 대한 선언이 여러 개 동시에 스코프에 있는 것을 허용해요. 단, 그 식별자들이 이름 공간이라는 서로 다른 범주에 속할 때만요.
-
레이블 이름 공간: 레이블(label)로 선언된 모든 식별자.
-
태그 이름:
struct,union, 열거형의 이름으로 선언된 모든 식별자. 단, 이 세 종류의 태그는 하나의 이름 공간을 공유한다는 점을 기억하세요. -
멤버 이름: 어떤 하나의
struct나union의 멤버로 선언된 모든 식별자. 모든 struct·union은 자신만의 이 종류의 이름 공간을 도입해요. -
전역 속성 이름 공간: 표준이 정의하거나 구현이 정의한 속성 접두사에 따른 속성 토큰.
-
비표준 속성 이름: 속성 접두사 뒤에 오는 속성 이름. 각 속성 접두사는 자신이 도입하는 구현 정의 속성을 위한 별도의 이름 공간을 가져요.
(C23부터)
-
그 밖의 모든 식별자: (1~5)와 구분하기 위해 **일반 식별자(ordinary identifiers)**라고 불러요 (함수 이름, 객체 이름,
typedef이름, 열거 상수).
조회가 일어나는 지점에서 식별자의 이름 공간은 그 식별자가 사용된 방식에 의해 결정돼요.
-
goto문의 피연산자로 나타나는 식별자는 레이블 이름 공간에서 조회돼요. -
struct,union,enum키워드 뒤에 오는 식별자는 태그 이름 공간에서 조회돼요. -
멤버 접근 연산자나 포인터를 통한 멤버 접근 연산자 뒤에 오는 식별자는, 멤버 접근 연산자의 왼쪽 피연산자가 결정하는 타입의 멤버 이름 공간에서 조회돼요.
-
속성 지정자(
[[...]])에 직접 나타나는 식별자는 전역 속성 이름 공간에서 조회돼요. -
속성 접두사 다음의
::토큰 뒤에 오는 식별자는 그 속성 접두사가 도입한 이름 공간에서 조회돼요.(C23부터)
-
그 밖의 모든 식별자는 일반 식별자의 이름 공간에서 조회돼요.
참고 (Notes)
매크로의 이름은 어떤 이름 공간에도 속하지 않아요. 매크로는 의미 분석 이전에 전처리기가 치환해 버리기 때문이에요.
구조체/공용체/열거형 이름을 typedef 선언으로 일반 식별자 이름 공간에 넣는 것은 흔한 관례예요.
struct A { }; // introduces the name A in tag name space
typedef struct A A; // first, lookup for A after "struct" finds one in tag name space
// then introduces the name A in the ordinary name space
struct A* p; // OK, this A is looked up in the tag name space
A* q; // OK, this A is looked up in the ordinary name space
두 이름 공간에 걸쳐 똑같은 식별자를 쓰는 잘 알려진 예로, POSIX 헤더 sys/stat.h의 식별자 stat이 있어요. 일반 식별자로 쓰이면 함수를 가리키고, 태그로 쓰이면 구조체를 가리켜요.
C++과 달리 C에서는 열거 상수가 구조체 멤버가 아니고, 그 이름 공간은 일반 식별자의 이름 공간이에요. 게다가 C에는 구조체 스코프가 없으므로, 열거 상수의 스코프는 구조체 선언이 나타나는 스코프가 돼요.
struct tagged_union {
enum {INT, FLOAT, STRING} type;
union {
int integer;
float floating_point;
char *string;
};
} tu;
tu.type = INT; // OK in C, error in C++
표준 속성, 속성 접두사, 비표준 속성 이름이 지원되지 않으면, 잘못된 속성 자체는 오류를 내지 않고 무시돼요. (C23부터)
예제 (Example)
void foo (void) { return; } // ordinary name space, file scope
struct foo { // tag name space, file scope
int foo; // member name space for this struct foo, file scope
enum bar { // tag name space, file scope
RED // ordinary name space, file scope
} bar; // member name space for this struct foo, file scope
struct foo* p; // OK: uses tag/file scope name "foo"
};
enum bar x; // OK: uses tag/file-scope bar
// int foo; // Error: ordinary name space foo already in scope
//union foo { int a, b; }; // Error: tag name space foo in scope
int main(void)
{
goto foo; // OK uses "foo" from label name space/function scope
struct foo { // tag name space, block scope (hides file scope)
enum bar x; // OK, uses "bar" from tag name space/file scope
};
typedef struct foo foo; // OK: uses foo from tag name space/block scope
// defines block-scope ordinary foo (hides file scope)
(foo){.x=RED}; // uses ordinary/block-scope foo and ordinary/file-scope RED
foo:; // label name space, function scope
}
더 알아보기
- 이름 공간은 **스코프(Scope)**와 수명(Lifetime) 개념과 이어져요. 조회가 '어디서 이름을 찾는지'를 정한다면, 스코프는 '그 이름이 언제까지 유효한지'를 정해요.
- C++의 이름 조회(Name lookup)와 비교해 보면 두 언어의 차이가 더 또렷해져요.
- cppreference의 Lookup and name spaces 원문에서 표준 절 번호를 더 확인할 수 있어요.