GNUC官方文档:
linux内核中大量采用了GNUC对标准C的扩展语法。这里列举了较为常见的扩展语法。
语句表达式
在GNU C 中,用括号将复合语句括起来也形成了表达式。他允许你在一个表达式内使用循环,跳转和局部变量。
一个复合语句是用大括号{}括起来的一组语句。在包含语句的表达式这种结构中,再用括号( )将大括号括起来,例如:
({ int y = foo (); int z;
if (y > 0) z = y;
else z = - y;
z; })
就是一个合法表达式,用于计算foo( )函数返回值的绝对值。
在上面的复合语句中,最后的一句必须是一个以分号结尾的表达式。这个表达式代表了整个结构的值。如果你在大括号里的最后一句用的是其他的语句,则整个结构的返回类型为void,即没有合法的返回值。
这种特性使得宏定义变得更加安全(因为每个操作数都只被计算一次,例如++运算)。
主要应用于复杂逻辑的宏定义。
详细可见GNUC官方文档6.1节。
typeof
引用表达式类型的另一种方法是使用typeof。使用此关键字的语法类似于sizeof,但该构造在语义上的行为类似于使用typedef定义的类型名称。
主要应用在宏定义中,便于引用宏参数的类型,通常和语句表达式结合使用。
详细可见GNUC官方文档6.7节。
内核代码举例:
linux4.x内核文件include/linux/kernel.h 有关max和min的定义如下:
__UNIQUE_ID宏定义主要是为了保证变量名称的唯一性。
(void)(&min1 == &min2);这一语句主要是为了检查min1和min2类型是否相同。当min1和min2类型不相同时GCC编译器会有如下警告:
warning: comparison of distinct pointer types lacks a cast
零长度数组
GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例
如:
结构的最后一个元素定义为零长度数组,它不占结构的空间。
零长度数组非常明显的一个好处就是便于内存管理,可以有效避免二次分配内存带来的内存泄露的隐患。
详细可见GNUC官方文档6.18节。
可变参数宏
在 GNU C 中,宏可以接受可变数目的参数,就象函数一样。例如:
这里 args 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构 成 args 的值,在宏扩展时替换 args。值得注意的是在扩展args时是配合“##”一起使用的,原因在于当args为空时此时由于宏扩展操作会有额外的“,”产生,使用“##”可以消除它。
详细可见GNUC官方文档6.21节。
标号元素
标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通 过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在 初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式。例如:
int widths[] = { [0 ... 9] = 1, [10 ... 99] = 2, [100] = 3 };
在结构初始值设定项中,指定要在元素值之前使用“.fieldname =”初始化的字段的名称,例如:
使用这种形式,当结构体的定义变化导致元素的偏移位置改变时,仍然可以确保已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。
详细可见GNUC官方文档6.29节。
case范围
GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:
其中case 1 ... 7: 相当于case 1: case 2: case 3: case 4: case 5: case 6: case 7:
需要注意的是在“...”周围需要键入空格符,否则会出现解析错误。
详细可见GNUC官方文档6.30节。
__attribute__特殊属性声明
GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写 attribute (( ATTRIBUTE ))其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持很多的属性,这里介绍最常用的:
* noreturn
属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:
# define ATTRIB_NORET __attribute__((noreturn))....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
* format
属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以让编译器根据格式串检查参数类型。例如:
asmlinkage int printk(const char * fmt, ...) __attribute__ ((format (printf, 1, 2)));
表示第一个参数是格式串,从第二个参数起根据格式串检查参数。
* unused
属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
* aligned
属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对齐量,以字节为单位,例如:
struct example_struct
{
char a;
int b;
long c;
} __attribute__((aligned(4)));
表示该结构类型的变量以4字节对界。
* packed
属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用
于枚举、结构或联合类型时表示该类型使用最小的内存。例如:
struct example_struct
{
char a;
int b__attribute__ ((packed));
long c__attribute__((packed));
};
对于结构体example_struct而言,在i386平台下,其sizeof的结果为9,如果删除其中的2个__attribute__((packed)),其sizeof将为12.
__builtin_内建函数
GCC提供了一系列的builtin函数,可以实现一些简单快捷的功能来方便程序编写,另外,很多builtin函数可用来优化编译结果。这些函数以“__builtin_”作为函数名前缀。下面例举一些内核中常见的内建函数并加以说明:
* __builtin_return_address(n):当前函数的第n级调用者的地址,用的最多的就是__builtin_return_address(0),即获得当前函数的调用者的地址。注意,该函数实现是体系结构相关的,有些体系结构只实现了n=0的返回结果。
* void __builtin_prefetch (const void *addr, ...):它通过对数据进行预取的方法,在使用地址addr的值之前就将其放到cache中,减少了延迟,从而提高了性能,但该函数也需要 CPU 的支持。该函数可接受三个参数,第一个参数addr是要预取的数据的地址,第二个参数可设置为0或1(1表示我对地址addr要进行写操作,0表示要进行读操作),第三个参数可取0-3(0表示不用关心时间局部性,取完addr的值之后便不用留在cache中,而1、2、3表示时间局部性逐渐增强)。
* int __builtin_constant_p(exp):可以用来确定在编译时某个值是否是常量,如果是,则返回1,否则返回0。返回0并不表示该值不是常量,而仅表示GCC无法证明它是具有-O选项的指定值的常量。在内存为关键资源的嵌入式系统开发中会经常用到此内联函数。
* long __builtin_expect(long exp, long c): 可以用来为编译器提供分支预测信息,返回值为exp的值,它应该是一个整数表达式,内置语意是期望exp == c 。编译器获取分支预测信息后可以优化程序编译时的指令序列,使其尽可能的顺序执行,以提高CPU的运行效率。第二个参数c可以取0和1。例如:
if (__builtin_expect (x, 0))
foo ();
表示x的值大部分情况下可能为0(默认概率为90%),因此foo()函数得到执行的机会比较少。
由于第二个参数只能取整数,所以如果要判断指针或字符串,可以像下面这样写:
if (__builtin_expect (ptr != NULL, 1))
foo (*ptr);
表示ptr一般不会为NULL,所以foo函数得到执行的概率较大。
* long __builtin_expect_with_probability(long exp, long c, double probability): 此内建函数是对__builtin_expect的补充,可以利用第三个参数来设置事件发生的概率,其值必须0.0到1.0(包含0.0和1.0)之间的浮点值。
......
GNUC支持的内建函数还有很多,可以在其官方文档中找到详细说明。
关于其它的扩展特性亦可以在GNU官方文档相关章节找到详细说明,需要不断学习和积累。
共勉。