谈谈宏函数

2020-07-29

最近同事发现一个神奇的现象,是一个关于宏函数的。其实要谈论宏函数,能说的有很多,这里先讲一部分。

先谈谈预处理阶段要做的事情:

  • #define的宏定义
  • #include的包含源文件
  • #if等条件编译的确定编译块
  • #error,#line,#pragma等的辅助编译

而这里要说的就是#define

这里需要记住下面的话:

  1. 所有的宏都是在预处理阶段被替换

  2. 对于常见的#define MAX 10,还是宏函数#define read(*s*, *mem*, *len*) lwip_read(s, mem, len),她们之间唯一的区别就是:

    宏函数会在预处理阶段被替换之前进行判断参数的个数,如果不对,会停止该编译单元的预处理。

  3. 对于普通的宏或者宏函数,她们遵循的基本规则是:

    • 会识别大小写的哈,宏名一般用大写好点。
    • 宏的作用区域是宏定义的地方开始,往下一直到源文件的最后。可以用#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吗?