C语言__attribute__

__attribute__ 是 GCC 编译器提供的一套**"给编译器看的注释"**,让你能精确控制:

  • 代码怎么生成

  • 变量怎么放

  • 函数怎么调用

  • 链接怎么处理

在嵌入式底层开发中几乎是必备技能。

基本语法

复制代码
__attribute__((ATTRIBUTE_NAME))
// 或带参数
__attribute__((ATTRIBUTE_NAME(parameter)))

第一类:内存布局控制

命令 作用 典型场景
packed 取消结构体对齐填充,紧凑排列 协议帧解析、EEPROM存储结构
aligned(n) 强制按 n 字节对齐 DMA缓冲区、Cache对齐
section("name") 放入指定链接段 自定义内存布局、Bootloader跳转表

packed 示例

cpp 复制代码
struct __attribute__((packed)) ModbusFrame {
    uint8_t  slave_addr;   // offset 0
    uint8_t  func_code;    // offset 1
    uint16_t reg_addr;     // offset 2
    uint16_t reg_count;    // offset 4
};
// sizeof = 6 字节,不加 packed 会变成 8 或 12 字节

aligned 示例

cpp 复制代码
uint8_t __attribute__((aligned(32))) dma_buf[1024];
// 保证 DMA 缓冲区首地址是 32 字节对齐

section 示例

cpp 复制代码
const char __attribute__((section(".version"))) fw_ver[] = "V1.2.3";
// 把版本字符串放到 .version 段,配合链接脚本固定地址

第二类:链接与符号控制

命令 作用 典型场景
weak 声明弱符号,强定义可覆盖 SDK默认实现、中断默认处理
alias("target") 给函数/变量取别名 API版本兼容
used 强制保留符号,不被优化掉 中断向量表引用的函数
unused 抑制未使用警告(确实没调用) 预留接口、形参占位

weak 示例

cpp 复制代码
// SDK 提供默认实现
__attribute__((weak)) void APP_IdleHook(void) {
    // 默认空实现
}

// 用户应用里可以覆盖
void APP_IdleHook(void) {
    // 用户自己的低功耗处理
}

alias 示例

cpp 复制代码
void new_i2c_write(uint8_t addr, uint8_t *data, uint32_t len) {
    // 真实实现
}

// 旧函数名作为别名,兼容老代码
void __attribute__((alias("new_i2c_write"))) i2c_send(uint8_t addr, uint8_t *data, uint32_t len);

used 示例

cpp 复制代码
// 强制保留,即使编译器认为没被调用
void __attribute__((used)) HardFault_Handler(void) {
    while(1);
}
// 中断向量表通过地址引用它,编译器看不到调用关系

unused 示例

cpp 复制代码
// 告诉编译器:这个参数是故意不用的,不要报警告
void callback(void __attribute__((unused)) *arg) {
    // 接口要求有这个参数,但当前实现用不上
}

第三类:优化控制

命令 作用 典型场景
always_inline 强制内联展开 高频调用的微小函数
noinline 禁止内联 调试时需要保留调用栈
optimize("O0") 指定优化等级 特定函数不开优化,方便调试

always_inline 示例

cpp 复制代码
// 强制内联:GPIO 位操作(高频调用,消除函数调用开销)
static inline __attribute__((always_inline)) void gpio_toggle(void) {
    GPIOA->ODR ^= 1 << 5;
}

这里同时使用了inline 和 always_inline

1.C语言标准(C99/C11)把 inline 定义为"建议"------告诉编译器"这个函数很短,调用开销大,建议你在这里展开"。

编译器可以完全无视它,尤其在以下情况:

  • 你开了 -O0(不优化)------编译器根本懒得展开。

  • 函数体太大(递归、循环等)------编译器认为展开后代码臃肿,自动拒绝。

  • 为了优化代码尺寸(-Os),编译器宁愿保持函数调用以节省 Flash 空间。

所以,如果只写 static inline,在调试模式(-O0)下,gpio_toggle 依然会被编译成 "调用+返回" 指令,存在函数调用开销。


  1. __attribute__((always_inline)) 是 GCC 的扩展属性,它明确强制编译器进行内联操作。GCC 会无视优化等级,在调用处直接展开函数体。

  1. 那为什么还要同时写 static inline

__attribute__GCC 专属 的。如果你换用 Keil(ARMCC)IAR ,它们不认识这个语法,直接编译报错。而 static inlineC 标准语法,所有 C 编译器都认识。在非 GCC 环境下,它至少还能作为"建议"生效(或正常编译通过)。

noinline 示例

cpp 复制代码
// 强制不内联:中断处理,调试时需要看完整调用栈
void __attribute__((noinline)) HardFault_Handler(void) {
    save_context();
}

optimize("O0") 示例

cpp 复制代码
// 调试专用:强制关优化,方便在调试器中逐行观察变量
void __attribute__((optimize("O0"))) debug_i2c_read(void) {
    uint32_t timeout = 0xFFFF;   // O2 下可能被优化掉,调试器看不到
    volatile uint8_t status = 0;
    
    while (timeout--) {
        status = I2C_GetFlag();  // 可以逐行打断点
        if (status == READY) break;
    }
}
// 调试完必须删除此属性,否则 Release 版本性能严重下降

第四类:生命周期控制

命令 作用 典型场景
constructor main 之前自动执行 早期硬件初始化
destructor main 退出后自动执行 资源释放、数据存盘
deprecated 标记已废弃,编译时警告 API平滑升级

constructor 示例

cpp 复制代码
// main 之前自动执行,用于早期硬件初始化
void __attribute__((constructor)) system_pre_init(void) {
    SystemClock_Config();
    HAL_Init();
}

destructor 示例

cpp 复制代码
// main 退出后执行(嵌入式 main 几乎不退出,实际使用极少)
void __attribute__((destructor)) system_cleanup(void) {
    save_data_to_flash();
}
// 嵌入式环境更推荐用 atexit(),但同样极少用

deprecated 示例

cpp 复制代码
// 标记旧接口已废弃,编译时产生警告
void __attribute__((deprecated("请改用 new_api()"))) old_api(void) {
    // 仍有实现,但编译会报警告
}
相关推荐
工业一体机老司机39 分钟前
Python实现工业一体机Modbus-TCP通信-从协议解析到多设备轮询实战
开发语言·python·tcp/ip
GIS数据转换器42 分钟前
智慧林草“一张图“平台
java·大数据·服务器·前端·javascript·数据库·人工智能
fpcc1 小时前
跟我学C++中级篇—static_assert和assert
开发语言·c++
2401_885885041 小时前
国际语音php接口代码示例:PHP使用cURL快速调用语音发送API
android·开发语言·前端·人工智能·python·php·语音识别
不灭的黄金瞳1231 小时前
C语言实现单链表
c语言·数据结构
智恒百亿1 小时前
5090八卡服务器整机技术解析与部署常见问题(FAQ)
运维·服务器
萌动的小火苗1 小时前
Linux进程线程面试题【无答案】
linux·运维·服务器·c语言·开发语言
cuijiecheng20181 小时前
Linux下ss命令用法
linux·运维·服务器
云上工程笔记1 小时前
4090 GPU 服务器适合哪些 AI 场景?2026 年显存、推理性能、租用价格和风险对比
运维·服务器·人工智能