当前位置: 首页 >> 程序设计 >> Linux 内核使用的 GNU GCC 的C 扩展
 

Linux 内核使用的 GNU GCC 的C 扩展

作者:      来源:linuxforum.net     发表时间:2006-03-16     浏览次数:      字号:    

===========================

Linux 内核使用的 GNU C 扩展

===========================

GNC CC 是一个功能非常强大的跨平台 C 编译器,它对 C 语言提供了很多扩展,

这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把

支持 GNU 扩展的 C 语言称为 GNU C。

Linux 内核代码使用了大量的 GNU C 扩展,以至于能够编译 Linux 内核的唯一编译器是 GNU CC,以前甚至出现过编译 Linux 内核要使用特殊的 GNU CC 版本的情况。本文是对 Linux 内核使用的 GNU C 扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看gcc.info。文中的例子取自 Linux 2.4.18。

语句表达式
==========

GNU C 把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出

现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本

只能在复合语句中使用。例如:



++++ include/linux/kernel.h

159: #define min_t(type,x,y)

160:         ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })

++++ net/ipv4/tcp_output.c

654:         int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));



复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。

这里定义了一个安全的求最小值的宏,在标准 C 中,通常定义为:



#define min(x,y) ((x) < (y) ? (x) : (y))



这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,使用

语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。





Typeof

======



使用前一节定义的宏需要知道参数的类型,利用 typeof 可以定义更通用的宏,不

必事先知道参数的类型,例如:



++++ include/linux/kernel.h

141: #define min(x,y) ({

142:         const typeof(x) _x = (x);      

143:         const typeof(y) _y = (y);      

144:         (void) (&_x == &_y);           

145:         _x < _y ? _x : _y; })



这里 typeof(x) 表示 x 的值类型,第 142 行定义了一个与 x 类型相同的局部变

量 _x 并初使化为 x,注意第 144 行的作用是检查参数 x 和 y 的类型是否相同。

typeof 可以用在任何类型可以使用的地方,通常用于宏定义。





零长度数组

==========



GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例

如:



++++ include/linux/minix_fs.h

85: struct minix_dir_entry {

86:         __u16 inode;

87:         char name[0];

88: };



结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要

定义数组长度为 1,分配时计算对象大小比较复杂。





可变参数宏

==========



在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:



++++ include/linux/kernel.h

110: #define pr_debug(fmt,arg...)

111:         printk(KERN_DEBUG fmt,##arg)



这里 arg 表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构

成 arg 的值,在宏扩展时替换 arg,例如:



    pr_debug("%s:%d",filename,line)



扩展为



    printk("<7>" "%s:%d", filename, line)



使用 ## 的原因是处理 arg 不匹配任何参数的情况,这时 arg 的值为空,GNU

C 预处理器在这种特殊情况下,丢弃 ## 之前的逗号,这样



    pr_debug("success!n")



扩展为



    printk("<7>" "success!n")



注意最后没有逗号。





标号元素

========



标准 C 要求数组或结构变量的初使化值必须以固定的顺序出现,在 GNU C 中,通

过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在

初始化值前写 '[INDEX] =',要指定一个范围使用 '[FIRST ... LAST] =' 的形式,

例如:



+++++ arch/i386/kernel/irq.c

1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };



将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。



要指定结构元素,在元素值前写 'FIELDNAME:',例如:



++++ fs/ext2/file.c

41: struct file_operations ext2_file_operations = {

42:         llseek:         generic_file_llseek,

43:         read:           generic_file_read,

44:         write:          generic_file_write,

45:         ioctl:          ext2_ioctl,

46:         mmap:           generic_file_mmap,

47:         open:           generic_file_open,

48:         release:        ext2_release_file,

49:         fsync:          ext2_sync_file,

50 };



将结构 ext2_file_operations 的元素 llseek 初始化为 generic_file_llseek,

元素 read 初始化为 genenric_file_read,依次类推。我觉得这是 GNU C 扩展中

最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然

保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。





Case 范围

=========



GNU C 允许在一个 case 标号中指定一个连续范围的值,例如:



++++ arch/i386/kernel/irq.c

1062:                         case '0' ... '9': c -= '0'; break;

1063:                         case 'a' ... 'f': c -= 'a'-10; break;

1064:                         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':





声明的特殊属性

==============



GNU C 允许声明函数、变量和类型的特殊属性,以便手工的代码优化和更仔细的代

码检查。要指定一个声明的属性,在声明后写



    __attribute__ (( ATTRIBUTE ))



其中 ATTRIBUTE 是属性说明,多个属性以逗号分隔。GNU C 支持十几个属性,这

里介绍最常用的:



* noreturn



属性 noreturn 用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的

代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:



++++ include/linux/kernel.h

47: # define ATTRIB_NORET  __attribute__((noreturn))

....

61: asmlinkage NORET_TYPE void do_exit(long error_code)

        ATTRIB_NORET;



* format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)



属性 format 用于函数,表示该函数使用 printf, scanf 或 strftime 风格的参

数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以

让编译器根据格式串检查参数类型。例如:



++++ include/linux/kernel.h?

89: asmlinkage int printk(const char * fmt, ...)

90:         __attribute__ ((format (printf, 1, 2)));



表示第一个参数是格式串,从第二个参数起根据格式串检查参数。



* unused



属性 unused 用于函数和变量,表示该函数或变量可能不使用,这个属性可以避免

编译器产生警告信息。



* section ("section-name")



属性 section 用于函数和变量,通常编译器将函数放在 .text 节,变量放在

.data 或 .bss 节,使用 section 属性,可以让编译器将函数或变量放在指定的

节中。例如:



++++ include/linux/init.h

78: #define __init          __attribute__ ((__section__ (".text.init")))

79: #define __exit          __attribute__ ((unused, __section__(".text.exit")))

80: #define __initdata      __attribute__ ((__section__ (".data.init")))

81: #define __exitdata      __attribute__ ((unused, __section__ (".data.exit")))

82: #define __initsetup     __attribute__ ((unused,__section__ (".setup.init")))

83: #define __init_call     __attribute__ ((unused,__section__ (".initcall.init")))

84: #define __exit_call     __attribute__ ((unused,__section__ (".exitcall.exit")))



连接器可以把相同节的代码或数据安排在一起,Linux 内核很喜欢使用这种技术,

例如系统的初始化代码被安排在单独的一个节,在初始化结束后就可以释放这部分

内存。



* aligned (ALIGNMENT)



属性 aligned 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对

[1] [2] [3]

责任编辑 webmaster

 
 
 
 
 
评论更多>>
 
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •