GNU C语法扩展(3)_linux


要研究LINUX内核,C语言是基础中的基础,但是LINUX并不是完全的标准C,而是对标准C做了很多扩展,这些扩展特性对于我们分析内核有着很重要的作用,下面做些总结性的工作。

3 变参宏

标准C的变参宏

  • 在ISO C99里,一个宏可以被声明为带可变的参数个数,就像函数一样。语法如下:
#define  debug(format, …)  fprintf(stderr, format, __VA_ARGS__)
  • 这里的 “…” 代表变参,在引用宏debug的地方它代表着零个或多个相应的标识符,包括逗号。这些标识符将会替换​​__VA_ARGS__​​。

但是这样的宏不能处理零变参的情况,否则编译不会通过,因为零变参的时候会多一个逗号。

GCC的变参宏

  • GCC 支持变参宏,并且提供另一种词法来定义它,即可赋予变参名称,就像普通参数一样:
#define  debug(format, args…)  fprintf(stderr, format, args)
  • 这种用法与上面所述的ISO C形式的宏定义完全一样,只是看起来更具阅读性。

args跟后面的三个点可以连在一起,也可以用空格分开,当然这个宏同样不支持零变参的情形,原因同上。

GNU C的变参宏

  • 除了前面提到的可以为变参命名之外,GNU 预处理器CPP对ISO C的变参宏还进行了进一步的扩展,使之能处理零变参个数的情况。
  • 举例来说,以下这个语句在ISO C编译器中编译时是错的:​​debug(“A message”)​​。在ISO C中不允许省略所有的变参,因为在这个字符串之后缺少了一个逗号”,”。
  • GNU预处理器CPP允许你省略全部的变参,方法是在变参前加上黏贴符“##”:
#define debug(format,…) fprintf(stderr, format,  ##__VA_ARGS__)

或者

#define  debug(format, args…)  fprintf(stderr, format, ##args)

这样,当我们省略变参的时候黏贴符能自动清除前面多余的逗号。

  • 另外,在宏里面,除了两个井号 ## 可以作为黏贴符之外,其实一个井号 # 也可以用来黏贴符号,但是它要被用在字符串当中,例如:
#define prt(n) printf("calculate i'n: "  "i#n  " = %d, with parameter %d/n",  i##n, n)

int i = 1;
int i8 = 800, i9 = 900;
prt(8); prt(9);

执行的结果如下:

calculate i'n: i8 = 800, with parameter 8
calculate i'n: i9 = 900, with parameter 9
  • 在字符串中,我们可以用一个井号来黏贴宏参数,就像上面我们看到的那样。其中字符串与黏贴字符之间的空格是可选的,预处理器会自动去掉多余的空格。

总结:在上面的例子中:

#define prt(n) printf("calculate i'n: "  "i#n  " = %d\n",  i##n)
  • 故意在四个地方都用到了标识符n(那个转义换行符'/n'不在讨论范围内),依次分析是:
    1.在字符串中直接出现的“宏参数”实际上并不会被当成参数,而是一个普通的字符n;
    2.如果要解决第一个问题,那就要在字符串当中使用一个井号 # 来黏贴宏参数;
    3.不在字符串当中,要黏贴宏参数,则需要两个井号 ## 来黏贴;
    4.不在字符串中,如果直接出现宏参数,则预处理器将进行宏展开。