C语言GNU扩展语法.docx
《C语言GNU扩展语法.docx》由会员分享,可在线阅读,更多相关《C语言GNU扩展语法.docx(26页珍藏版)》请在冰豆网上搜索。
C语言GNU扩展语法
GNUC9条扩展语法
GNCCC是一个功能非常强大的跨平台C编译器,它对标准C语言进展了一系列扩展,以增强标准C的功能,这些扩展对优化、目标代码布局、更平安的检查等方面提供了很强的支持。
本文把支持GNU扩展的C语言称为GNUC。
Linux内核代码使用了大量的GNUC扩展,以至于能够编译Linux内核的唯一编译器是GNUCC,以前甚至出现过编译Linux内核要使用特殊的GNUCC版本的情况。
本文是对Linux内核使用的GNUC扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文找到一个初步的解答,更详细的信息可以查看gcc.info。
文中的例子取自Linux2.4.18。
1、零长度和变量长度数组
GNUC允许使用零长度数组,在定义变长对象的头构造时,这个特性非常有用。
例如:
//include/linux/minix_fs.h
structminix_dir_entry
{
_u16inode;
charname[0];
};
构造的最后一个元素定义为零长度数组,它不占构造的空间。
在标准C中那么需要定义数组长度为1,分配时计算对象大小比拟复杂。
GNUC允许使用一个变量定义数组的长度,比方:
intn=0;
scanf("%d",&n);
intarray[n];
2、case范围
GNUC允许在一个case标号中指定一个连续范围的值,例如:
//arch/i386/kernel/irq.c
case'0'...'9':
c-='0';break;
case'a'...'f':
c-='a'-10;break;
case'A'...'F':
c-='A'-10;break;
3、语句表达式
GNUC把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。
例如:
//include/linux/kernel.h
#definemin_t(type,x,y)({type__x=(x);type__y=(y);__x<__y?
__x:
__y;})
//net/ipv4/tcp_output.c
intfull_space=min_t(int,tp->window_clamp,tcp_full_space(sk));
复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。
这里定义了一个平安的求最小值的宏,在标准C中,通常定义为:
#definemin(x,y)((x)<(y)?
(x):
(y))
这个定义计算x和y分别两次,当参数有副作用时,将产生不正确的结果,使用语句表达式只计算参数一次,防止了可能的错误。
语句表达式通常用于宏定义。
4、typeof关键字
使用前一节定义的宏需要知道参数的类型,利用typeof可以定义更通用的宏,不必事先知道参数的类型,例如:
//include/linux/kernel.h
#definemin(x,y)({/
consttypeof(x)_x=(x); /
consttypeof(y)_y=(y); /
(void)(&_x==&_y); /
_x<_y?
_x:
_y;})
这里typeof(x)表示x的值类型,第3行定义了一个与x类型一样的局部变量_x并初使化为x,注意第5行的作用是检查参数x和y的类型是否一样。
typeof可以用在任何类型可以使用的地方,通常用于宏定义。
5、可变参数宏
在GNUC中,宏可以承受可变数目的参数,就象函数一样,例如:
//include/linux/kernel.h
#definepr_debug(fmt,arg...)printk(KERN_DEBUGfmt,##arg)
这里arg表示其余的参数,可以是零个或多个,这些参数以及参数之间的逗号构成arg的值,在宏扩展时替换arg,例如:
pr_debug("%s:
%d",filename,line)
扩展为
printk("<7>""%s:
%d",filename,line)
使用##的原因是处理arg不匹配任何参数的情况,这时arg的值为空,GNUC预处理器在这种特殊情况下,丢弃##之前的逗号,这样
pr_debug("success!
/n")
扩展为
printk("<7>""success!
/n")
注意最后没有逗号。
6、标号元素
标准C要求数组或构造变量的初使化值必须以固定的顺序出现,在GNUC中,通过指定索引或构造域名,允许初始化值以任意顺序出现。
指定数组索引的方法是在初始化值前写'[INDEX]=',要指定一个范围使用'[FIRST...LAST]='的形式,例如:
//arch/i386/kernel/irq.c
staticunsignedlongirq_affinity[NR_IRQS]={[0...NR_IRQS-1]=~0UL};
将数组的所有元素初使化为~0UL,这可以看做是一种简写形式。
要指定构造元素,在元素值前写'FIELDNAME:
',例如:
//fs/ext2/file.c
structfile_operationsext2_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,依次类推。
我觉得这是GNUC扩展中最好的特性之一,当构造的定义变化以至元素的偏移改变时,这种初始化方法仍然保证元素的正确性。
对于未出现在初始化中的元素,其初值为0。
标准C要求数组或构造体的初始化值必须以固定的顺序出现,在GNUC中,通过指定索引或构造体成员名,允许初始化以任意顺序出现。
unsignedchardata[MAX]={
[0]=10,
[10]=100,
};
structfile_operationsext2_file_operations={
open:
ext2_open,
close:
ext2_close,
};
在linux2.6中推荐如下方式:
structfile_operationsext2_file_operations={
.read=ext2_read,
.write=ext2_write,
};
7、当前函数名
GNUC中预定义两个标志符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY__FUNCTION__保存带语言特色的名字。
在C函数中这两个名字是一样的。
在C++函数中,__PRETTY_FUNCTION__包括函数返回类型等额外信息,Linux内核只使用了__FUNCTION__。
//fs/ext2/super.c
voidext2_update_dynamic_rev(structsuper_block*sb)
{
structext2_super_block*es=EXT2_SB(sb)->s_es;
if(le32_to_cpu(es->s_rev_level)>EXT2_GOOD_OLD_REV)
return;
ext2_warning(sb,__FUNCTION__,"updatingtorev%dbecauseofnewfeatureflag,""runninge2fsckisremended",EXT2_DYNAMIC_REV);
这里__FUNCTION__将被替换为函数名ext2_update_dynamic_rev。
虽然__FUNCTION__看起来类似于标准C中的__FILE__,但实际上__FUNCTION__是被编译器替换的,不象__FILE__被预处理器替换。
在C99中支持__func__宏,因此建议使用__func__替代__FUNCTION__
8、特殊属性声明
GNUC允许声明函数、变量和类型的特殊属性,以便进展手工的代码优化和定制代码检查的方法。
要指定一个声明属性,只需要在声明后添加__attribute__((ATTRIBUTE))。
其中ATTRIBUTE为属性说明,如果存在多个属性,那么以逗号分隔。
GNUC支持noreturn、format、section、aligned、packed等十个属性。
这里介绍最常用的:
noreturn属性用于函数,表示该函数从不返回。
这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比方未初使化的变量。
例如:
//include/linux/kernel.h
#defineATTRIB_NORET__attribute__((noreturn))....
asmlinkageNORET_TYPEvoiddo_exit(longerror_code)ATTRIB_NORET;
format(ARCHETYPE,STRING-INDEX,FIRST-TO-CHECK)属性用于函数,表示该函数使用printf,scanf或strftime风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定format属性可以让编译器根据格式串检查参数类型。
例如:
//include/linux/kernel.h?
asmlinkageintprintk(constchar*fmt,...)__attribute__((format(printf,1,2)));
表示第一个参数是格式串,从第二个参数起根据格式串检查参数。
unused属性用于函数和变量,表示该函数或变量可能不使用,这个属性可以防止编译器产生警告信息。
section("section-name")属性用于函数和变量,通常编译器将函数放在.text区,变量放在.data区或.bss区,使用section属性,可以让编译器将函数或变量放在指定的节中。
例如:
//include/linux/init.h
#define__init __attribute__((__section__(".text.init")))
#define__exit __attribute__((unused,__section__(".text.exit")))
#define__initdata __attribute__((__section__(".data.init")))
#define__exitdata __attribute__((unused,__section__(".data.exit")))
#define__initsetup __attribute__((unused,__section__(".setup.init")))
#define__init_call __attribute__((unused,__section__(".initcall.init")))
#define__exit_call __attribute__((unused,__section__(".exitcall.exit")))
连接器可以把一样节的代码或数据安排在一起,Linux内核很喜欢使用这种技术,例如系统的初始化代码被安排在单独的一个节,在初始化完毕后就可以释放这局部内存。
aligned(ALIGNMENT)属性用于变量、构造或联合类型,指定变量、构造域、构造或联合的对齐量,以字节为单位,例如:
//include/asm-i386/processor.h
structi387_fxsave_struct{
unsignedshort cwd;
......
}__attribute__((aligned(16)));
表示该构造类型的变量以16字节对齐。
通常编译器会选择适宜的对齐量,显示指定对齐通常是由于体系限制、优化等原因。
packed属性用于变量和类型,用于变量或构造域时表示使用最小可能的对齐,用于枚举、构造或联合类型时表示该类型使用最小的内存。
例如:
//include/asm-i386/desc.h
structXgt_desc_struct{
unsignedshortsize;
unsignedlongaddress__attribute__((packed));
};
域address将紧接着size分配。
属性packed的用途大多是定义硬件相关的构造,使元素之间没有因对齐而造成的空洞。
9、内建函数
GNUC提供了大量的内建函数,其中很多是标准C库函数的内建版本,例如memcpy,它们与对应的C库函数功能一样,本文不讨论这类函数,其他内建函数的名字通常以__builtin开场。
内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数调用者的返回地址,依此类推。
例如:
//kernel/sched.c
printk(KERN_ERR"schedule_timeout:
wrongtimeout""value%lxfrom%p/n",timeout,
__builtin_return_address(0));
内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回1,否那么返回0。
//include/asm-i386/bitops.h
#definetest_bit(nr,addr)/
(__builtin_constant_p(nr)?
/
constant_test_bit((nr),(addr)):
/
variable_test_bit((nr),(addr)))
很多计算或操作在参数为常数时有更优化的实现,在GNUC中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。
内建函数__builtin_expect(EXP,C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的值必须是编译时常数。
例如:
//include/linux/piler.h
#definelikely(x) __builtin_expect((x),1)
#defineunlikely(x) __builtin_expect((x),0)
//kernel/sched.c
if(unlikely(in_interrupt())){
printk("Schedulingininterrupt/n");
BUG();
}
这个内建函数的语义是EXP的预期值是C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。
上面的例子表示处于中断上下文是很少发生的,第6-7行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。
假设不想使用GNUC扩展,那么只需要在gcc参数后面加上-ansi-pedantic即可,使用上述参数后,所有GNCC扩展语法局部将会有编译警报。
linuxgcc的属性解析
GNUC的一大特色〔却不被初学者所知〕就是__attribute__机制。
__attribute__可以设置函数属性〔FunctionAttribute〕、变量属性〔VariableAttribute〕和类型属性〔TypeAttribute〕。
__attribute__书写特征是:
__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。
__attribute__语法格式为:
__attribute__((attribute-list))
其位置约束为:
放于声明的尾部“;〞之前。
函数属性〔FunctionAttribute〕
函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。
__attribute__机制也很容易同非GNU应用程序做到兼容之成效。
GNUCC需要使用–Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。
下面介绍几个常见的属性参数。
__attribute__format
该__attribute__属性可以给被声明的函数加上类似printf或者scanf的特征,它可以使编译器检查函数声明和函数实际调用参数之间的格式化字符串是否匹配。
该功能十分有用,尤其是处理一些很难发现的bug。
format的语法格式为:
format(archetype,string-index,first-to-check)
format属性告诉编译器,按照printf,scanf,strftime或strfmon的参数表格式规那么对该函数的参数进展检查。
“archetype〞指定是哪种风格;“string-index〞指定传入函数的第几个参数是格式化字符串;“first-to-check〞指定从函数的第几个参数开场按上述规那么进展检查。
具体使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中参数m与n的含义为:
m:
第几个参数为格式化字符串〔formatstring〕;
n:
参数集合中的第一个,即参数“…〞里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身〞的呢,后面会提到;
在使用上,__attribute__((format(printf,m,n)))是常用的,而另一种却很少见到。
下面举例说明,其中myprint为自己定义的一个带有可变参数的函数,其功能类似于printf:
//m=1;n=2
externvoidmyprint(constchar*format,...)__attribute__((format(printf,1,2)));
//m=2;n=3
externvoidmyprint(intl,constchar*format,...)__attribute__((format(printf,2,3)));
需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎〞了,例如:
//m=3;n=4
externvoidmyprint(intl,constchar*format,...)__attribute__((format(printf,3,4)));
其原因是,类成员函数的第一个参数实际上一个“隐身〞的“this〞指针。
〔有点C++根底的都知道点this指针,不知道你在这里还知道吗?
〕
这里给出测试用例:
attribute.c,代码如下:
externvoidmyprint(constchar*format,...)__attribute__((format(printf,1,2)));
voidtest()
{
myprint("i=%d\",6);
myprint("i=%s\",6);
myprint("i=%s\","abc");
myprint("%s,%d,%d\",1,2);
}
运行$gcc–Wall–cattribute.cattribute后,输出结果为:
attribute.c:
Infunction`test':
attribute.c:
7:
warning:
formatargumentisnotapointer(arg2)
attribute.c:
9:
warning:
formatargumentisnotapointer(arg2)
attribute.c:
9:
warning:
toofewargumentsforformat
如果在attribute.c中的函数声明去掉__attribute__((format(printf,1,2))),再重新编译,即运行$gcc–Wall–cattribute.cattribute后,那么并不会输出任何警告信息。
注意,默认情况下,编译器是能识别类似printf的“标准〞库函数。
__attribute__noreturn
该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而却不可能运行到返回值处就已经退出来的情况,该属性可以防止出现错误信息。
C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:
externvoidexit(int) __attribute__((noreturn));
externvoidabort(void)__attribute__((noreturn));
为了方便理解,大家可以参考如下的例子:
externvoidmyexit();
inttest(intn)
{
if(n>0)
{
myexit();
/*程序不可能到达这里*/
}
else
{
}
return0;
}
编译显示的输出信息为:
$gcc–Wall–cnoreturn.c
noreturn.c:
Infunction`test':
noreturn.c:
12:
warning:
controlreachesendofnon-voidfunction
警告信息也很好理解,因为你定义了一个有返回值的函数test却有可能没有返回值,程序当然不知道怎么办了!
加上__attribute__((noreturn))那么可以很好的处理类似这种问题。
把externvoidmyexit()修改为:
externvoidmyexit()__attribute__((noreturn))之后,编译不会再出现警告信息。
__attribute__const
该属性只能用于带有数值类型参数的函数上。
当重复调用带有数值参数的函数时,由于返回值是一样的,所以此时编译器可以进展优化处理,除第一次需要运算外,其它只需要返回第