以文本方式查看主题

-  曙海教育集团论坛  (http://sun4.cn/bbs/index.asp)
--  C++语言开发  (http://sun4.cn/bbs/list.asp?boardid=63)
----  C(不讨论C++)语言  (http://sun4.cn/bbs/dispbbs.asp?boardid=63&id=2426)

--  作者:wangxinxin
--  发布时间:2010-12-10 14:26:54
--  C(不讨论C++)语言

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是平级的.本篇文章