3、函数头注释:功能描述、参数说明、返回值说明、注意事项的规范模板
函数头注释,说白了就是函数的「身份证」。
我刚开始写嵌入式代码那会儿,总觉得函数名起得够直白就行了。直到有一次接手一个老项目,打开一个 .c 文件,满屏的函数没有一个注释。我盯着一个叫 proc_data 的函数看了半天,愣是不知道它要处理什么数据、参数怎么传、返回值啥意思。嗯,那天我加班到凌晨两点。
从那以后,我给自己定了个规矩:每个函数都必须有头注释。这不是形式主义,这是对自己和同事的尊重。
3.1 标准模板长什么样?
我个人习惯用下面这个模板。它不花哨,但该有的信息一个不少:
/**
* @brief 函数功能的一句话描述
*
* @param[in] param1 参数1的说明(输入参数)
* @param[out] param2 参数2的说明(输出参数)
* @param[in,out] param3 参数3的说明(既是输入也是输出)
*
* @return 返回值说明
*
* @note 注意事项、使用限制、调用条件等
* @warning 潜在风险或陷阱
* @see 相关函数或参考文档
*/
你想想看,有了这个模板,任何人看到你的函数,三秒钟就能知道:这函数干嘛的、怎么用、有什么坑。
3.2 逐项拆解:每个字段该怎么写?
3.2.1 @brief —— 功能描述
一句话说清楚函数做什么。别写废话。
✅ 好的写法:
/**
* @brief 计算CRC16校验值,采用Modbus RTU标准多项式0x8005
*/
❌ 差的写法:
/**
* @brief 这是一个用来计算CRC16的函数
*/
我在项目中遇到过,有人写「这是一个处理数据的函数」。你想想,哪个函数不处理数据?这种注释等于没写。
3.2.2 @param —— 参数说明
每个参数都要写清楚三件事:方向、类型、含义。
| 标记 | 含义 | 示例 |
|---|---|---|
[in] |
输入参数,函数只读 | 缓冲区指针、配置值 |
[out] |
输出参数,函数写入 | 结果存放地址 |
[in,out] |
既是输入也是输出 | 状态机当前状态 |
举个例子:
/**
* @brief 从UART缓冲区读取一帧数据
*
* @param[in] uart_id UART端口号,取值范围0~2
* @param[out] buffer 存放接收数据的缓冲区,长度至少为FRAME_MAX_LEN
* @param[in] timeout_ms 等待超时时间,单位毫秒,0表示不等待
*
* @return 成功返回接收到的字节数,超时返回0,错误返回-1
*
* @note 调用前需确保UART已初始化
* @warning 如果buffer为NULL,函数会直接返回-1,不会崩溃
*/
3.2.3 @return —— 返回值说明
很多新手只写「成功返回0,失败返回-1」。这不够。
我建议你写清楚:什么情况返回什么值,调用方该怎么判断。
小技巧:如果函数有多种失败原因,可以用枚举或宏定义错误码,然后在注释里说明每种错误码的含义。
/**
* @return 0 成功
* @return -1 参数错误(如NULL指针或越界)
* @return -2 硬件未就绪
* @return -3 超时
*/
3.2.4 @note / @warning —— 注意事项
这是最容易忽略,但也是最有价值的部分。
我曾经在一个项目中,有个函数必须在关中断后才能调用。结果新来的同事没看注释,直接在中断里调了它,导致系统死锁。排查了整整两天。
避坑指南:把你在调试过程中踩过的坑,都写进 @note 和 @warning 里。比如:
- 这个函数是否可重入?
- 是否需要关中断?
- 参数有没有取值范围限制?
- 有没有副作用(比如修改全局变量)?
- 调用前需要满足什么前置条件?
3.3 完整示例:一个真实项目中的函数头
下面是我在一个STM32项目里实际用过的函数头注释,你可以直接套用这个风格:
/**
* @brief 向指定的I2C从设备写入多个字节数据
*
* @param[in] dev_addr 从设备7位地址(左对齐)
* @param[in] reg_addr 寄存器地址
* @param[in] data 待写入数据的指针
* @param[in] len 数据长度,单位字节,最大不超过32
*
* @return 0 写入成功
* @return -1 设备无应答(NACK)
* @return -2 总线忙,超时
* @return -3 参数错误(data为NULL或len超出范围)
*
* @note 1. 调用前需确保I2C外设已初始化
* 2. 本函数会阻塞等待,最大等待时间为100ms
* 3. 不支持在中断服务函数中调用
*
* @warning 如果len为0,函数直接返回0,不会执行任何操作
* 请勿在多个任务中同时调用此函数操作同一I2C总线
*
* @see i2c_read_bytes(), i2c_init()
*/
3.4 我的一些个人习惯
- 每个函数都要有头注释,哪怕只有三行。static函数也不例外。
- 参数说明里写清楚取值范围。比如「取值范围0~7」、「buffer长度至少为64字节」。这能省去调用方很多查代码的时间。
- 返回值用多行写,每个可能的值一行。别挤在一行里,看着累。
- @note 和 @warning 不要重复。note写一般注意事项,warning写可能导致程序崩溃或数据损坏的严重问题。
- 修改函数时同步更新注释。我见过太多注释和代码对不上的情况,那比没注释更坑人。
最后说一句:好的函数头注释,不是写给编译器看的,是写给三个月后的你自己看的。那时候你早忘了当初为什么这么设计,但注释会帮你回忆起来。