C语言之__attribute__

公众号:嵌入式不难

__attribute__声明函数属性

可以使用__attribute__来声明函数的属性,这些属性可以帮助编译器优化调用或更仔细地检查代码的正确性。例如,属性(noreturn)用来指定函数从不返回。函数属性声明由__attribute__关键字引入,函数后跟一个用双括号括起来的属性说明。你可以在声明中指定多个属性,方法是在双括号里面用逗号将各个声明分开。

aligned

  • aligned(alignment)
    对齐属性指定函数的第一条指令的最小对齐方式,以字节为单位,对齐时必须是2的整数幂。

formart

  • format(archetype, string-index, first-to-check)
    format属性指定函数采用 printf、scanf、strftime 或 strfmon 样式参数,这些参数应根据格式字符串进行类型检查。
    –archetype指定了按照什么格式进行校验,参数应为printf、scanf、strftime 或 strfmon。
    –string-index指定了被声明函数的第几个参数(从1开始)为format格式化字符串参数。
    –first-to-check指定了被申明函数的第几个参数(从1开始)为可变参数的第一个参数。
    现有函数如下
extern int my_printf(void *my_object, const char *my_format, ...) __attribute__((format (printf, 2, 3)));
//摘取printf的定义为 int printf(const char *format, ...)
//archetype=printf:按照printf格式进行检查my_printf
//string-index=2:my_printf的第2个参数(my_format)对应了printf参数中的format
//first-to-check=3:my_printf的第3个参数(...)对应了printf参数中的...

此时编译器会检查 my_printf 的调用中的参数是否与 printf 样式格式字符串参数 my_format 保持一致。

__attribute__指定基础类型,数组,结构体

aligned

  • aligned (alignment)

aligend指定了最小对齐边界字节数,参数alignment必须是2的幂次方,例如1,2,4,8…当alignment为空时,等效于目标最大对齐边界(通常但不一定是8/16字节),指定参数会影响变量地址和占用内存两个属性。

aligned应用于基础类型变量和数组时

编译器对于基础类型变量和数组的编译原则如下:

  1. 系统默认按照变量类型本身占用内存的字节数来对齐边界(地址原则)
  2. 系统按照变量类型本身占用内存来分配内存字节数(内存原则)
  3. 通俗来讲,针对于基础类型变量和数组时,属性限定了变量存储的内存首地址,但是并没有影响变量实际占用内存的大小。

举例如下

#include <stdio.h>

int main(int argc, char *argv[])
{
    int x;
    int y __attribute__((aligned (16))) = 0;
    char z[15] __attribute__((aligned (16)));

    printf("sizeof(char) = %lu Bytes, sizeof(int) = %lu Bytes\r\n", sizeof(char), sizeof(int));
    printf("x.addr = %p, sizeof(x) = %lu Bytes\r\n", &x, sizeof(x));
    printf("y.addr = %p, sizeof(y) = %lu Bytes\r\n", &y, sizeof(y));
    printf("z[0].addr = %p, sizeof(z) = %lu Bytes\r\n", &z[0], sizeof(z));
    return 0;
}

/*实验结果
sizeof(char) = 1 Bytes, sizeof(int) = 4 Bytes
x.addr = 0x7ffea29250fc, sizeof(x) = 4 Bytes
y.addr = 0x7ffea29250f0, sizeof(y) = 4 Bytes
z[0].addr = 0x7ffea2925100, sizeof(z) = 15 Bytes
*/
/*分析过程
x:没有指定参数,等效于__attribute((aligned(sizeof(int)))),地址(0x7ffea29250fc)能被4整除,占用内存为4
y:指定了参数__attribute__((aligned (16))),地址(0x7ffea29250f0)能被16整除,占用内存为4
z:指定了参数__attribute__((aligned (16))),地址(0x7ffea2925100)能被16整除,占用内存为15
*/
aligned应用于结构体时

编译器对于结构体边界对齐的原则如下:

  1. 结构体整个占用字节默认按照结构体内最大基础变量类型占用字节数对齐(内存原则)
  2. 结构体内部的基础变量类型默认按照变量类型本身占用字节来对齐边界(地址和内存原则)
  3. 结构体变量的首地址默认按照结构体内最大基础变量类型占用字节数对齐边界(地址原则)
  4. 注意:结构体内有结构体时,需要拆分到最基础的数据类型
