이름 공간과 조회

이름 공간과 조회 (Lookup and name spaces)

C 프로그램에서 식별자(identifier)를 만나면, 그 식별자를 도입한 선언 중 현재 스코프에 있는 것을 찾는 **조회(Lookup)**가 일어나요. 그런데 잠깐, 같은 이름을 구조체 태그로도 쓰고 변수 이름으로도 쓸 수 있지 않나요? C는 이게 가능해요. 이유는 식별자가 서로 다른 **이름 공간(Name space)**이라는 범주에 속할 수 있기 때문이에요. 같은 식별자라도 범주가 다르면 동시에 스코프에 있어도 돼요.

출처: cppreference Lookup and name spaces

본문

C 프로그램에서 식별자를 만나면 조회를 수행해서, 그 식별자를 도입했고 현재 **스코프(scope)**에 있는 선언을 찾아요. C는 같은 식별자에 대한 선언이 여러 개 동시에 스코프에 있는 것을 허용해요. 단, 그 식별자들이 이름 공간이라는 서로 다른 범주에 속할 때만요.

  1. 레이블 이름 공간: 레이블(label)로 선언된 모든 식별자.

  2. 태그 이름: struct, union, 열거형의 이름으로 선언된 모든 식별자. 단, 이 세 종류의 태그는 하나의 이름 공간을 공유한다는 점을 기억하세요.

  3. 멤버 이름: 어떤 하나의 structunion의 멤버로 선언된 모든 식별자. 모든 struct·union은 자신만의 이 종류의 이름 공간을 도입해요.

  4. 전역 속성 이름 공간: 표준이 정의하거나 구현이 정의한 속성 접두사에 따른 속성 토큰.

  5. 비표준 속성 이름: 속성 접두사 뒤에 오는 속성 이름. 각 속성 접두사는 자신이 도입하는 구현 정의 속성을 위한 별도의 이름 공간을 가져요.

    (C23부터)

  6. 그 밖의 모든 식별자: (1~5)와 구분하기 위해 **일반 식별자(ordinary identifiers)**라고 불러요 (함수 이름, 객체 이름, typedef 이름, 열거 상수).

조회가 일어나는 지점에서 식별자의 이름 공간은 그 식별자가 사용된 방식에 의해 결정돼요.

  1. goto 문의 피연산자로 나타나는 식별자는 레이블 이름 공간에서 조회돼요.

  2. struct, union, enum 키워드 뒤에 오는 식별자는 태그 이름 공간에서 조회돼요.

  3. 멤버 접근 연산자나 포인터를 통한 멤버 접근 연산자 뒤에 오는 식별자는, 멤버 접근 연산자의 왼쪽 피연산자가 결정하는 타입의 멤버 이름 공간에서 조회돼요.

  4. 속성 지정자([[...]])에 직접 나타나는 식별자는 전역 속성 이름 공간에서 조회돼요.

  5. 속성 접두사 다음의 :: 토큰 뒤에 오는 식별자는 그 속성 접두사가 도입한 이름 공간에서 조회돼요.

    (C23부터)

  6. 그 밖의 모든 식별자는 일반 식별자의 이름 공간에서 조회돼요.

참고 (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 원문에서 표준 절 번호를 더 확인할 수 있어요.