一、代码风格规范
1. 命名规则
变量命名:采用小写字母加下划线的组合,例如current_speed
、sensor_value
。避免使用单个字符命名,除非是循环计数器(如i
、j
)。
函数命名:使用动词 + 名词的结构,清晰表达函数功能,如calculate_average()
、init_gpio()
。
宏与常量命名:全部大写,单词间用下划线分隔,如MAX_BUFFER_SIZE
、ADC_REF_VOLTAGE
。
2. 代码缩进与格式
- 采用 4 个空格缩进(而非 Tab),确保代码在不同编辑器中显示一致。
- 大括号位置:左大括号放在语句末尾,右大括号单独一行,例如:
if (condition) {
// 执行语句
} else {
// 执行语句
}
- 每行代码长度限制在 80-100 个字符,长表达式应适当换行。
3. 注释规范
- 函数头部注释:说明函数功能、参数、返回值及注意事项,例如:
/**
* @brief 初始化GPIO引脚
* @param pin_num 引脚编号
* @param direction 方向(INPUT/OUTPUT)
* @return 成功返回0,失败返回-1
*/
int init_gpio(uint8_t pin_num, uint8_t direction);
- 行注释:用于解释复杂逻辑或关键步骤,避免冗余注释。
二、内存管理规范
1. 静态内存分配优先
嵌入式系统资源有限,优先使用静态内存分配(如全局变量、局部数组),减少动态内存分配(malloc/free
)。例如:
#define BUFFER_SIZE 1024
static uint8_t buffer[BUFFER_SIZE]; // 静态缓冲区
2. 动态内存管理原则
- 必须检查
malloc
返回值是否为NULL
。 - 确保内存释放(
free
)与分配(malloc
)成对出现,避免内存泄漏。 - 禁止在中断服务函数中使用动态内存分配。
3. 内存访问边界检查
操作数组或缓冲区时,必须进行边界检查,防止越界访问:
void copy_data(uint8_t* src, uint8_t* dst, uint32_t length) {
if (length > MAX_BUFFER_SIZE) {
// 错误处理
return;
}
// 复制数据
}
三、指针与结构体使用规范
1. 指针安全使用
- 指针初始化时赋值为
NULL
,使用前检查是否为NULL
。 - 避免使用野指针,例如:
uint8_t* ptr = NULL;
ptr = malloc(10);
if (ptr != NULL) {
// 使用ptr
free(ptr);
ptr = NULL; // 释放后立即置为NULL
}
2. 结构体设计
- 按成员变量大小排序(从大到小),减少内存对齐带来的空间浪费。
- 使用
#pragma pack
控制结构体对齐方式时,需谨慎权衡访问效率与空间占用。
四、中断服务函数(ISR)规范
1. 中断函数设计原则
- 保持 ISR 简短,避免复杂计算或耗时操作。
- 禁止在 ISR 中调用可能阻塞的函数(如
printf
)。 - 使用全局标志变量与主程序通信,例如:
volatile uint8_t flag = 0; // 必须使用volatile关键字
void EXTI0_IRQHandler(void) {
// 清除中断标志
flag = 1; // 设置标志通知主程序
}
2. 临界区保护
- 对共享资源的访问需进行临界区保护,例如:
void disable_interrupts(void);
void enable_interrupts(void);
void update_shared_data(uint32_t value) {
disable_interrupts();
shared_data = value;
enable_interrupts();
}
五、错误处理与断言
1. 错误码设计
- 函数通过返回值传递错误码,例如:
#define ERROR_OK 0
#define ERROR_PARAM -1
#define ERROR_TIMEOUT -2
int read_sensor_data(uint8_t* data, uint32_t length) {
if (data == NULL) {
return ERROR_PARAM;
}
// 读取传感器数据
return ERROR_OK;
}
2. 断言(Assert)使用
- 在开发阶段使用断言检查程序逻辑,例如:
#include <assert.h>
void set_duty_cycle(uint8_t value) {
assert(value <= 100); // 确保占空比在0-100范围内
// 设置PWM占空比
}
六、编译优化与调试信息
1. 编译优化级别选择
- 开发阶段使用低优化级别(如
-O0
),便于调试。 - 生产阶段根据性能需求选择适当优化级别(如
-O2
),但需进行充分测试。
2. 调试信息保留
- 保留调试符号(
-g
选项),便于使用调试工具(如 GDB)定位问题。
七、可移植性考虑
1. 数据类型定义
- 使用标准整数类型(如
stdint.h
中的uint8_t
、int32_t
)替代平台依赖类型。 - 通过条件编译处理平台差异,例如:
#ifdef __ARM_ARCH__
// ARM架构特定代码
#else
// 其他架构代码
#endif
2. 避免依赖编译器特性
- 不使用特定编译器的扩展特性(如 GCC 的
__attribute__
),除非必要。 - 显式指定数据类型大小,避免依赖默认类型长度。
八、代码测试与验证
1. 单元测试
- 为关键函数编写单元测试用例,覆盖正常情况和边界条件。
- 使用测试框架(如 Unity、CMock)辅助测试。
2. 内存泄漏检测
- 使用工具(如 Valgrind)检测内存泄漏,尤其在使用动态内存分配时。
九、文档与版本控制
1. 接口文档生成
- 使用 Doxygen 等工具生成 API 文档,便于团队协作。
2. 版本控制系统
- 使用 Git 等版本控制系统管理代码,遵循分支管理策略(如 Git Flow)。
十、代码安全规范
1. 输入验证
- 对用户输入或外部数据进行合法性验证,防止缓冲区溢出等安全漏洞。
2. 禁用危险函数
- 避免使用
gets()
、strcpy()
等不安全函数,改用fgets()
、strncpy()
等安全版本。
以上规范是嵌入式 C 语言开发的基础准则,实际项目中可根据需求进行调整和扩展。遵循规范有助于提高代码质量、可维护性和安全性。
如觉得有用,欢迎关注我,学习更多嵌入式开发小知识,让你嵌入式开发逐步得心应手!