问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言attribute关键字参数详解

创作时间:
作者:
@小白创作中心

C语言attribute关键字参数详解

引用
CSDN
1.
https://blog.csdn.net/qq_69004149/article/details/141186729

在C语言中,attribute关键字是一个非常强大的工具,它允许开发者为函数、变量或类型添加特殊属性,从而指导编译器进行特定方面的优化或代码检查。本文将详细介绍attribute关键字的各种参数及其用法,帮助开发者更好地利用这一特性。

一、简介

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;
}

  • 其他用法:

    #define LRAM_BUF                     __attribute__((section(".bss.lram")))
    #define RO_KEEP                     __attribute__((used, section(".rodata.keep")))
    

#define SECTION(s) attribute ((section(#s)))
#define NO_INIT attribute ((section(“.bss.no_init”)))

  • 2.2 aligned:对齐

    GNU C 通过 __attribute__ 来声明 aligned 和 packed 属性,指定一个变量或类型的对齐方式。

    #define PACKED                      __attribute__((packed))
    

    通过aligned属性,我们可以显示地指定变量在内存中的地址对齐方式。aligned有一个参数,表示要按几个字节对齐,使用时要注意,地址对齐的字节数必须是 2 的幂次方,否则编译就会报错。

    2.3 packed:对齐

    aligned 属性一般用来增大变量的地址对齐,元素之间地址对齐会造成一定的内存空洞,而packed属性则正好相反,一般用来减少地址对齐,指定变量或类型使用最可能小的地址对齐方式。

    示例一:

    struct my{ char ch; int a;}    
     sizeof(int)=4;
     sizeof(my)=8;//(非紧凑模式)
    struct my{ char ch; int a;}__attrubte__ ((packed))        sizeof(int)=4;sizeof(my)=5
    

    示例二:

    struct MyStruct3 {
        char c;
        long a;
        int  i;
        long d;
        short s;
    }__attribute__ ((aligned(8)));
    

    sizeof(struct MyStruct3 ) = 40 //每一个变量占8个字节

    注意:若连着多个变量未达到对齐要求,可以存放在一个对齐单位内。
    示例二:

    struct MyStruct3 {
        char c;
          short s;
        int  i;
        long a;
        long d;
    

} attribute ((aligned(8)));

  • sizeof(struct MyStruct3 ) = 32 //前三个变量占8个字节,后面变量a、d各占8个字节

    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:弱声明

    C语言强、弱符号,强、弱引用
    在公司项目开发的过程中,为了实现多平台兼容,增加代码的通用性,一般都是面向对象的设计思路;如果我们需要新增一个函数getTime,但是又不想影响到其他平台的使用,这个时候就需要弱引用。
    弱引用:有强引用优先编译强引用,没有则编译器弱引用函数,避免编译出错

    #define WEAK                        __attribute__((weak))
    __attribute__ ((weak)) int32_t  getTime()
    {
        OSA_WARN(" is unsupported\n");
    

}

  • 将一个函数和变量强符号,转换为弱符号。使用方法如下:

    void __attribute__((weak)) func(void);
    int variable __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));
    

    注意:
    inline仅仅是建议编译器内联,但不一定内联,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语句就需要退出来的情况,该属性可以避免出现错误信息。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号