现在的位置: 首页 > 综合 > 正文

GNU C扩展(一)

2017年12月12日 ⁄ 综合 ⁄ 共 4472字 ⁄ 字号 评论关闭

。。。

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

一:柔性数组(flexible array) 
柔性数组也称为零长度数组,或者零长度数组。这种数组通常在结构体当中出现,它本身并不占用空间,但是有了柔性数组意味着结构体的尺寸充满了变数。
例如以下例子:
struct  usb_interface_cache {
        unsigned  num_altsetting;  // number of alternate settings
        struct  kref  ref;
         /* variablee-length array of alternate settings for this           
             interface, stored usb_host_interface altsetting[0] */
         struct usb_host_interface  altsetting[0];
};
定义了包含柔性数组的结构,可以在随后的实例化过程中通过具体所需来动态地改变结构体的大小,例如:
struct  usb_interface_cache *p = /
           malloc(sizeof(struct usb_interface_cache) + datasize);
其中,datasize就是你需要扩展的内存大小,可以用来存放结构体数据,此时对这部分内存的引用,就可以用以下方式:
p->altsetting[ i ];
这样,就能通过标识符altsetting来访问紧邻结构体成员之后的内存了。用这种方法可以根据具体情况方便地扩展结构体的大小。

二、标号元素
在标准C里,数组或者结构变量的初始化值必须以固定的顺序出现,而在GCC中,通过制定索引或者结构域名,则允许初始化值以任意顺序出现。
指定数组索引的方法是在初始化值前面写“[INDEX]=”,还可以使用“[FIRST … LAST]=”的形式指定一个范围。比如:
int  array[20] = {[2] = 100, [10 … 19] = 200};
对于结构体初始化,比如:
struct  person  Bill = {
                  .name = “Bill Gates”;
                  .age = 50;
                  .wealth’s world ranking = 1;
};
将结构Bill的元素name初始化为”Bill Gates”, 元素age初始化为50,依此类推。
使用这种形式,当结构体的定义变化导致元素的偏移位置改变时,仍然可以确保已知元素的正确性。对于未出现在初始化中的元素,其初值为0。

三、case范围
在GCC中,你可以在case标记后面指定一个连续值,例如:
case  low … high:
这种写法等价于把每个值独立成一个个case标记的情况:
case  low:
case  low+1:
… …
case  high:
这个特性对于要写连续的ASCII码值的时候特别有用:
case  ‘A’ … ‘Z’ :
注意:在 “…” 的左右两边一定要有空格,否则编将有词法错误。

四、语句表达式(statement-embedded expression)
GCC 把包含在括号中的复合语句看作是一个表达式,称为语句表达式,它允许在一个表达式内使用循环、跳转、局部变量甚至函数调用,并可以出现在任何允许表达式出现的地方。
位于括号中的复合语句的最后一句必须是一个以分号结尾的表达式。它的值将成为这个语句表达式的值。
例如,以下是一个语句表达式:
({int y = foo();   int z;
   if(y > 0)  z = y;   else  z = -y;
   z;})

语句表达式(statement-embedded expression)
计算极值通常被定义为:
#define   MAX(x, y)    (((x)>(y)) ? (x) : (y)) 
#define   MIN(x, y)    (((x)<(y)) ? (x) : (y))
但是其中的x和y可能会分别被计算两次。当参数x和y带有副作用时,将会产生错误的结果。而内核则使用语句表达式将其定义为:
#define  MAX(x, y)  ({ typeof(x)  _x = x;  typeof(y)  _y = y;
                                    void (&_x == &_y);
                                    (((_x)<(_y)) ? (_x) : (_y))     })
其中,typeof关键字用来获取变量的类型。中间的void (&_x == &_y);用来判断x和y的类型是否相同的(如果相同则没有反应,如果不相同则他们的地址类型是不同的,会有警告)。

五、变参宏
在ISO C99里,一个宏可以被声明为带可变的参数个数,就像函数一样。语法如下:
#define  debug(format, …)  fprintf(stderr, format, __VA_ARGS__)
这里的 “…” 代表变参,在引用宏debug的地方它代表着零个或多个相应的标识符,包括逗号。这些标识符将会替换__VA_ARGS__。(但是这样的宏不能处理零变参的情况,否则编译不会通过,因为零变参的时候会多一个逗号)

GCC 支持变参宏,并且提供另一种词法来定义它,即可赋予变参名称,就像普通参数一样:
#define  debug(format, args…)  fprintf(stderr, format, args)
这种用法与上面所述的ISO C形式的宏定义完全一样,只是看起来更具阅读性。(args跟后面的三个点可以连在一起,也可以用空格分开,当然这个宏同样不支持零变参的情形,原因同上)

除了前面提到的可以为变参命名之外,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'不在讨论范围内),依次分析是:
第一:在字符串中直接出现的“宏参数”实际上并不会被当成参数,而是一个普通的字符n;第二:如果要解决第一个问题,那就要在字符串当中使用一个井号 # 来黏贴宏参数;第三,不在字符串当中要黏贴宏参数,则需要两个井号 ## 来黏贴;第四,不在字符串中,如果直接出现宏参数,则预处理器将进行宏展开。

六、局部标签
GCC允许你在任何内嵌代码块中声明局部标签,所谓的局部标签跟普通的标签用法一样(用在goto语句或者被获取地址),只不过你只能在声明它的代码块中使用。
局部标签的声明如下:
__label__  label;       或者
__label__  label1, label1, … ;
局部标签声明只是定义了标签的名字,但是并没有定义标签本身,它本身必须像普通标签那样在语句内嵌表达式内部使用局部标签。
另外要注意的是,局部标签的声明必须在代码块的起始位置(即位于任何其他声明和语句之前)
在复杂的宏定义中,局部标签显得尤为有用。如果一个宏包含有内嵌循环,goto语句可以方便地跳出它们。

然而,拥有整个函数作用域的普通标签在这里不能被使用,因为该宏可能会在一个函数中被展开若干次,那样的话同样的一个标签就会被重复定义。局部标签就是用来避免这种情况的,例如:#define SEARCH(value, array, target) 
do {
      __label__ found;
      typeof (target) _SEARCH_target = (target);   
      typeof (*(array)) *_SEARCH_array = (array);int i, j; int value; 
      for (i = 0; i < max; i++)
           for (j = 0; j < max; j++) 
                if (_SEARCH_array[j] == _SEARCH_target) 
                      { (value) = i; goto found; } 
      (value) = -1;
      found:;
} while (0)

当然,也可以用语句表达式改写这个宏定义:
#define SEARCH(value, array, target) 
({    __label__ found;
      typeof (target) _SEARCH_target = (target);   
      typeof (*(array)) *_SEARCH_array = (array);int i, j; int value; 
      for (i = 0; i < max; i++)
           for (j = 0; j < max; j++) 
                if (_SEARCH_array[j] == _SEARCH_target) 
                      { (value) = i; goto found; } 
      (value) = -1;
      found:
             value;
})
局部标签在内嵌函数(如果有)中也是可见的。 (小细节:注意到我们用语句表达式的时候,在最后的found局部标签后面有个语句 value;  而在do ... while循环中并无出现,原因是语句表达式的值取决于最后的表达式,而do ... while 循环中的found局部标签仅仅用来跳出循环)

。。。

抱歉!评论已关闭.