GNU C 9条扩展语法 您所在的位置:网站首页 GNU编译器是跨平台编译 GNU C 9条扩展语法

GNU C 9条扩展语法

2024-06-21 13:11| 来源: 网络整理| 查看: 265

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 window_clamp,tcp_full_space(sk));

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

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

#define min(x,y) ((x) 

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 属性可以让编译器根据格式串检查参数类型。例如:

//include/linux/kernel.h?

asmlinkage int printk(const char * 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

struct i387_fxsave_struct {

unsigned short  cwd;

......

} __attribute__ ((aligned (16)));

表示该结构类型的变量以16字节对齐。通常编译器会选择合适的对齐量,显示指定对齐通常是由于体系限制、优化等原因。

packed属性用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。例如:

//include/asm-i386/desc.h

struct Xgt_desc_struct {

unsigned short size;

unsigned long address __attribute__((packed));

};

域address将紧接着size分配。属性packed的用途大多是定义硬件相关的结构,使元素之间没有因对齐而造成的空洞。

9、内建函数

GNU C提供了大量的内建函数,其中很多是标准C库函数的内建版本,例如memcpy,它们与对应的C库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以__builtin开始。

内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数调用者的返回地址,依此类推。例如:

//kernel/sched.c

printk(KERN_ERR "schedule_timeout: wrong timeout " "value %lx from %p/n", timeout,

__builtin_return_address(0));

内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回 1,否则返回 0。

// include/asm-i386/bitops.h

#define test_bit(nr,addr) /

(__builtin_constant_p(nr) ? /

constant_test_bit((nr),(addr)) : /

variable_test_bit((nr),(addr)))

很多计算或操作在参数为常数时有更优化的实现,在GNU C中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。

内建函数__builtin_expect(EXP, C)用于为编译器提供分支预测信息,其返回值是整数表达式EXP的值,C的值必须是编译时常数。例如:

//include/linux/compiler.h

#define likely(x)       __builtin_expect((x),1)

#define unlikely(x)     __builtin_expect((x),0)

// kernel/sched.c

if (unlikely(in_interrupt())) {

printk("Scheduling in interrupt/n");

BUG();

}

这个内建函数的语义是EXP的预期值是C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第6-7行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。

若不想使用GNU C扩展,那么只需要在gcc参数后面加上-ansi -pedantic即可,使用上述参数后,所有GNC C扩展语法部分将会有编译警报。

linux gcc的属性解析

GNU C的一大特色(却不被初学者所知)就是__attribute__机制。__attribute__可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。

__attribute__书写特征是:__attribute__前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__参数。

__attribute__语法格式为:

__attribute__ ((attribute-list))

其位置约束为:放于声明的尾部“;”之前。

函数属性(Function Attribute)

函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容之功效。

GNU CC需要使用–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:第几个参数为格式化字符串(format string);

n:参数集合中的第一个,即参数“…”里的第一个参数在函数参数总数排在第几,注意,有时函数参数里还有“隐身”的呢,后面会提到;

在使用上,__attribute__((format(printf,m,n)))是常用的,而另一种却很少见到。下面举例说明,其中myprint为自己定义的一个带有可变参数的函数,其功能类似于printf:

//m=1;n=2

extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));

//m=2;n=3

extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));

需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,例如: 

//m=3;n=4 

extern void myprint(int l,const char *format,...) __attribute__((format(printf,3,4))); 

其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。(有点C++基础的都知道点this指针,不知道你在这里还知道吗?) 

这里给出测试用例:attribute.c,代码如下:

extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));

void test()

{

myprint("i=%d\",6);

 myprint("i=%s\",6);

 myprint("i=%s\","abc"); 

myprint("%s,%d,%d\",1,2);

}

运行$gcc –Wall –c attribute.c attribute后,输出结果为:

attribute.c: In function `test':

attribute.c:7: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: format argument is not a pointer (arg 2)

attribute.c:9: warning: too few arguments for format

如果在attribute.c中的函数声明去掉__attribute__((format(printf,1,2))),再重新编译,即运行$gcc –Wall –c attribute.c attribute后,则并不会输出任何警告信息。

注意,默认情况下,编译器是能识别类似printf的“标准”库函数。

__attribute__ noreturn 

该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:

extern void exit(int)   __attribute__((noreturn));

extern void abort(void) __attribute__((noreturn));

为了方便理解,大家可以参考如下的例子:

extern void myexit();

int test(int n)

{

if ( n > 0 )

    {

    myexit();

        /* 程序不可能到达这里*/

    }

    else

{

}

    return 0;

}

编译显示的输出信息为:

$ gcc –Wall –c noreturn.c

noreturn.c: In function `test':

noreturn.c:12: warning: control reaches end of non-void function

警告信息也很好理解,因为你定义了一个有返回值的函数test却有可能没有返回值,程序当然不知道怎么办了!

加上__attribute__((noreturn))则可以很好的处理类似这种问题。把extern void myexit()修改为:extern void myexit() __attribute__((noreturn))之后,编译不会再出现警告信息。

__attribute__ const

该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。该属性主要适用于没有静态状态(static state)和副作用的一些函数,并且返回值仅仅依赖输入的参数。

为了说明问题,下面举个非常“糟糕”的例子,该例子将重复调用一个带有相同参数值的函数,具体如下:

extern int square(int n) __attribute__((const));

...

    for (i = 0; i 



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有