__attribute__是GNU C扩展下一大特性机制,用于设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute )。
__attribute__前后以两个下划线 __ 标识,后端跟 () 说明跟随参数属性。语法格式为:
__attribute__ (( attribute-list ))
常见的属性设置如下:
1、aligned
aligned用于变量、结构或联合,设定一个指定大小的对齐格式,以字节为单位(参数有效值为2的幂值),比如:
#include <stdio.h>
void printf_red(const char *s)
{
printf("\033[0m\033[1;31m%s\033[0m", s);
}
struct test_struct
{
char flag;
int state;
int length;
} __attribute__((aligned(8)));
struct test_struct test;
int main(int argc, char **argv)
{
printf_red("==================================================\r\n");
printf("This struct size is %d\r\n", sizeof(test));
printf_red("==================================================\r\n");
return 0;
}
最终运行结果如下:
aligned属性使编译器尽其所能的确保在分配变量空间时,为其设置4字节对齐大小。如果属性后不跟指定数字,编译器将依据你的目标机器情况使用最大最有益的对齐方式。
#include <stdio.h>
void printf_red(const char *s)
{
printf("\033[0m\033[1;31m%s\033[0m", s);
}
struct test_struct
{
char flag;
int state;
int length;
} __attribute__((aligned(2)));
struct test_struct test;
int main(int argc, char **argv)
{
printf_red("==================================================\r\n");
printf("This struct size is %d\r\n", sizeof(test));
printf_red("==================================================\r\n");
return 0;
}
上面的代码中指定了使用2字节对齐,但是编译器会按照最优的方式进行对齐,最终结果就是按照4字节进行对齐,上面代码的运行结果如下:
需要注意的是,attribute 属性的效力与编译器有关,如果编译器最大只支持16 字节对齐,那么此时定义32 字节对齐也是无济于事的。
2、packed
属性packed常常用于修饰结构体,表示使用最小可能的对齐,也就是取消结构体的字节对齐
#include <stdio.h>
void printf_red(const char *s)
{
printf("\033[0m\033[1;31m%s\033[0m", s);
}
struct test_struct
{
char flag;
int state;
int length;
} __attribute__((packed));
struct test_struct test;
int main(int argc, char **argv)
{
printf_red("==================================================\r\n");
printf("This struct size is %d\r\n", sizeof(test));
printf_red("==================================================\r\n");
return 0;
}
取消字节对齐后,成员变量按照1字节对齐,运行结果如下:
其作用类似于 #pragma pack(1),但是需要注意的是,packed仅仅对修饰的对象起作用,对内嵌结构体是不生效的,例如下面的代码:
![https://duojiyun.xinmouren.cn/blog/23/9/d41d8cd98f00b204e9800998ecf8427e.png](https://duojiyun.xinmouren.cn/blog/23/9/d41d8cd98f00b204e9800998ecf8427e.png)#include <stdio.h>
void printf_red(const char *s)
{
printf("\033[0m\033[1;31m%s\033[0m", s);
}
struct test_struct_1
{
char t;
int length;
};
struct test_struct
{
char flag;
int state;
int length;
struct test_struct_1 t_1;
} __attribute__((packed));
struct test_struct test;
int main(int argc, char **argv)
{
printf_red("==================================================\r\n");
printf("This struct size is %d\r\n", sizeof(test));
printf_red("==================================================\r\n");
return 0;
}
上述代码中,外层的结构体被packed修饰,取消了字节对齐,但是内部嵌套的那个结构体并未被修饰,所以依旧按照4字节进行对齐,上面代码运行结果如下:
3、at
at常用于嵌入式的开发,用来设置变量的绝对地址,指定某个变量处于RAM或 FLASH 里面的某个给定的地址,语法为__attribute__((at(addr)))。
作用有:
- 定位到flash中,一般用于固化的信息,如出厂设置的参数以及flash标记等等
- 定位到RAM中,一般用于内存池的实现
//以下代码适配STM32
const uint8_t array[] __attribute__((at(0x08002800)))={0xA5,0x5A};//将数组直接写到指定的flash地址
uint8_t __attribute__((aligned(32))) ccm_ram_base[CCM_RAM_MAX_SIZE] __attribute__((at(0X10000000))); // 内部CCM内存池
以内存池为例,经过面的内存分配以后,就可以自己进行内存的申请和释放了
//以下仅作演示
static uint32_t ccm_ram_mem_malloc(uint32_t size)
{
signed long offset = 0;
uint32_t nmemb; // 需要的内存块数
uint32_t cmemb = 0; // 连续空内存块数
uint32_t i;
if (!mallco_dev.memrdy)
mallco_dev.init(); // 未初始化,先执行初始化
if (size == 0)
return 0XFFFFFFFF; // 不需要分配
nmemb = size / CCM_RAM_BLOCK_SIZE; // 获取需要分配的连续内存块数
if (size % CCM_RAM_BLOCK_SIZE)
nmemb++;
for (offset = CCM_RAM_BLOCK_SIZE - 1; offset >= 0; offset--) // 搜索整个内存控制区
{
if (!mallco_dev.memmap[offset])
cmemb++; // 连续空内存块数增加
else
cmemb = 0; // 连续内存块清零
if (cmemb == nmemb) // 找到了连续 nmemb 个空内存块
{
for (i = 0; i < nmemb; i++) // 标注内存块非空
{
mallco_dev.memmap[offset + i] = nmemb;
}
return (offset * CCM_RAM_BLOCK_SIZE); // 返回偏移地址
}
}
return 0XFFFFFFFF; // 未找到符合分配条件的内存块
}
定位至flash中,需要增加const关键字修饰
4、section
该属性用于修饰函数或者变量,在编译时将被修饰的变量或是函数编译至特定段中。语法使用__attribute__((section(“xxx”))).
uint8_t ret attribute((section(“.test.”))) = 0;
该段语句意为定义uint8_t类型变量,其初值为0,编译放入".test."的输入段中。
例如想要实现类似RThread中的自动初始化机制或者分散函数的位置,让应用程序无需显式调用某些函数,都可以使用此功能
可以现在连接文件中定义一个段,再用宏将所有需要执行的函数包裹,这样在编译阶段就能拿到所有需要运行的函数指针(函数名称就是一个指针),我们只需一个for循环即可执行所有的函数