目录
- 一、简介
- 二、参数详解
- 2.1 section:自定义段
- 2.2 aligned:对齐
- 2.3 packed:对齐
- 2.4 format:检查函数变参格式
- 2.5 used
- 2.6 unused
- 2.7 at 绝对定位
- 2.8 constructor
- 2.9 destructor
- 2.10 weak:弱声明
- 2.11 alias:函数起别名
- 2.12 always_inline:内联函数总是展开
- 2.13 noinline:无内联
- 2.14 transparent_union
- 2.15 deprecated
- 2.16 noreturn
- 三、其他相关链接
一、简介
GNU C编译器增加了一个__attribute__ 关键字用来声明一个函数、变量或类型的特殊属性。申明这些属性主要用途就是指导编译程序进行特定方面的优化或代码检查。
attribute 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。
关键字__attribute__ 也可以对结构体(struct )或共用体(union )进行属性设置。
attribute 书写特征是:attribute 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__ 参数,格式如下:
__attribute__ ((ATTRIBUTE))
二、参数详解
2.1 section:自定义段
section属性的主要作用是:在程序编译时,将一个函数或者变量放到指定的段,即指定的section中。
一个可执行文件注意由代码段,数据段、bss段构成。代码段主要用来存放编译生成的可执行指令代码、数据段和bss段用来存放全局变量和未初始化的全局变量。
除了这三个段,可执行文件还包含一些其他的段。我们可以用 readelf 去查看一个可执行文件各个section信息。
int global_val = 0;
int unint_val __attribute__((section(".data")));
int main()
{
return 0;
}
2.2 aligned:对齐
GNU C 通过 __attribute__ 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。
通过aligned属性,我们可以显示地指定变量在内存中的地址对齐方式。aligned有一个参数,表示要按几个字节对齐,使用时要注意,地址对齐的字节数必须是 2 的幂次方,否则编译就会报错。
2.3 packed:对齐
aligned 属性一般用来增大变量的地址对齐,元素之间地址对齐会造成一定的内存空洞,而packed属性则正好相反,一般用来减少地址对齐,指定变量或类型使用最可能小的地址对齐方式。
2.4 format:检查函数变参格式
指定变参函数的参数格式检查
__attribute__((format (archetype, string-index, frist-to-check)))
void LOG(const char *fmt, ...) __attribute__((format(printf,1,2)));
属性format(printf,1,2) 有3个参数,第一个参数pritnf 是告诉编译器,按照printf的标准来检查;第二个参数表示LOG()函数所有的参数列表中格式字符串的位置索引,第三个参数是告诉编译器要检查的参数的起始位置。
LOG("hello world ,i am %d ages ", age); /* 前者表示格式字符串,后者表示所有的参数*/
2.5 used
used的作用是告诉编译器,我声明的这个符号是需要保留的。被used修饰以后,意味着即使函数没有被引用,在Release下也不会被优化。如果不加这个修饰,那么Release环境链接器会去掉没有被引用的段。
1、这个函数属性通知编译器在目标文件中保留一个静态函数,即使它没有被引用;
2、标记为attribute__((used))的函数被标记在目标文件中,以避免链接器删除未使用的节;
3、静态变量也可以标记为used,方法是使用 attribute((used))。
static int lose_this(int);
static int keep_this(int) __attribute__((used)); // retained in object file
__attribute__((used,section(".rodata.$AppID")))
const uint8_t ApplicationID[16] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x12, 0x34, 0x00, 0x00
};
2.6 unused
表示该函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
#define __attribute_unused__ __attribute__((__unused__))
2.7 at 绝对定位
绝对定位,可以把变量或函数绝对定位到Flash中,或者定位到RAM。
定位到flash中,一般用于固化的信息,如出厂设置的参数,上位机配置的参数,ID卡的ID号,flash标记等等。
const u16 gFlashDefValue[512] __attribute__((at(0x0800F000))) = {0x1111,0x1111,0x1111,0x0111,0x0111,0x0111};//定位在flash中,其他flash补充为00
const u16 gflashdata__attribute__((at(0x0800F000))) = 0xFFFF;
2.8 constructor
确保此函数在main函数被调用之前调用
constructor和destructor会在ELF文件中添加两个段-.ctors和.dtors。当动态库或程序在加载时,会检查是否存在这两个段,如果存在执行对应的代码。
int main(int argc, char * argv[]) {
@autoreleasepool {
printf("main function");
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
__attribute__((constructor)) static void beforeFunction()
{
printf("beforeFunction\n");
}
打印结果如下:
beforeFunction
main function
static __attribute__((constructor(101))) void before1()
{
printf("before1\n");
}
static __attribute__((constructor(102))) void before2()
{
printf("before2\n");
}
static __attribute__((constructor(102))) void before3()
{
printf("before3\n");
}
以上三个函数会依照优先级的顺序调用.另外,我以前看过,这个1-100的范围是保留的,所以,最好从100之后开始用。
2.9 destructor
确保此函数在main()函数退出或者调用了exit()之后,调用我们的函数。
2.10 weak:弱声明
将一个强符号,转换为弱符号。使用方法如下:
void __attribute__((weak)) func(void);
int num __attribute__((weak));
在一个程序中,无论是变量名,还是函数名,在编译器眼里,就是一个符号而已,符号可以分为强符号和弱符号。
强符号:函数名,初始化的全局变量名
弱符号:未初始化的全局变量名。
//func1.c
int a __attribute__((weak)) = 1;
void func(void)
{
printf("func.a = %d\n", a);
}
//main.c
int a = 4;
void __attribute__((weak)) func(void)
{
printf("main.a = %d\n", a);
}
int main(void)
{
func();
return 0;
}
弱符号的用途
1、在一个源文件中引用一个编号或者函数,当编译器只看到声明,而没看到其定义时,一般编译时不会报错。在链接阶段,链接器会到其他文件中找到这些符号的定义,若未找到,则报未定义错误。
2、当函数被声明一个弱符号时,会有一个奇特地方:当链接器找不到这个函数的定义时,也不会报错。编译器会将这个函数名,即弱符号,设置为0或者一个特殊值。只有当程序运行时,调用到这个函数,跳转到零地址或者一个特殊的地址才会报错误,产生一个内存错误。
3、如果我们在使用函数前,判断这个函数地址是否为0,即可避免段错误。你会发现,即使函数未定义也可以正常编过。
4、弱符号的这个特性在库函数开发设计中应用十分广泛,如果在开发一个库时,基础功能已经实现,有些高级功能还未实现,那么你就可以将这些函数通过weak 属性声明转换为一个弱符号。
符号(或弱定义) weak属性只会在静态库(.o .a )中生效,动态库(.so)中不会生效
__attribute__((weakref(“别名”)))
//引用,必须是static类型,别名要写,不然等效于weak(其实也可以直接写成weak的形式)
2.11 alias:函数起别名
主要用来给函数定义一个别名
void __f(void)
{
printf("__f\n");
}
void f(void) __attribute__((alias("__f")));
int main(void)
{
f();
return 0;
}
2.12 always_inline:内联函数总是展开
说起内联函数,就不得不说起函数调用开销。一个函数在执行过程中,如果要调用其他函数,则一般会执行以下过程:
1、保存当前函数现场;
2、跳到调用函数执行;
3、恢复当前函数现场;
4、继续执行当前函数;
对于一些短小精悍,并且调用频繁的函数,调用开销大,这个时候我们可以将函数声明为内联函数。编译器遇到内联函数会想宏一样将内联函数之间在调用处展开,这样做就减少了函数调用的开销。
当我们认为一个函数体积小、而且被大量调用,应做内联展开时,就可以使用static inline 关键字修饰它,但是编译器不一定会内联展开。如果想明确告诉编译器一定要展开,或者不展开就可以使用 noinline 和 always_inline 对函数的属性做一个声明。
内联的本质是用代码块直接替换掉函数调用处,好处是:快代码的执行,减少系统开销。
void test(int a)__attribute__((always_inline));
2.13 noinline:无内联
与always_inline相反,无内联展开
2.14 transparent_union
我们可以使用透明联合类型,函数指针能够指向参数类型不同的函数。
2.15 deprecated
如果被变量或者函数的声明使用deprecated属性进行描述,那么编译器编译过程中不会产生警告或者错误,但是,被该属性描述的变量或者函数在其他地方被调用,那么编译会产生警告,警告信息中包含过时接口定义的位置及代码中的引用位置。
#define attribute_deprecated __attribute__((deprecated))
/* Variable Attribute */
attribute_deprecated int variable_old = 0;
/* Function Attribute */
attribute_deprecated void function_old(void);
2.16 noreturn
几个标注库函数,例如abort exit,没有返回值。GCC能够自动识别这种情况。noreturn属性指定像这样的任何不需要返回值的函数。当遇到类似函数还未运行到return语句就需要退出来的情况,该属性可以避免出现错误信息。
三、其他相关链接
1、C语言常用函数详细总结
2、C语言中指针、数组作为作为函数参数使用总结
3、C语言常见数据类型字节数和打印格式总结
4、C语言、Makefile和shell中添加打印调试信息总结
5、c语言volatile关键字总结