#include <stdio.h>
typedef struct F{char a[60];}F_T;
typedef struct F1{
    char i;
    short j;
    int k;
}F1_T;
typedef struct F2{
    char i;
    short j;
    int k;
}__attribute__((aligned(16))) F2_T;
typedef struct F3{
    char i;
    short j;
    int k;
    F_T f;
}__attribute__((aligned(16))) F3_T;
int main(int argc, char *argv[])
{
    F_T f;
    F1_T f1;
    F2_T f2;
    F3_T f3;

    printf("sizeof(char)=%lubytes, sizeof(short)=%lubytes, sizeof(int)=%lubytes\r\n", sizeof(char), sizeof(short), sizeof(int));
    printf("f.addr=%p, sizeof(F_T)=%lubytes\r\n", &f, sizeof(F_T));
    printf("f1.addr=%p, sizeof(F1_T)=%lubytes\r\n", &f1, sizeof(F1_T));
    printf("f2.addr=%p, sizeof(F2_T)=%lubytes\r\n", &f2, sizeof(F2_T));
    printf("f3.addr=%p, sizeof(F3_T)=%lubytes\r\n", &f3, sizeof(F3_T));

    return 0;
}
/*实验结果
sizeof(char)=1bytes, sizeof(short)=2bytes, sizeof(int)=4bytes
f.addr=0x7ffd9b9e85e0, sizeof(F_T)=60bytes
f1.addr=0x7ffd9b9e85c0, sizeof(F1_T)=8bytes
f2.addr=0x7ffd9b9e85d0, sizeof(F2_T)=16bytes
f3.addr=0x7ffd9b9e8620, sizeof(F3_T)=80bytes
*/
/*分析过程
F_T&f:没有指定参数,等效于typedef struct F{...}__attribute((aligned(sizeof(char)))) F_T;
    结构体占用内存为60*sizeof(char);
    首地址(0x7ffd9b9e85e0)能被sizeof(char)整除;
F1_T&f1:没有指定参数,等效于typedef struct F1{...}__attribute((aligned(sizeof(int)))) F1_T;
    结构体占用内存为8字节,因为char和short类型可以存放在一个int占用的内存里,且不违背变量对齐原则
    首地址(0x7ffd9b9e85c0)能被sizeof(int)整除
F2_T&f2:指定按照16字节对齐
    结构体占用内存: 必须是16的整数倍,结果为16字节
    首地址必须能被16整除: 地址(0x7ffd9b9e85d0)满足
F3_T&f3:指定按照16字节对齐
    结构体占用内存: 必须是16的整数倍,结果为80字节,满足要求
    首地址必须能被16整除: 地址(0x7ffd9b9e8620)满足要求
    注意事项:F3_T内部包含了一个F_T(占用60字节),分析时需要拆开结构体,看内部的基础类型
*/

packed

packed应用于结构体时

编译器对于packed属性原则如下:

  1. 系统应该为结构体成员按照最小的对齐方式来对齐边界(1bit或者1字节)

示例如下

#include <stdio.h>

typedef struct F1{
    char i;
    int j[2] __attribute__((packed));
}F1_T;
typedef struct F2{
    char i;
    int j[2] __attribute__((packed));
    int k;
}F2_T;
int main(int argc, char *argv[])
{
    F1_T f1;
    F2_T f2;

    printf("sizeof(char)=%lubtyes, sizeof(int)=%lubtyes \r\n", sizeof(char), sizeof(int));
    printf("f1.addr=%p, sizeof(F1_T)=%lubtyes \r\n", &f1, sizeof(F1_T));
    printf("f2.addr=%p, sizeof(F2_T)=%lubtyes \r\n", &f2, sizeof(F2_T));
    return 0;
}

/*实验结果
sizeof(char)=1btyes, sizeof(int)=4btyes 
f1.addr=0x7fffeb902240, sizeof(F1_T)=9btyes 
f2.addr=0x7fffeb902250, sizeof(F2_T)=16btyes 
*/
/*分析过程
F1_T&F1:仅仅指定结构体内部的一个参数受packed限定。
    首地址:由于结构体规则,首地址需要对齐结构体内基础类型最大占用内存字节数,这里是int,但是int由被packed限定了,所以地址按照1字节边界对齐。
    内存占用:由于结构体规则,占用内存字节需要能整除结构体内基础类型最大占用内存字节数,这里是int,但是int由被packed限定了,所以结构体能被1整除就行了,所以这里占用了9字节。
F2_T&F2:仅仅指定结构体内部的一个参数受packed限定。
    首地址:与F1_T对比,此结构体新增了int k成员,且sizoef(int)>1,所以地址需要保持与sizoef(int)边界对齐。
    内存占用:与F1_T对比,此结构体新增了int k成员,且sizoef(int)>1,所以占用内存需要能整除sizeof(int),所以这里占用了16字节。
*/

section

通常情况下,编译器将变量放置在data段/bss段。然而有时候,可能需要额外增加段,例如,映射到相关到硬件。通过section属性就可以指定到固定段空间。举例如下

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
int init_data __attribute__ ((section ("INITDATA")));

参考列表

GCC 9.4 Manual

  • 3
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
__attribute__是C语言中的一个关键字,用于对函数、变量、类型等进行属性修饰。通过__attribute__关键字可以给程序添一些特定的属性,从而改变编译器的行为或者生成特定的代码。 __attribute__关键字后面可以跟上一对圆括号,括号中可以包含多个属性修饰符。常见的属性修饰符包括: 1. aligned:指定变量的对齐方式,可以指定变量的自然对齐边界。例如,`int var __attribute__((aligned(16)));`将变量var的对齐边界设置为16字节。 2. packed:指定结构体或联合体的对齐方式,可以使其以最小的空间进行存储。例如,`struct __attribute__((packed)) MyStruct { char a; int b; };`将结构体按照紧凑的方式进行存储。 3. deprecated:表示该函数或变量已经过时不推荐使用,编译器会给出相关警告信息。例如,`void oldFunction() __attribute__((deprecated));`表示该函数已经过时。 4. noreturn:表示函数不会返回,可以用于标记程序的终止函数,如exit()或abort()。编译器可以进行一些优化。例如,`void terminate() __attribute__((noreturn));`表示该函数不会返回。 5. unused:表示函数或变量未被使用,编译器会给出相关警告信息。例如,`int unusedVar __attribute__((unused));`表示该变量未被使用。 6. format:指定函数参数格式化检查,可以用于格式化打印函数,如printf()。例如,`void myPrintf(char *format, ...) __attribute__((format(printf, 1, 2)));`表示第一个参数是格式字符串,后面的参数按照格式字符串进行处理。 这些只是一些常见的属性修饰符,__attribute__还支持其他更多的修饰符,具体可以根据不同的编译器和平台进行查阅。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值