也谈C语言标识符的NAMESPACE
P.J Plauger的"The Standard C Library"一书的Chapter0的章后练习中有这样的一道题:编写一个包含如下一行语句的正确的程序:
x: ((struct x*)x)->x=x(5);
并描述这行语句中x的5种截然不同的use,这里其实涉及到这么一个知识或者说概念:C语言的命名空间(namespace),在"C语言参考手册"中还被称作: overloading class。
这里namespace,并非C++中的那个keyword "namespace",这里的namespace更多是编译器为了识别不同范围下的标识符而进行的划分,而不是提供给应用程序员的类似c++中的那个namespace facility。再次注意:C的namespace不是一个关键字。
简单分析一下这行语句:x: ((struct x*)x)->x=x(5);
这里有5个x,第一印象:这样的语句能编译过去么?那既然P.J Plauger提出了这样的问题,那么自然有solution。
从左到右顺序:
第一个x — 毋庸置疑,这是一个标号(label) ;
第二个x — 这里的x显然是一个struct tag(结构体标志);
第三个x — 这里的x 无法确定其具体身份,可能是一指针类型,也可能就是一个整型;
第四个x — x前面有->,显然这个x是某结构体的一个成员变量;
第五个x — x(5)让人"浮想联翩",第一印象是函数调用,细致一想还可能是一个宏哦(你肯定会说不可能,呵呵,别着急,慢慢来)
到底如何增加一些语法元素能让这一行能顺利通过编译,并执行后得到合理结果呢?我们不妨先来温习一下C标准中对C的"命名空间"的诠释。
在"C语言参考手册"中有如此说明,标准C将其Namespace分成了五种,分别是:
1) 预处理器宏名
2) 语句标号
3) 结构、枚举、联合结构的标志
4) 成员名
5) 其他名称 包括变量名、函数名、typedef名称和枚举常量
有了以上的说明,我们有了第一种方案:
上面说了,语句x: ((struct x*)x)->x=x(5)中有三个x都是可以确定的,不确定的是第三个x和最后一个x。我们先考虑让最后一个x为一个函数。
考虑到最后一个名称空间的说明,一旦最后一个x为函数的话,第三个x就不能为变量名、typedef名称和枚举常量了。如果x是对象宏(不带参数的宏),显然也不合理;那么我们先将x实现为函数看看:
struct x { //for the 2nd x
int x; //for the 4th x
};
int x(int a) { //for the 3rd and 5th x
return a;
}
int main() {
x: ((struct x*)x)->x=x(5);
}
这个在gcc(sunos or mingw on windows下)下编译能顺利通过。但是执行一下编译出的程序,会出现致命错误。初略分析一下也不奇怪。函数x的地址是在代码段,那块内存区域是只读且受保护的,尝试强制赋值显然os是不允许的。
第一种方案虽然能通过编译,但是执行结果不合理。我们来做第二种尝试:试着将最后一个x实现为一个函数宏(带参数的宏)。
struct x { //for the 2nd x
int x; //for the 4th x
};
struct x ax;
#define x(a) (a);
int main() {
int x = (int)(&ax);
x: ((struct x*)x)->x=x(5);
printf("%d\n", ((struct x*)x)->x); //output: 5
}
这回,我们得到了正确的且合理的solution了。在P.J Plauger的"The Standard C Library"一书中还有一张关于C语言命名空间的图,记起来更形象。
评论