GNU C __attribute__ 精解

来源:互联网 发布:企业级软件架构设计 编辑:程序博客网 时间:2024/05/01 14:15
__attribute__ 可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute)。__attribute__ 后面紧跟一对括号,里面是相应的__attribute__参数。__attribute__ 语法格式为:__attribute__ ((attribute-list)) 位置为:放于函数等声明尾部的 ; 之前。 函数属性(Function Attribute)函数属性帮助开发者把一些特性添加到函数声明中,可以使编译器在错误检查方面的功能更强大。__attribute__机制也很容易同非GNU应用程序做到兼容。GNU CC需要使用–Wall编译器来击活该功能。下面介绍几个常见的属性参数:regparm告诉编译器使用几个通用寄存器传递函数参数。 1. #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0))) 2. #define fastcall __attribute__((regparm(3))) 3. #define internal_function __attribute__ ((regparm (3), stdcall))最后一行表示从寄存器传递3个参数,而stdcall表明由被调用函数来清栈,一般的函数是由调用者来负责清栈,用的是cdecl。format该属性给被声明的函数加上类似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: 1. //m=1;n=2 2. extern void myprint(const char *format,...) __attribute__((format(printf,1,2))); 3. //m=2;n=3 4. extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));需要特别注意的是,如果myprint是一个函数的成员函数,那么m和n的值可有点“悬乎”了,其原因是,类成员函数的第一个参数实际上一个“隐身”的“this”指针。这里给出测试用例:attribute.c,代码如下: 1. 2. extern void myprint(const char *format,...) __attribute__((format(printf,1,2))); 3. 4. void test() 5. { 6. myprint("i=%d/n",6); 7. myprint("i=%s/n",6); 8. myprint("i=%s/n","abc"); 9. myprint("%s,%d,%d/n",1,2); 10. }运行$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))),再重新编译后,不会输出任何警告信息。注意,默认情况下,编译器是能识别类似printf的“标准”库函数。noreturn该属性通知编译器函数从不返回值,当遇到函数需要返回值却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示: 1. extern void exit(int) __attribute__((noreturn)); 2. extern void abort(void) __attribute__((noreturn)); 为方便理解,大家可以参考如下的例子: 1. //name: noreturn.c 2. //测试__attribute__((noreturn)) 3. extern void myexit(); 4. 5. int test(int n) 6. { 7. if ( n > 0 ) { 8. myexit(); 9. /* 程序不可能到达这里*/ 10. } else 11. return 0; 12. } 编译显示的输出信息为: $gcc –Wall –c noreturn.cnoreturn.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));之后,编译不会再出现警告信息。const该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。该属性主要适用于没有静态状态static state和副作用的一些函数,并且返回值仅仅依赖输入的参数。为了说明问题,下面举个非常“糟糕”的例子,该例子将重复调用一个带有相同参数值的函数,具体如下: 1. extern int square(int n) __attribute__((const)); 2. ... 3. for (i = 0; i < 100; i++) { 4. total += square(5) + i; 5. } 通过添加__attribute__((const))声明,编译器只调用了函数一次,以后只是直接得到了相同的一个返回值。事实上,const参数不能用在带有指针类型参数的函数中,因为该属性不但影响函数的参数值,同样也影响到了参数指向的数据,它可能会对代码本身产生严重甚至是不可恢复的严重后果。并且,带有该属性的函数不能有任何副作用或者是静态的状态,所以,类似getchar()或time()的函数是不适合使用该属性的。-finstrument-functions该参数可以使程序在编译时,在函数的入口和出口处生成instrumentation调用。恰好在函数入口之后并恰好在函数出口之前,将使用当前函数的地址和调用地址来调用下面的 profiling 函数。(在一些平台上,__builtin_return_address不能在超过当前函数范围之外正常工作,所以调用地址信息可能对profiling函数是无效的。) 1. void __cyg_profile_func_enter(void *this_fn, void *call_site); 2. void __cyg_profile_func_exit(void *this_fn, void *call_site);其中,第一个参数this_fn是当前函数的起始地址,可在符号表中找到;第二个参数call_site是指调用处地址。instrumentation 也可用于在其它函数中展开的内联函数。从概念上来说,profiling调用将指出在哪里进入和退出内联函数。这就意味着这种函数必须具有可寻址形式。如果函数包含内联,而所有使用到该函数的程序都要把该内联展开,这会额外地增加代码长度。如果要在C代码中使用extern inline声明,必须提供这种函数的可寻址形式。可对函数指定no_instrument_function属性,在这种情况下不会进行instrumentation操作。例如,可以在以下情况下使用no_instrument_function属性:上面列出的profiling函数、高优先级的中断例程以及任何不能保证profiling正常调用的函数。no_instrument_function如果使用了-finstrument-functions,将在绝大多数用户编译的函数的入口和出口点调用profiling函数。使用该属性,将不进行instrument操作。constructor/destructor若函数被设定为constructor属性,则该函数会在main()函数执行之前被自动的执行。类似的,若函数被设定为destructor属性,则该函数会在main()函数执行之后或者exit()被调用后被自动的执行。拥有此类属性的函数经常隐式的用在程序的初始化数据方面。这两个属性还没有在面向对象C中实现。同时使用多个属性可以在同一个函数声明里使用多个__attribute__,并且实际应用中这种情况是十分常见的。使用方式上,你可以选择两个单独的__attribute__,或者把它们写在一起,可以参考下面的例子: 1. /* 把类似printf的消息传递给stderr 并退出 */ 2. extern void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2))); 或者写成 1. extern void die(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2)));如果带有该属性的自定义函数追加到库的头文件里,那么所以调用该函数的程序都要做相应的检查。 和非GNU编译器的兼容性庆幸的是,__attribute__设计的非常巧妙,很容易作到和其它编译器保持兼容,也就是说,如果工作在其它的非GNU编译器上,可以很容易的忽略该属性。即使__attribute__使用了多个参数,也可以很容易的使用一对圆括弧进行处理,例如: 1. /* 如果使用的是非GNU C, 那么就忽略__attribute__ */ 2. #ifndef __GNUC__ 3. #define __attribute__(x) /*NOTHING*/ 4. #endif需要说明的是,__attribute__ 适用于函数的声明而不是函数的定义。所以,当需要使用该属性的函数时,必须在同一个文件里进行声明,例如: 1. /* 函数声明 */ 2. void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf,1,2))); 3. void die(const char *format, ...){ 4. /* 函数定义 */ 5. } 变量属性(Variable Attributes)关键字__attribute__也可以对变量(variable)或结构体成员(structure field)进行属性设置。这里给出几个常用的参数的解释,更多的参数可参考本文给出的连接。在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。aligned (alignment)该属性规定变量或结构体成员的最小的对齐格式,以字节为单位。例如:int x __attribute__ ((aligned (16))) = 0;编译器将以16字节对齐的方式分配变量。也可以对结构体成员变量设置该属性,例如,创建一个双字对齐的int对,可以这么写:struct foo { int x[2] __attribute__ ((aligned (8))); };如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:short array[3] __attribute__ ((aligned));选择针对目标机器最大的对齐方式,可以提高拷贝操作的效率。aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。packed使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。下面的例子中,x成员变量使用了该属性,则其值将紧放置在a的后面: 1. struct test{ 2. char a; 3. int x[2] __attribute__ ((packed)); 4. };其它可选的属性值还可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport 等。类型属性(Type Attribute)关键字__attribute__也可以对结构体(struct)或共用体(union)进行属性设置。大致有六个参数值可以被设定,即:aligned, packed, transparent_union, unused, deprecated 和 may_alias。在使用__attribute__参数时,你也可以在参数的前后都加上“__”(两个下划线),例如,使用__aligned__而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。aligned (alignment)该属性设定一个指定大小的对齐格式(以字节为单位),例如: 1. struct S { short f[3]; } __attribute__ ((aligned (8))); 2. typedef int more_aligned_int __attribute__ ((aligned (8))); 该声明将强制编译器确保(尽它所能)变量类型为struct S或者more-aligned-int的变量在分配空间时采用8字节对齐方式。如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如: 1. struct S { short f[3]; } __attribute__ ((aligned)); 这里,如果sizeof(short)的大小为2(byte),那么,S的大小就为6。取一个2的次方值,使得该值大于等于6,则该值为8,所以编译器将设置S类型的对齐方式为8字节。packed使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。当用在enum类型定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integral type should be used)。下面的例子中,my-packed-struct类型的变量数组中的值将会紧紧的靠在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使用packed进行相应的约束。 1. struct my_unpacked_struct { 2. char c; 3. int i; 4. }; 5. 6. struct my_packed_struct { 7. char c; 8. int i; 9. struct my_unpacked_struct s; 10. } __attribute__ ((__packed__));其它属性的含义见:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes变量属性与类型属性举例下面的例子中使用__attribute__属性定义了一些结构体及其变量,并给出了输出结果和对结果的分析。程序代码为: 1. struct p { 2. int a; 3. char b; 4. char c; 5. }__attribute__((aligned(4))) pp; 6. 7. struct q { 8. int a; 9. char b; 10. struct n qn; 11. char c; 12. }__attribute__((aligned(8))) qq; 13. 14. 15. int main() 16. { 17. printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d/n",sizeof(int),sizeof(short),sizeof(char)); 18. printf("pp=%d,qq=%d /n", sizeof(pp),sizeof(qq)); 19. 20. return 0; 21. }输出结果: sizeof(int)=4,sizeof(short)=2.sizeof(char)=1pp=8,qq=24 分析:sizeof(pp):sizeof(a)+ sizeof(b)+ sizeof(c)=4+1+1=6<23=8= sizeof(pp)sizeof(qq):sizeof(a)+ sizeof(b)=4+1=5sizeof(qn)=8;即qn是采用8字节对齐的,所以要在a,b后面添3个空余字节,然后才能存储qn,4+1+(3)+8+1=17因为qq采用的对齐是8字节对齐,所以qq的大小必定是8的整数倍,即qq的大小是一个比17大又是8的倍数的一个最小值,由此得到17<24+8=24= sizeof(qq) 转自:http://blog.csdn.net/gates84/archive/2006/11/03/1365508.aspx Weak AliasWeak Alias 跟 Weak Reference 完全没有任何关系,不过是我在看到 Weak Reference 的时候想到的而已。Weak Alias 是 gcc 扩展里的东西,实际上是函数的属性。这个东西在库的实现里面可能会经常用到,比如 glibc 里面就用了不少。抄录一段 gcc 手册里面的话解释下函数属性是干啥的:In GNU C, you declare certain things about functions called in your program which help the compiler optimize function calls and check your code more carefully.先上代码,看看 weak alias 怎么写。第一个文件 dummy.c 内容, 1. #include 2. 3. /* Do some thing. */ 4. int __foo() { 5. puts(“I do no thing.”); 6. } 7. 8. int foo() __attribute__ ((weak, alias(“__foo”)));weak 和 alias 分别是两个属性。weak 使得 foo 这个符号在目标文件中作为 weak symbol 而不是 global symbol。用 nm 命令查看编译 dummy.c 生成的目标文件可用看到 foo 是一个 weak symbol,它前面的标记是 W。00000000 T __foo00000000 W foo U puts而 alias 则使 foo 是 __foo 的一个别名,__foo 和 foo 必须在同一个编译单元中定义,否则会编译出错。那么这个东西的用处是?看第二个文件,func.c, 1. #include 2. 3. int foo() { 4. puts(“I do something.”); 5. }这里有一个函数名字是 foo。如果我们编译 func.c 和 dummy.c 得到两个目标文件,当我们同时使用 func.o 和 dummy.o 和其他目标文件进行链接时,如果其他目标文件里面引用符号 foo,最终使用到的是 func.c 中定义的函数,而不是 __foo,虽然它有一个别名 foo。也就是说,我们最终使用到的函数会是“实际做事”的那个函数。当然,单独使用 dummy.o 链接的话使用的是那个“不做事”的函数。如果 dummy.o 中的 foo 不是 weak symbol 的话,在链接时会产生冲突,这就是我们要使用 weak 的原因。glibc 的实现里面经常用 weak alias。比如它的 socket 函数,在 C 文件里面你会看到一个 __socket 函数,它几乎什么都没有做,只是设置了一些错误代码,返回些东西而已。在同一个 C 文件里面会再声明一个 __socket 的 weak alias 别名 socket。实际完成工作的代码通过汇编来实现,在另外的汇编文件里面会有设置系统调用号,执行 sysenter 或者 int 等动作去请求系统调用。以前看 glibc 里面系统调用的实现的时候郁闷过很久,就是那个时候才知道了 weak alias 这个东西。补充: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.h159: #define min_t(type,x,y) /160: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })++++ net/ipv4/tcp_output.c654: 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.h141: #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 可以用在任何类型可以使用的地方,通常用于宏定义。typeof 操作符返回参数的类型,这里的参数可以是表达式、类型等等。关键字 typeof 是GNUC的扩展。在使用 -std=c99 编译时,typeof 默认是关闭的。若使用该功能,可以使用 __typeof__关键字。在不请求对标准 C99 扩展时,__typeof__ 也可以被 gcc 识别。而 typeof 必须请求对 C99 的 GNU 扩展。(例如使用 -fasm 选项。所以推荐使用 __typeof__。零长度数组GNU C 允许使用零长度数组,在定义变长对象的头结构时,这个特性非常有用。例如:++++ include/linux/minix_fs.h85: struct minix_dir_entry {86: __u16 inode;87: char name[0];88: };结构的最后一个元素定义为零长度数组,它不占结构的空间。在标准 C 中则需要定义数组长度为 1,分配时计算对象大小比较复杂。可变参数宏在 GNU C 中,宏可以接受可变数目的参数,就象函数一样,例如:++++ include/linux/kernel.h110: #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.c1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };将数组的所有元素初使化为 ~0UL,这可以看做是一种简写形式。要指定结构元素,在元素值前写 'FIELDNAME:',例如:++++ fs/ext2/file.c41: 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.c1062: 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.h47: # 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.h78: #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 用于变量、结构或联合类型,指定变量、结构域、结构或联合的对齐量,以字节为单位,例如:++++ include/asm-i386/processor.h294: struct i387_fxsave_struct {295: unsigned short cwd;296: unsigned short swd;297: unsigned short twd;298: unsigned short fop;299: long fip;300: long fcs;301: long foo;......308: } __attribute__ ((aligned (16)));表示该结构类型的变量以 16 字节对齐。通常编译器会选择合适的对齐量,显示指定对齐通常是由于体系限制、优化等原因。* packed属性 packed 用于变量和类型,用于变量或结构域时表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。例如:++++ include/asm-i386/desc.h51: struct Xgt_desc_struct {52: unsigned short size;53: unsigned long address __attribute__((packed));54: };域 address 将紧接着 size 分配。属性 packed 的用途大多是定义硬件相关的结构,使元素之间没有因对齐而造成的空洞。当前函数名GNU CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外信息,Linux 内核只使用了 __FUNCTION__。++++ fs/ext2/super.c98: void ext2_update_dynamic_rev(struct super_block *sb)99: {100: struct ext2_super_block *es = EXT2_SB(sb)->s_es;101:102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)103: return;104:105: ext2_warning(sb, __FUNCTION__,106: "updating to rev %d because of new feature flag, "107: "running e2fsck is recommended",108: EXT2_DYNAMIC_REV);这里 __FUNCTION__ 将被替换为字符串 "ext2_update_dynamic_rev"。虽然__FUNCTION__ 看起来类似于标准 C 中的 __FILE__,但实际上 __FUNCTION__是被编译器替换的,不象 __FILE__ 被预处理器替换。内建函数GNU C 提供了大量的内建函数,其中很多是标准 C 库函数的内建版本,例如 memcpy,它们与对应的 C 库函数功能相同,本文不讨论这类函数,其他内建函数的名字通常以 __builtin 开始。* __builtin_return_address (LEVEL)内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数 LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依此类推。例如:++++ kernel/sched.c437: printk(KERN_ERR "schedule_timeout: wrong timeout "438: "value %lx from %p/n", timeout,439: __builtin_return_address(0));* __builtin_constant_p(EXP)内建函数 __builtin_constant_p 用于判断一个值是否为编译时常数,如果参数 EXP 的值是常数,函数返回 1,否则返回 0。例如:++++ include/asm-i386/bitops.h249: #define test_bit(nr,addr) /250: (__builtin_constant_p(nr) ? /251: constant_test_bit((nr),(addr)) : /252: variable_test_bit((nr),(addr)))很多计算或操作在参数为常数时有更优化的实现,在 GNU C 中用上面的方法可以根据参数是否为常数,只编译常数版本或非常数版本,这样既不失通用性,又能在参数是常数时编译出最优化的代码。* __builtin_expect(EXP, C)内建函数 __builtin_expect 用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时常数。例如:++++ include/linux/compiler.h13: #define likely(x) __builtin_expect((x),1)14: #define unlikely(x) __builtin_expect((x),0)++++ kernel/sched.c564: if (unlikely(in_interrupt())) {565: printk("Scheduling in interrupt/n");566: BUG();567: }这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。上面的例子表示处于中断上下文是很少发生的,第 565-566 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。
原创粉丝点击