GNU __attribute详解
zhezhongyun 2025-04-24 10:37 61 浏览
GNU C的一大特色(却不被初学者所知)就是 attribute 机制。
attribute可以设置函数属性(Function Attribute)、变量属性(Variable Attribute) 和 类型属性(Type Attribute)。
attribute书写特征是: attribute前后都有两个下划线,并且后面会紧跟一对圆括弧,括弧里面是相应的attribute参数。
attribute语法格式为:
__attribute__ ((attribute-list))
其位置约束为: 放于声明的尾部“;”之前。
1. 函数属性(Function Attribute)
函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。
attribute机制也很容易同非GNU应用程序做到兼容之功效。
GNU CC需要使用 –Wall编译器来激活该功能,这是控制警告信息的一个很好的方式。
下面介绍几个常见的属性参数。
1.1attributeformat
该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,代码如下:
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))),再重新编译,
既运行$gcc –Wall –c attribute.c attribute后,则并不会输出任何警告信息。
注意,默认情况下,编译器是能识别类似printf的“标准”库函数。
1.2attributenoreturn
该属性通知编译器函数从不返回值,当遇到类似函数需要返回值而却不可能运行到返回值处就已经退出来的情况,该属性可以避免出现错误信息。
C库函数中的abort()和exit()的声明格式就采用了这种格式,如下所示:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn)); 为了方便理解,大家可以参考如下的例子:
//name: noreturn.c ;测试__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));之后,编译不会再出现警告信息。
1.3attributeconst
该属性只能用于带有数值类型参数的函数上。
当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除第一次需要运算外,其它只需要返回第一次的结果就可以了,进而可以提高效率。该属性主要适用于没有静态状态(static state)和副作用的一些函数,并且返回值仅仅依赖输入的参数。
为了说明问题,下面举个非常“糟糕”的例子,该例子将重复调用一个带有相同参数值的函数,具体如下:
extern int square(int n) __attribute__((const));
...
for (i = 0; i < 100; i++ )
{
total += square (5) + i;
} 通过添加attribute((const))声明,编译器只调用了函数一次,以后只是直接得到了相同的一个返回值。
事实上,const参数不能用在带有指针类型参数的函数中,因为该属性不但影响函数的参数值,同样也影响到了参数指向的数据,它可能会对代码本身产生严重甚至是不可恢复的严重后果。
并且,带有该属性的函数不能有任何副作用或者是静态的状态,所以,类似getchar()或time()的函数是不适合使用该属性的。
1.4 同时使用多个属性
可以在同一个函数声明里使用多个attribute,并且实际应用中这种情况是十分常见的。
使用方式上,你可以选择两个单独的attribute,或者把它们写在一起,可以参考下面的例子:
/* 把类似printf的消息传递给stderr 并退出 */
extern void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf, 1, 2)));
或者写成
extern void die(const char *format, ...) __attribute__((noreturn, format(printf, 1, 2))); 如果带有该属性的自定义函数追加到库的头文件里,那么所以调用该函数的程序都要做相应的检查。
1.5 和非GNU编译器的兼容性
庆幸的是,attribute设计的非常巧妙,很容易作到和其它编译器保持兼容,也就是说,如果工作在其它的非GNU编译器上,可以很容易的忽略该属性。即使attribute使用了多个参数,也可以很容易的使用一对圆括弧进行处理,例如:
/* 如果使用的是非GNU C, 那么就忽略__attribute__ */
#ifndef __GNUC__#
define __attribute__(x) /*NOTHING*/
#endif 需要说明的是,attribute适用于函数的声明而不是函数的定义。
所以,当需要使用该属性的函数时,必须在同一个文件里进行声明,
例如:
/* 函数声明 */
void die(const char *format, ...) __attribute__((noreturn)) __attribute__((format(printf,1,2)));
void die(const char *format, ...)
{
/* 函数定义 */
} 更多的属性含义参考:
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
2. 变量属性(Variable Attributes)
关键字attribute也可以对变量(variable)或结构体成员(structure field)进行属性设置。
这里给出几个常用的参数的解释,更多的参数可参考本文给出的连接。
在使用attribute参数时,你也可以在参数的前后都加上“”(两个下划线),例如,使用__aligned而不是aligned,
这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
2.1 aligned(alignment)
该属性规定变量或结构体成员的最小的对齐格式,以字节为单位。例如:
int x __attribute__((aligned (16))) = 0; 编译器将以16字节(注意是字节byte不是位bit)对齐的方式分配一个变量。
也可以对结构体成员变量设置该属性,例如,创建一个双字对齐的int对,可以这么写:
struct foo { int x[2] __attribute__((aligned (8))); }; 如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:
short array[3] __attribute__((aligned)); 选择针对目标机器最大的对齐方式,可以提高拷贝操作的效率。
aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。
需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。
2.2 packed
使用该属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
下面的例子中,x成员变量使用了该属性,则其值将紧放置在a的后面:
struct test
{
char a;
int x[2] attribute ((packed));
};
其它可选的属性值还可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport等,
详细信息可参考:
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Variable-Attributes.html#Variable-Attributes
3. 类型属性(Type Attribute)
关键字attribute也可以对结构体(struct)或共用体(union)进行属性设置。
大致有六个参数值可以被设定,即:
aligned, packed, transparent_union, unused, deprecated 和 may_alias。
在使用attribute参数时,你也可以在参数的前后都加上“”(两个下划线),例如,使用__aligned而不是aligned,这样,你就可以在相应的头文件里使用它而不用关心头文件里是否有重名的宏定义。
3.1 aligned (alignment)
该属性设定一个指定大小的对齐格式(以字节为单位),例如:
struct S { short f[3]; } __attribute__ ((aligned (8)));
typedef int more_aligned_int __attribute__ ((aligned (8)));该声明将强制编译器确保(尽它所能)变量类型为struct S 或者more-aligned-int的变量在分配空间时采用8字节对齐方式。
如上所述,你可以手动指定对齐的格式,同样,你也可以使用默认的对齐方式。
如果aligned后面不紧跟一个指定的数字值,那么编译器将依据你的目标机器情况使用最大最有益的对齐方式。例如:
struct S { short f[3]; } __attribute__ ((aligned));这里,如果sizeof(short)的大小为2(byte),那么,S的大小就为6。取一个2的次方值,使得该值大于等于6,则该值为8,所以编译器将设置S类型的对齐方式为8字节。
aligned属性使被设置的对象占用更多的空间,相反的,使用packed可以减小对象占用的空间。
需要注意的是,attribute属性的效力与你的连接器也有关,如果你的连接器最大只支持16字节对齐,那么你此时定义32字节对齐也是无济于事的。
3.2 packed
使用该属性对struct或者union类型进行定义,设定其类型的每一个变量的内存约束。
当用在enum类型定义时,暗示了应该使用最小完整的类型(it indicates that the smallest integral type should be used)。
下面的例子中,my-packed-struct类型的变量数组中的值将会紧紧的靠在一起,但内部的成员变量s不会被“pack”,如果希望内部的成员变量也被packed的话,my-unpacked-struct也需要使用packed进行相应的约束。
struct my_unpacked_struct
{
char c;
int i;
};
struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));其它属性的含义见:
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes
4. 变量属性与类型属性举例
下面的例子中使用attribute属性定义了一些结构体及其变量,并给出了输出结果和对结果的分析。
程序代码为:
struct p
{
int a;
char b;
char c;
}__attribute__((aligned(4))) pp;
struct q
{
int a;
char b;
struct n qn;
char c;
}__attribute__((aligned(8))) qq;
int main()
{
printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
printf("pp=%d,qq=%d \n", sizeof(pp),sizeof(qq));
return 0;
}输出结果:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=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=5
sizeof(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://gcc.gnu.org/
Reference:
1.有关attribute的相对简单的介绍:
http://www.unixwiz.net/techtips/gnu-c-attributes.html2.attribute详细介绍:http://gcc.gnu.org/
相关推荐
- Python入门学习记录之一:变量_python怎么用变量
-
写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...
- python变量命名规则——来自小白的总结
-
python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...
- Python入门学习教程:第 2 章 变量与数据类型
-
2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...
- 绘制学术论文中的“三线表”具体指导
-
在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...
- Python基础语法知识--变量和数据类型
-
学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...
- 一文搞懂 Python 中的所有标点符号
-
反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...
- Python变量类型和运算符_python中变量的含义
-
别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...
- 从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序
-
在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...
- Python中下划线 ‘_’ 的用法,你知道几种
-
Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...
- 解锁Shell编程:变量_shell $变量
-
引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...
- 一文学会Python的变量命名规则!_python的变量命名有哪些要求
-
目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...
- 更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for
-
src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...
- C++第五课:变量的命名规则_c++中变量的命名规则
-
变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....
- Rust编程-核心篇-不安全编程_rust安全性
-
Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...
- 探秘 Python 内存管理:背后的神奇机制
-
在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- opacity 属性 (32)
- transition 属性 (33)
- 1-1. 变量声明 (31)
