Namespaces
Variants

Lookup and name spaces

From cppreference.net

当在C程序中遇到 标识符 时,会执行查找操作以定位引入该标识符且当前 在作用域内 声明 。C语言允许多个相同标识符的声明同时存在于作用域中,只要这些标识符属于不同的类别,即称为 命名空间

1) 标签命名空间:所有声明为 labels 的标识符。
2) 标签名:所有声明为 结构体 联合体 枚举类型 名称的标识符。注意这三类标签共享同一个命名空间。
3) 成员名称:所有被声明为任一 struct union 成员的标识符。每个struct和union都会引入其自身的此类名称空间。
4) 全局属性命名空间:由标准定义的 属性标记 或实现定义的属性前缀。
5) 非标准属性名称:跟随属性前缀的属性名称。每个属性前缀为其引入的实现定义属性拥有独立的命名空间。
(C23起)
6) 所有其他标识符,称为 普通标识符 以区别于 (1-5) (函数名称、对象名称、typedef名称、枚举常量)。

在查找时,标识符的命名空间由其使用方式决定:

1) 作为 goto 语句 操作数出现的标识符会在标签命名空间中查找。
2) 跟在关键字 struct union enum 后面的标识符会在标签命名空间中查找。
3) 成员访问 运算符或通过指针的成员访问运算符之后出现的标识符,会在由成员访问运算符左操作数确定的类型的成员命名空间中进行查找。
4) 直接在属性说明符( [ [ ... ] ] )中出现的标识符会在全局属性命名空间中查找。
5) 在属性前缀后跟随 :: 标记后的标识符会在该属性前缀引入的命名空间中查找。
(since C23)
6) 所有其他标识符在普通标识符的命名空间中查找。

目录

注释

宏的名称不属于任何命名空间,因为它们会在语义分析之前由预处理器进行替换。

通常的做法是使用 typedef 声明将结构体/联合体/枚举名称注入到普通标识符的命名空间中:

struct A { };       // 在标签命名空间引入名称 A
typedef struct A A; // 首先,在"struct"后的 A 查找会在标签命名空间找到一个
                    // 然后在普通命名空间引入名称 A
struct A* p;        // 正确,此 A 在标签命名空间中被查找
A* q;               // 正确,此 A 在普通命名空间中被查找

一个著名的在同一标识符在两个命名空间中被使用的例子是来自POSIX头文件 sys/stat.h 的标识符 stat 。当作为普通标识符使用时,它 命名一个函数 ;而当作为标签使用时,它 表示一个结构体

与 C++ 不同,枚举常量不是结构体成员,其命名空间属于普通标识符的命名空间,并且由于 C 语言中没有结构体作用域,它们的作用域就是结构体声明出现的作用域:

struct tagged_union {
   enum {INT, FLOAT, STRING} type;
   union {
      int integer;
      float floating_point;
      char *string;
   };
} tu;
tu.type = INT; // 在C语言中有效,在C++中错误

如果标准属性、属性前缀或非标准属性名称不受支持,该无效属性本身将被忽略而不会引发错误。

(since C23)

示例

void foo (void) { return; } // 普通命名空间,文件作用域
struct foo {      // 标签命名空间,文件作用域
    int foo;      // 此结构体foo的成员命名空间,文件作用域
    enum bar {    // 标签命名空间,文件作用域
        RED       // 普通命名空间,文件作用域
    } bar;        // 此结构体foo的成员命名空间,文件作用域
    struct foo* p; // 正确:使用标签/文件作用域名称"foo"
};
enum bar x; // 正确:使用标签/文件作用域bar
// int foo; // 错误:普通命名空间foo已在作用域中
//union foo { int a, b; }; // 错误:标签命名空间foo已在作用域中
int main(void)
{
    goto foo; // 正确:使用标签命名空间/函数作用域中的"foo"
    struct foo { // 标签命名空间,块作用域(隐藏文件作用域)
       enum bar x; // 正确:使用标签命名空间/文件作用域中的"bar"
    };
    typedef struct foo foo; // 正确:使用标签命名空间/块作用域中的foo
                            // 定义块作用域普通foo(隐藏文件作用域)
    (foo){.x=RED}; // 使用普通/块作用域foo和普通/文件作用域RED
foo:; // 标签命名空间,函数作用域
}

参考文献

  • C17 标准 (ISO/IEC 9899:2018):
  • 6.2.3 标识符的名称空间 (p: 29-30)
  • C11标准(ISO/IEC 9899:2011):
  • 6.2.3 标识符的名称空间(页码:37)
  • C99标准(ISO/IEC 9899:1999):
  • 6.2.3 标识符的名称空间(页码:31)
  • C89/C90 标准 (ISO/IEC 9899:1990):
  • 3.1.2.3 标识符的命名空间

参见

C++ 文档 关于 Name lookup