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写可能导致程序崩溃或数据损坏的严重问题。
  • 修改函数时同步更新注释。我见过太多注释和代码对不上的情况,那比没注释更坑人。

最后说一句:好的函数头注释,不是写给编译器看的,是写给三个月后的你自己看的。那时候你早忘了当初为什么这么设计,但注释会帮你回忆起来。