Linux内核必须使用GNU的GCC编译器来编译,而GCC提供了很多的C语言扩展,这些扩展对优化、目标代码布局、更安全的检查等提供了很强的支持。因此,内核代码所使用的C语法并不完全符合ANSI
C标准,实际上,只要有可能,内核开发者总是要用到GCC提供的C语言扩展部分。所以特意找了几个常用的特性总结下。
1、语句内嵌表达式(statement-embedded expression)
在gnu c 中,用括号将复合语句括起来也形成了表达式。他允许你在一个表达式内使用循环,跳转和局部变量。
一个复合语句是用大括号{}括起来的一组语句。在包含语句的表达式这种结构中,再用括号( )将大括号括起来
例如:
({ int y = foo (); int z;
if (y > 0) z = y;
else z = - y;
z; }
)
就是一个合法表达式,用于计算foo( )函数返回值的绝对值。
在上面的复合语句中,最后的一句必须是一个以分号结尾的表达式。这个表达式代表了整个结构的值。
如果你在大括号里的最后一句用的是其他的语句,则整个结构的返回类型为void,即没有合法的返回值。
这种特性使得宏定义变得更加安全(因为每个操作数都只被计算一次,例如++运算)。例如计算最大值通常在c语言中被定义为这样的宏:
#define max(a,b) ((a) > (b) ? (a) : (b))
但是其中的a和b可能会被计算两次,如果操作数带有副作用,则会产生错误的结果。
在gnu c中,如果你知道了操作数的类型(假设为int),你可以这样安全的定义宏:
#define maxint(a,b) \
({int _a = (a), _b = (b); _a > _b ? _a : _b; })
内核中做法:
#define min_t(type,x,y) \
({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
#define max_t(type,x,y) \
({ type __x = (x); type __y = (y); __x > __y ? __x: __y; })
使用语句表达式只计算参数一次,避免了可能的错误。
语句内嵌在常量表达式(例如枚举类型),位域尺寸或静态变量初始化中是不允许的。
如果你不知道操作数的类型,你也可以使用typeof来获得类型。
典型用法用下单链表pop操作:
#define _$slist_pop(H,L)\
({\
typeof((H)->L) _p$ = (H)->L;\
if( _p$){ (H)->L = _p$->next; _p$->next = NULL;} _p$;\
})
使用 typeof 获得类型、最后一个 _p$ 用于返回值。
2、可变参数宏 __VA_ARGS__ 常用: ##__VA_ARGS__
举例如下:
#define err_log(fmt, ...) fprintf (stderr, fmt, __VA_ARGS__)
其中的"…"表示可变参数,实际调用时,它们会替代宏体里的__VA_ARGS__。
但在fprintf的参数列表中最后的逗号后面没有参数。在编译时就会报错。
此时可如此定义:
#define err_log(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__)
"##"主要针对参数为空的情况。既然称为可变参数,那传递空参数也是可以的。如果没有使用"##",传递空参数时,比如:
err_log("A null message");
宏展开后,其中的字符串后面会多个多余的逗号,而"##"则会使预处理器去掉这个多余的逗号。
还有如下的宏方便用于调试:
__FILE__ 宏在预编译时会替换成当前的源文件名
__LINE__宏在预编译时会替换成当前的行号
__FUNCTION__宏在预编译时会替换成当前的函数名称
3、GCC 中零长数组
GCC 中允许使用零长数组,把它作为结构体的最后一个元素非常有用.
struct line {
int length;
char contents[0];
};
struct line *thisline = (struct line *) malloc (sizeof (struct line) + this_length);
thisline->length = this_length;
零长数组在有固定头部的可变对象上非常适用,我们可以根据对象的大小动态地去分配结构体的大小。
作为零长度数组的原始实现的神奇之处,sizeof被赋值为0。sizeof (struct line) = 4B
4、标号元素
在标准C里,数组或结构变量的初始化值必须以固定的顺序出现,而在GCC中,通过指定索引或结构域名,
则允许初始化值以任意顺序出现。
指定数组索引的方法是在初始化值前写"[INDEX] =",还可以使用"[FIRST ... LAST] ="的形式指定一个范围
int platform_intr_list[ACPI_MAX_PLATFORM_INTERRUPTS] = {
[0 ... ACPI_MAX_PLATFORM_INTERRUPTS - 1] = -1
};
将数组platform_intr_list的任何元素都初始化为-1
对于结构初始化,比如:
const struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read = do_sync_read,
.write = do_sync_write,
}
将结构ext2_file_operations的元素llseek初始化为generic_file_llseek,当结构体的定义变化导致元素的偏移位置改变时,仍然可以确保已知元素的正确性。
5、特殊属性(__attribute__)
GCC允许声明函数、变量和类型的特殊属性,以便指示编译器进行特定方面的优化和更仔细的代码检查。使用方式为在声明后加上:
attribute__ (( ATTRIBUTE ))
其中ATTRIBUTE是属性的说明,多个说明之间以逗号分隔.
//=========变参调用的编译检查宏==========
#ifndef _AS_PRINTF
#if defined(__GNUC__)
# define _$PRINTF(m,n) __attribute__ ((__format__ (__printf__, (m), (n))))
# define _$SCANF(m,n) __attribute__ ((__format__ (__scanf__, (m), (n))))
#else
# define _$PRINTF(m,n)
# define _$SCANF(m,n)
#endif
#endif
=========================================================
顺便对比了一下逗号表达式与语句内嵌表达式的区别:
逗号运算符,优先级别最低,逗号表达式的运算规则是从左向右依此计算,把最后一个表达式的值作为整个表达式的值.
举例说明:
main()
{
int x,y,z;
x=y=1;
z=x++,y++,++y;
printf("%d,%d,%d\n",x,y,z);
}
记住逗号运算符优先级最低、所以等价:(z=x++),y++,++y; 答案: x(2),y(3),z(1)
(a = 3,b = 5,b+ = a,c = b* 5),求逗号表达式的值? 40
func(rec1,rec2+rec3,(rec4,rec5));该函数调用语句中,含有的实参个数是
C语言中规定,函数调用时实参与实参之间是用逗号隔开的,其中第一个实参是rec1,第二个实参是rec2+rec3,
第三个实参是(rec4,rec5),这里的第三个实参就是一个逗号表达式,根据逗号表达式的运算规则,
第三个实参的值应该等于rec5的值。
fun(a+b,(x,y),fun(n+k,d,(a,b))); 在此函数调用语句中实参的个数是 3