今天看了一下午的linux内核编程方面的内容,发现linux内核中GNUC与标准C有一些差别,特记录如下: linux系统上可用的C编译器是GNUC编译器,它建立在自由软件基金会的编程许可证的基础上,因此可以自由发布。GNU C对标准C进行进一步扩展,以增强标准C的功能。下面我们对GNUC中的扩展进行一下总结: 1、零长度数组 GNUC 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如: structminix_dir_entry { __u16 inode; charname[0]; }; 结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准C 中则需要定义数组长度为1,分配时计算对象大小比较复杂。 2、case范围 GNUC 允许在一个 case标号中指定一个连续范围的值,例如: case '0' ...'9': c -= '0'; break; case 'a' ... 'f': c -= 'a'-10; break; case'A' ... 'F': c -= 'A'-10; break; 其中case'0' ... '9': 相当于 case'0': case '1': case '2': case '3': case '4': case '5':case '6': case '7': case '8': case '9': 3、语句表达式 GNUC把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。例如: #definemin_t(type,x,y) \ ({ type __x = (x); type __y = (y);__x < __y ? __x: __y; }) 复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。这里定义了一个安全的求最小值的宏,在标准C 中,通常定义为: #define min(x,y) ((x) < (y) ? (x) : (y)) 这个定义计算x 和y分别两次,当参数有副作用时(比如出现参数自增或自减语句时),将产生不正确的结果,使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。 4、typeof关键字 使用前一节定义的宏需要知道参数的类型,利用typeof可以定义更通用的宏,不 必事先知道参数的类型,例如: #definemin(x,y) ({ \ const typeof(x) _x = (x); \ const typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x < _y ? _x : _y; }) 这里typeof(x) 表示x 的值类型,const typeof(x)_x = (x); 中定义了一个与 x类型相同的局部变量 _x并初使化为 x,(void) (&_x== &_y); 的作用是检查参数 x和 y的类型是否相同。typeof可以用在任何类型可以使用的地方,通常用于宏定义。 5、可变参数的宏 在GNU C中,宏可以接受可变数目的参数,就象函数一样,例如: #definepr_debug(fmt,arg...) \ printk(fmt,##arg) 这里arg表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成arg 的值,在宏扩展时替换arg,例如: pr_debug("%s:%d",filename,line) 会被扩展为 printk("%s:%d",filename, line) 使用 ##的原因是处理 arg不匹配任何参数的情况,这时arg 的值为空,GNUC 预处理器在这种特殊情况下,丢弃## 之前的逗号,这样 pr_debug("success!\n") 会被扩展为 printk("success!\n") 而不是printk("success!\n",) 注意最后的逗号。 6、标号元素 标准C要求数组或结构变量的初使化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写'[INDEX]=',要指定一个范围使用 '[FIRST... LAST] =' 的形式,例如: staticunsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL }; 将数组的所有元素初使化为~0UL,这可以看做是一种简写形式。 要指定结构元素,在元素值前写'FIELDNAME:',例如: structfile_operations ext2_file_operations = { llseek: generic_file_llseek, read: generic_file_read, write: generic_file_write, ioctl: ext2_ioctl, mmap: generic_file_mmap, open: generic_file_open, release: ext2_release_file, fsync: ext2_sync_file, }; 将结构ext2_file_operations的元素 llseek初始化为 generic_file_llseek,元素read 初始化为genenric_file_read,依次类推。我觉得这是GNU C扩展中最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然保证已知元素的正确性。对于未出现在初始化中的元素,其初值为0。 7、当前函数名 GNUCC 预定义了两个标志符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY_FUNCTION__保存带语言特色的名字。在 C函数中,这两个名字是相同的,在C++函数中,__PRETTY_FUNCTION__包括函数返回类型等额外信息,Linux内核只使用了 __FUNCTION__。 voidexample() { printf{“This is function:%s”,__FUNCTION__}; } 代码中__FUNCTION__意味着字符串“example”。 8、特殊属性声明 GNUC 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代码检查。要指定一个声明的属性,在声明后写 __attribute__ ((ATTRIBUTE ))其中 ATTRIBUTE是属性说明,多个属性以逗号分隔。GNUC 支持十几个属性,这里介绍最常用的: * noreturn 属性 noreturn用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如: # defineATTRIB_NORET __attribute__((noreturn)).... asmlinkageNORET_TYPE void do_exit(long error_code) ATTRIB_NORET; * format 属性 format用于函数,表示该函数使用printf, scanf 或strftime风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定format属性可以让编译器根据格式串检查参数类型。例如: asmlinkageint printk(const char * fmt, ...) __attribute__ ((format(printf, 1, 2))); 表示第一个参数是格式串,从第二个参数起根据格式串检查参数。 * unused 属性unused用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。 * aligned 属性aligned用于变量、结构或联合类型,指定变量、结构域、结构或联合的对齐量,以字节为单位,例如: structexample_struct { char a; int b; long c; }__attribute__((aligned(4))); 表示该结构类型的变量以4字节对界。 *packed 属性 packed用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用 于枚举、结构或联合类型时表示该类型使用最小的内存。例如: structexample_struct { char a; int b__attribute__ ((packed)); longc__attribute__((packed)); }; 对于结构体example_struct而言,在i386平台下,其sizeof的结果为9,如果删除其中的2个—attribute__((packed)),其sizeof将为12. 9、内建函数 GNUC 提供了大量的内建函数,其中很多是标准C库函数的内建版本,例如memcpy,它们与对应的C库函数功能相同,不属于库函数的其他内建函数的名字通常以__builtin开始。例如: *__builtin_return_address (LEVEL) 内建函数__builtin_return_address返回当前函数或其调用者的返回地址,参数LEVEL指定在栈上搜索框架的个数,0表示当前函数的返回地址,1表示当前函数的调用者的返回地址,依此类推。 *__builtin_constant_p(EXP) 内建函数__builtin_constant_p用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回 1,否则返回0。 *__builtin_expect(EXP, C) 内建函数__builtin_expect用于为编译器提供分支预测信息,其返回值是整数表 达式EXP 的值,C的值必须是编译时常数。 例如,下面的代码检测第一个参数是否为编译时常数以确定采用参数版本还是非参数版本代码: #definetest_bit(nr,addr) \ (__builtin_constant_p(nr) ? \ constant_test_bit((nr),(addr)) : \ variable_test_bit((nr),(addr)))