C语言中的名字空间, 较少被提及. 下面的写法乍看之下是会让人吃惊的:
#include <stdio.h>
struct Foo
{
int table_id;
signed int length:4;
} ;
typedef struct Foo Foo;
int main()
{
Foo Foo;
printf("size: %lld ", (long long) sizeof(Foo));
// Foo t1 = {0};
return 0;
}
Foo 首先是一个struct的tag名字, 其次又被typedef定义了一个同名的别名. 然后, 在main函数中,
以Foo Foo; 定义了该类型的一个变量, 同样名为Foo.
这样的程序竟然是符合标准的. 原因就在于C语言中有4个名字空间, 当标识符在不同的上下文情境下位于不同的名字空间时, 可同时出现而不会引起冲突.
我查看了C语言标准, 6.2.3 Name Spaces and Identifiers
其中定义的4个名字空间如下:
1. label 单独位于一个名字空间, 由于goto有害论, label受到牵连, 现今其重要性极低.
2. struct, union , enum的名字, 在C标准中用tag一词指代, 它们的名字位于一个名字空间, 也就是说, 如果你已经
struct Foo { ... };
就不能再
enum Foo {... };
了
3. struct, 或union 的成员, 位于由相应的struct或union 声明范围内的一个密闭名字空间, 两个不同的struct, 或struct与union 的成员, 可以有同样的名字, 这一规则可以递归地施行于struct / union 的子成员. 如果它们本身也是一个struct或union 的话.
4. 所有其它的一切东西, 比如函数名, 变量名等等.
根据这4条, 上面的程序该如何解释? Foo 重复出现了3次:
struct Tag.
typedef 或
main内的变量名.
根据上面的定义, 作为typedef定义出来的类型名和main内的变量名同属于"其它"类, 应该会出现冲突. 但实际上这样的用法是允许的. 因为在main内通过
Foo Foo;
定义变量Foo时, 第一个Foo的语意只能是typedef定义出来的Foo才合理, 此时作为变量的Foo还没定义完成, 所以没有冲突.
那一行注释起来的
// Foo t1...
如果去掉注释, 就会引起编译错误,
test.c:15: error: expected ';' before 't1'
gcc的这条错误并没提供多少有用的信息.
因为在此时的上下文中, 就有了两个identifier位于同一个名字空间. 而printf中的sizeof(Foo) 究竟是作为typedef定义出来的别名Foo, 还是变量名Foo.
虽然无法从程序运行结果上知道, 但可以确定应该是变量名Foo, 简单的实验加推理可以证实这一点:
将Foo Foo改为char Foo;
此时大小变为1.
推理:
typdef定义的别名其作为域在最外层, 而在main内, 变量Foo暂时性地遮蔽了外层Foo的意义.
对上面程序作如下修改, 得到的结果可以证实:
int main()
{
{
char Foo;
int a = 4, b = a;
printf("size: %lld ", (long long)sizeof(Foo) );
}
printf("size: %lld ", (long long)sizeof(Foo) );
struct Foo t1 = {0};
return 0;
}
另一个需要注意的地方是, C语言中定义的结构的可见性, 是平坦的,
struct Foo
{
struct Bar { ... };
};
熟悉C++类型系统的人可能会怀疑是否能直接使用结构Bar, 要不要Foo::, C里面Bar的可用性跟Foo是平级的.本篇文章