最近同事发现一个神奇的现象,是一个关于宏函数的。其实要谈论宏函数,能说的有很多,这里先讲一部分。
先谈谈预处理阶段要做的事情:
- #define的宏定义
- #include的包含源文件
- #if等条件编译的确定编译块
- #error,#line,#pragma等的辅助编译
而这里要说的就是#define!
这里需要记住下面的话:
-
所有的宏都是在预处理阶段被替换
-
对于常见的
#define MAX 10
,还是宏函数#define read(*s*, *mem*, *len*) lwip_read(s, mem, len)
,她们之间唯一的区别就是:宏函数会在预处理阶段被替换之前进行判断参数的个数,如果不对,会停止该编译单元的预处理。
-
对于普通的宏或者宏函数,她们遵循的基本规则是:
- 会识别大小写的哈,宏名一般用大写好点。
- 宏的作用区域是宏定义的地方开始,往下一直到源文件的最后。可以用#undef命令终止宏定义的作用域。
- 如果重复定义,会报警告,最后定义的会覆盖之前的。
- 对于宏函数,会先判断参数个数,然后再判断,并且和普通的宏一样进行判断并替换。
- 最后,最最重要的就是,怎么判断她是可被替换的宏?宏只替换标识符。
可能,我没有说清楚,那么show code
:
#define read(s, mem, len) lwip_read(s, mem, len)
#define NUM 2
struct lili
{
int num = NUM;
int (*read)(int s, int name, int namelen); /* data */
read(1, 2, 3);
read(int s, int name, int namelen);
};
int main()
{
struct lili test1;
struct lili *test2;
test1.read(1, 2, 3);
test2->read(1, 2, 3);
}
最后预处理的结果是:
struct lili
{
int num = 2;
int (*read)(int s, int name, int namelen);
lwip_read(1, 2, 3);
lwip_read(int s, int name, int namelen);
};
int main()
{
struct lili test1;
struct lili *test2;
test1.lwip_read(1, 2, 3);
test2->lwip_read(1, 2, 3);
}
可见:
宏替换的预处理结果,并不关心结构体或者结构体成员的使用,而是统一的把她当作字符替换。
这就导致一个问题:
对于结构体里的函数指针,我们是这样使用的:
struct lili
{
int num = 2;
int (*read)(int s, int name, int namelen);
};
很显然,因为read被括号括起来,导致和后面的参数分离,所以不会被认为是宏函数,也不会被替换。
然后,函数指针的使用则是:
int main()
{
struct lili test1;
struct lili *test2;
test1.read(1, 2, 3);
test2->read(1, 2, 3);
}
可见,read后面跟括号,被认为是函数,然后read前面是.
或者->
,因此read是一个独立的标识符,可被替换。
那么问题就来了,在结构体里的函数指针不会被替换,而在main中执行的函数指针则就被替换了,我可以说这是gnu的BUG吗?