GNU C 9条扩展语法
GNC CC是一个功能非常强大的跨平台C编译器,它对标准C语言进行了一系列扩展,以增强标准C的功能,这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。本文把支持GNU扩展的C语言称为GNU C。
Linux内核代码使用了大量的GNU C扩展,以至于能够编译Linux内核的唯一编译器是GNU CC,以前甚至出现过编译Linux内核要使用特殊的GNU CC版本的情况。本文是对Linux内核使用的GNU C扩展的一个汇总,希望当你读内核源码遇到不理解的语法和语义时,能从本文到一个初步的解答,更详细的信息可以查看gcc.info。文中的例子取自 Linux 2.4.18。
1、零长度和变量长度数组
GNU C允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:
//include/linux/minix_fs.h
struct minix_dir_entry
{
_u16 inode;
char name[0];
};
结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准C中则需要定义数组长度为1,分配时计算对象大小比较复杂。
GNU C 允许使用一个变量定义数组的长度,比如:
int n=0;
scanf("%d",&n);
int array[n];
2、case范围
GNU C允许在一个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、语句表达式
GNU C把包含在括号中的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方,你可以在语句表达式中使用循环、局部变量等,原本只能在复合语句中使用。例如:
//include/linux/kernel.h
#define min_t(type,x,y) ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })
//net/ipv4/tcp_output.c
int full_space = min_t(int, tp->window_clamp,tcp_full_space(sk));
复合语句的最后一个语句应该是一个表达式,它的值将成为这个语句表达式的值。
这里定义了一个安全的求最小值的宏,在标准C中,通常定义为:
#define min(x,y) ((x) < (y) ? (x) : (y))
这个定义计算x和y分别两次,当参数有副作用时,将产生不正确的结果,使用语句表达式只计算参数一次,避免了可能的错误。语句表达式通常用于宏定义。
4、typeof 关键字
使用前一节定义的宏需要知道参数的类型,利用typeof可以定义更通用的宏,不必事先知道参数的类型,例如:
//include/linux/kernel.h
#define min(x,y) ({ /
const typeof(x) _x = (x); /
const typeof(y) _y = (y); /
(void) (&_x == &_y); /
_x < _y ? _x : _y; })
这里typeof(x)表示x的值类型,第3行定义了一个与 x 类型相同的局部变量 _x 并初使化为 x,注意第5行的作用是检查参数x和y的类型是否相同。typeof 可以用在任何类型可以使用的地方,通常用于宏定义。
5、可变参数宏
在GNU C中,宏可以接受可变数目的参数,就象函数一样,例如:
//include/linux/kernel.h
#define pr_debug() 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")
注意最后没有逗号。
6、标号元素
标准C要求数组或结构变量的初使化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构域名,允许初始化值以任意顺序出现。指定数组索引的方法是在初始化值前写 '[INDEX] =',要指定一个范围使用'[FIRST ... LAST] ='的形式,例如:
//arch/i386/kernel/irq.c
static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1]= ~0UL };
将数组的所有元素初使化为~0UL,这可以看做是一种简写形式。
要指定结构元素,在元素值前写'FIELDNAME:',例如:
//fs/ext2/file.c
struct file_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,
gnu编译器};
将结构ext2_file_operations的元素llseek初始化为generic_file_llseek,元素read初始化为g
enenric_file_read,依次类推。我觉得这是GNU C扩展中最好的特性之一,当结构的定义变化以至元素的偏移改变时,这种初始化方法仍然保证已知元素的正确性。对于未出现在初始化中的元素,其初值为 0。
标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成员名,允许初始化以任意顺序出现。
unsigned char data[MAX] ={
[0]=10,
[10]=100,
};
struct file_operations ext2_file_operations={
open:ext2_open,
close:ext2_close,
};
在linux 2.6中推荐如下方式:
struct file_operations ext2_file_operations={
.read=ext2_read,
.write=ext2_write,
};
7、当前函数名
GNU C中预定义两个标志符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY__FUNCTION__保存带语言特的名字。在C函数中这两个名字是相同的。在C++函数中,__PRETTY_FUNCTION__包括函数返回类型等额外信息,Linux内核
只使用了__FUNCTION__。
//fs/ext2/super.c
void ext2_update_dynamic_rev(struct super_block *sb)
{
struct ext2_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__, "updating to rev %d because of new feature flag, " "running e2fsck is recommended", EXT2_DYNAMIC_REV);
这里__FUNCTION__将被替换为函数名ext2_update_dynamic_rev。虽然__FUNCTION__ 看起来类似于标准C中的__FILE__,但实际上__FUNCTION__是被编译器替换的,不象 _
_FILE__被预处理器替换。
在C99中支持__func__宏,因此建议使用__func__替代__FUNCTION__
8、特殊属性声明
GNU C允许声明函数、变量和类型的特殊属性,以便进行手工的代码优化和定制代码检查的方法。
要指定一个声明属性,只需要在声明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE为属性说明,如果存在多个属性,则以逗号分隔。GNU C 支持noreturn、format、section、aligned、packed等十个属性。这里介绍最常用的:
noreturn属性用于函数,表示该函数从不返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息比如未初使化的变量。例如:
//include/linux/kernel.h
#define ATTRIB_NORET __attribute__((noreturn)) ....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)属性用于函数,表示该函数使用printf, scanf或strftime风格的参数,使用这类函数最容易犯的错误是格式串与参数不匹配,指定 format 属性可以让编译器根据格式串检查参数类型。例如:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论