文章首发于 “物联网学前班” 公众号,关注查看更多精彩内容。
此文为约定,并非规范,由自己的编程习惯总结而来。
一切的前提。
强烈建议使用 UTF-8 编码格式。UTF-8 编码已经广泛应用在 WEB 中,是首选的编码格式。
UTF-8 编码是 Unicode 标准中的变长编码方式,可以表示 Unicode 中任意一个字符,使用1~4个字节表示字符,且字节顺序无关。统一使用 UTF-8 字符编码可以避免各种乱码问题的出现。
但是使用 UTF-8 编码格式的目的并不是为了解决乱码问题,而是为了格式的统一,避免在国际化过程中出现乱码而已。另外,不建议在代码里使用中文(除非是协议特殊需要传输非 UTF-8 中文字符的情况)。
在使用各种 IDE 或者文本编辑器的时候,请留意字符编码格式设置,修改成 UTF-8。
结论:不建议程序里使用中文,注释也不建议使用中文,独立的文档可以是中文。
原因:
在嵌入式 C 编程里,只用 4 个空格进行缩进,杜绝使用 Tab。
由于 TAB 控制符在不同的地方其展示宽度不同,尤其是当 TAB 和空格混用的时候,导致代码乱糟糟的。
建议:
拒绝使用 TAB,使用 4 个空格代替,并使用代码格式化工具检查。
程序文件末尾必须留一行空行。
因为某些编译器会报警告(如 keil)。
建议每一小块代码前后空一行。
不建议的 示例代码:
void main(void) /* 这里多留了空格 */
{ /* 这里多留了空格 */
while(1)
{
/* TODO */
}
/* 这里空了很多行 */
}
命名风格
个人喜欢 Unix 风格,使用全小写字母加 _
命令,简单直观。
全局变量命名
关于全局变量,虽不建议全局变量满天飞,但偶尔还是需要定义全局变量的,我习惯将全局变量名以前缀 g_
开头。
枚举类型命名
枚举元素名全大写,枚举名也建议全大小,枚举元素必须显示给出明确值。
typedef 重命名
typedef 重命名使用全小写格式,并以 _t
后缀结尾。
宏定义全部大写
文件、文件夹命名请全部使用小写字母加 _
的方式。
一个文件的文件头部应该给出文件名、许可信息、更新历史等内容。我常用的格式如下所示:
/*
* Copyright (c) 2021, <这里替换你的邮箱或者公司的名称>
*
* <这里替换成使用的许可协议>
*
* Change Logs:
* Date Author Notes
* 2021-01-01 xxx first version
*/
为了避免头文件被重复包含导致各种重定义问题或其他问题,通常头文件格式如下:
#ifndef __FILE_H__
#define __FILE_H__
/* 其他内容 */
#endif
通过定义一个宏来避免文件被重复包含。宏的名称需注意格式为全大写,并且是将文件名全大写并前后加两个 _
转变而来。
头文件里按照如下方式进行 C/C++ 兼容。
#if defined(__cplusplus)
extern "C" {
#endif /* __cplusplus */
/* 这里放数据结构和函数原型 */
#if defined(__cplusplus)
}
#endif /* __cplusplus */
可以考虑将每行的字符数量控制在 80 个字符以内。
出现 80 线受限于当时的计算机技术,电脑昂贵,显示器小,代码通常要打印出来阅读(打印机仅支持 80 列)。但现在的显示器已经不是问题了,还有很多人会配置带鱼屏来阅读代码,所以这条规则可选择应用。
现在使用 80 线规则有一个好处是可以方便地将两个文件横向并排显示在屏幕上,方便同一个文件前后同时阅读,或者进行文件内容比较。如果你觉得 80 个字符过少,动不动就超过的话,可以考虑 120 线。
另外,如果一行过长,需要考虑是不是变量名过长、嵌套过深,如果是这样,是时候优化代码啦。如果这些都不是,那么就主动换行以便于阅读。
代码在更新迭代的过程中,难免会遗漏删除不再使用的变量,请留意编译器警告,及时清理。
不留未用变量的目的:一是为了严谨可读;而是为了清除编译器警告。
并不是所有的编译器警告都是无关紧要的,有些警告关乎程序的命运!
示例代码:
上图例子中的警告就是一个严重的错误,而且是一个典型的很难发现的一个手误。如果你没有看警告,没有察觉到这个问题,那代码跑起来,指不定会出什么幺蛾子。
提示:
警告,还是能修的都给修了吧,没有坏处。
不匹配的数据类型会导致编译器报警告,警告不能忽视。
预编译要处理的条件,请使用宏定义,不要在预处理条件里写枚举常量。
因为预编译时,枚举值时恒定的,会导致预编译条件永远成立,参考 CSDN。
杜绝全局变量满天飞。必须用到全局变量的时候,请务必将其限定在非常小的作用域范围内,能加 static 就加 static,禁止跨文件引用全局变量。
如需要使用另外一个文件里的全局变量,请在那个文件里加 set、get 接口来引用。
typedef struct
{
void *arg;
} test_struct_t; /* 注意这里 } 后面有一个空格 */
if (1 == 1) /* 注意 if 与 '(' 之间有一个空格,元素 '1' 与操作符 '==' 之间有一个空格 */
{
/* TODO */
}
{}
的使用有的人喜欢在一行代码的末尾使用 {
,而有的人喜欢将 {
和 }
单独占一行,我喜欢后者,原因如下:
可以方便地注释 {
前面的一行代码而不影响使用,示例如下:
void main(void)
{
/* while (1) */
{
}
}
所以为了统一,也建议在定义枚举、结构体的时候也将 {
和 }
单独占一行。
单行注释请使用这样的形式,避免使用 “//” 形式的注释,因为其实 C++ 类型的注释,某些编译器不支持。
注释只允许出现在语句的前面和后面。
函数的注释,重在描述功能和注意事项。
/* 注释内容与注释标记之间需要留一个空格,像这样 */
/* API 的注释,示例如下 */
/**
* test_func
*
* @brief Test function. Used to show the name and age.
*
* @param name Input param, Specify user name.
* @param age Input param, Specify user age.
*
* @return void
*
*/
void test_func(const char *name, uint8_t age);
#if 0
或 #if 1
或成片的代码注释结论:不建议使用。
如果是测试用途或者其他用途,一定要注释说明这么写的原因,以及是否可以移除或者修改。
如果是遗弃代码,请明显标识遗弃字样,并给出替代接口,或者给出遗弃原因。
不建议使用 bool 类型。
C 语言标准(C89) 没有定义布尔类型,如果你使用 true 和 false,会出现错误。
C99 还提供了一个头文件
由于这个历史原因,导致一些代码模块会自定义 bool 类型,也自定义 TRUE 和 FALSE。
不同的 C 标准库对 bool 类型的大小定义不同。
建议使用
基本数据类型,除非设计不同平台 size 不同的情况,请直接用 C99
定义一定赋初值。
枚举的大小也不是恒定的,根编译器有关。在配合结构体使用时请注意不要随便使用相对结构体指针的偏移量来读取数据成员的值。
return
和 goto
主要为了留意是否会有内存泄漏,或者使用了未初始化的变量。
如果你的项目里引入了第三方模块,但是第三方模块跟你项目的代码风格不一致,这个时候怎么做呢?
为了方便日后更新维护,建议保留其原来的文件格式,不做任何修改(一个好的模块,也基本不需要做修改)。
如果必须要修改第三方模块的代码,视情况转化为统一的风格,轻改动风格不变,重改动统一风格(既然是重改动,肯定需要自己维护这套代码)。
注意在版权范围内引入和修改第三方代码。
参考 中文文案排版指北。
仅站在巨人的肩膀上做了些许的总结,向他们致敬!
RT-Thread 代码规范
受 RT-Thread 编程风格影响很大。
MISRA-C 官网
知乎-szyyy-MISRA C标准