目录
一、命名
1、通用规范
2、文件名
3、变量名
4、函数名
5、宏名
二、变量
三、函数
1、重复代码尽可能提炼为函数
2、函数不超过 50 行
3、代码块嵌套不超过四层
4、可重入函数避免使用共享变量
5、对参数的合法性进行检查
6、设计合理的函数错误码
7、函数输入输出通过参数传递
8、设计高扇入合理扇出的函数
9、函数不变的参数使用 const 修饰
10、应检查非参数输入的有效性
11、函数的参数不应超过五个
12、除打印函数外,不使用可变长参函数
13、不对外暴露的函数应使用 static 修饰
四、宏
1、表达式善用完备的小括号
2、多条表达式用 do-while(0) 方式定义
3、不对宏参数进行操作
4、尽可能使用函数代替宏
5、常量可以使用 const 替代宏
6、格外注意魔鬼数字
7、宏内不使用改变流程的语句
五、头文件
1、除入口主函数,每个 C 文件都应该有一个头文件
2、禁止头文件的循环依赖
3、禁止包含用不到的头文件
4、头文件应该自包含
5、应当编写头文件保护符
6、禁止在头文件中定义变量
六、质量保证
1、了解编译系统的内存分配方式
2、注意接口实现的隐含约束
3、禁止内存操作越界
4、禁止内存泄漏
5、禁止引用已经释放了的空间
6、编程时,要防止差 1 的错误
8、不要使用 goto 语句。
9、注意表达式是否会上溢、下溢
10、通过对数据结构、程序算法的优化来提高效率
11、避免跳跃式访问数组成员
12、创建资源库以减少分配对象的开销
13、频繁调用的 “ 小函数” 改为 inline 函数或者宏实现
七、注释
1、优秀的代码可以自注释
2、注释内容要清楚、明了
3、对功能和意识层次进行注释
4、持续维护代码的注释
5、文件注释
6、函数注释
7、全局变量注释
附录
1、常见缩写
2、常见反义词
编码习惯因人而异,这边博客主要是自己编码习惯的一个总结,同时结合网络上的一些优秀编码规范进行完善。
同时这篇博客的内容不会太长,不会对规范进行详细介绍,主要起到一个快速索引的作用,以便需要的时候立即查看。
非常推荐阅读网络上流传的《华为C语言编程规范》。
一、命名
1、通用规范
(1)统一采用 unix 命名法,字母小写与下划线结合使用。
(2)命名需含义明确,自注释。不使用含义不明的缩写。常见缩写,见附录。
(3)使用正确的反义词表示互斥。常见缩写,见附录。
2、文件名
(1)统一采用小写字符;
(2)C 文件和对应的头文件 H 文件同名
(3)文件名可以是项目缩写_模块名称,例如 ngx_file、ngx_inet 等
3、变量名
(1)全局变量应增加“ g_” 前缀;静态变量应增加“ s_” 前缀;
(2)禁止使用单字节命名变量,但可将 i、j、k 作为局部循环变量;
(3)不使用匈牙利命名法;
(4)使用名词或者形容词+名词方式命名变量;
4、函数名
(1)函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构,例 print_record();
(2)函数指针除了前缀,其他按照函数的命名规则命名;
5、宏名
(1)对于数值和字符串等常量的定义,全部大写,单词之间通过 "_" 连接;
(2)除了头文件或编译开关等特殊标识定义,不能使用下划线 "_" 开头和结尾;
(3)命名同变量名:使用名词或者形容词+名词方式命名变量
(4)头文件保护符:PROJECTNAME_PATH_FILE_NAME_H
二、变量
(1)不用或少用全局变量作为共享变量;
(2)定义结构体结构时需要注意内存对齐,否则可能占用过多内存;
(3)网络通讯过程中需要注意字节序的变化:发送前应该进行主机序到网络序的转换;接收时也必须进行网络序到主机序的转换;
(4)面向接口编程思想,通过 API 访问数据;如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥;
(5)变量定义时最好先初始化,或者初始化与定义的近些;
// 较好的初始化:使用默认有意义的初始化
int speedup_factor = -1;
if (condition)
{speedup_factor = 2;
}//较好的初始化:使用 ?: 减少数据流和控制流的混合
int speedup_factor = condition? 2: -1;//较好的初始化:使用函数代替复杂的计算流
int speedup_factor = ComputeSpeedupFactor();
(6)全局变量的初始化和首次使用需要考虑时序;
(7)尽量减少数据类型默认转换与强制转换;
(8)建议不要在参数中做对变量进行操作(自增或自减),因为可能引用的接口函数,在某个版本升级后,变成了一个兼容老版本所做的一个宏,结果可能不可预知。
三、函数
1、重复代码尽可能提炼为函数
应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。
当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复
2、函数不超过 50 行
3、代码块嵌套不超过四层
函数的代码块嵌套深度指的是函数中的代码控制块(例如:if、for、while、switch等)之间互相包含的深度。
嵌套太多,难以阅读
4、可重入函数避免使用共享变量
必须使用共享变量时,需要加锁互斥
5、对参数的合法性进行检查
对参数的合法性检查,由调用者或函数实现负责;
建议由函数实现负责,因为调用者水平未知;
6、设计合理的函数错误码
函数可以返回错误码,标识函数的执行状态;
定义时,可通过枚举,或者宏定义进行定义;
7、函数输入输出通过参数传递
8、设计高扇入合理扇出的函数
扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它(最好小于7)
9、函数不变的参数使用 const 修饰
不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。
10、应检查非参数输入的有效性
函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入,即非参数输入。函数在使用输入参数之前,应进行有效性检查
11、函数的参数不应超过五个
12、除打印函数外,不使用可变长参函数
13、不对外暴露的函数应使用 static 修饰
四、宏
需要明确一点:宏只是简单的代码替换。
1、表达式善用完备的小括号
用宏定义表达式时,使用完备的小括号
// 如下定义的宏都存在一定的风险,例如 c/RECTANGLE_AREA(a, b),会变为 c/a * b #define RECTANGLE_AREA(a, b) a * b#define RECTANGLE_AREA(a, b) (a * b)#define RECTANGLE_AREA(a, b) (a) * (b)
// 正确的定义应为:#define RECTANGLE_AREA(a, b) ((a) * (b))
2、多条表达式用 do-while(0) 方式定义
将宏所定义的多条表达式放在大括号中,并且用 do-while(0) 方式定义宏,完全不用担心使用者如何使用宏,也不用给使用者加什么约束;
#define FOO(x) do { \printf("arg is %s\n", x); \do_something_useful(x); \
} while(0)
3、不对宏参数进行操作
使用宏时,不允许参数发生变化,例如自增与自减
4、尽可能使用函数代替宏
除非必要,应尽可能使用函数代替宏
主要是以下考虑:
(1)宏缺乏类型检查,不如函数调用检查严格;
(2)以宏形式写的代码难以调试难以打断点,不利于定位问题;
(3)宏如果调用的很多,会造成代码空间的浪费,不如函数空间效率高;
5、常量可以使用 const 替代宏
6、格外注意魔鬼数字
对于代码中的魔鬼数字需要尽可能多的注释。对于局部使用的可以定义局部 const 变量,对于全局广泛使用的必须定义const 全局变量或宏。
魔鬼数字既是含义不明的数字;
7、宏内不使用改变流程的语句
宏定义中尽量不使用 return 、 goto 、 continue 、 break 等改变程序流程的语句
五、头文件
1、除入口主函数,每个 C 文件都应该有一个头文件
2、禁止头文件的循环依赖
3、禁止包含用不到的头文件
4、头文件应该自包含
其他头文件包含这个头文件,即可使用该头文件的所有接口,不需要再额外包含其他头文件
5、应当编写头文件保护符
命名规范:PROJECTNAME_PATH_FILE_NAME_H
6、禁止在头文件中定义变量
包含该头文件的所有变量都将重复定义该变量
六、质量保证
1、了解编译系统的内存分配方式
了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。
2、注意接口实现的隐含约束
接口的实现除了和调用者传递的参数相关,往往还受制于其他隐含约束,如:物理内存的限制,网络状况,具体看“抽象漏洞原则”。
3、禁止内存操作越界
内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果非常严重。
有以下措施防止内存越界:
(1)数组的大小要考虑最大情况,避免数组分配空间不够;
(2)避免使用危险函数 sprintf /vsprintf/strcpy/strcat/gets 操作字符串,使用相对安全的函数 snprintf/strncpy/strncat/fgets代替;
(3)使用 memcpy/memset 时一定要确保长度不要越界
(4)字符串考虑最后的 ’\0’, 确保所有字符串是以 ’\0’ 结束
(5)指针加减操作时,考虑指针类型长度
(6)数组下标进行检查
(7)使用时 sizeof 或者 strlen 计算结构/字符串长度,避免手工计算
4、禁止内存泄漏
内存和资源(包括定时器/文件句柄/Socket/队列/信号量/GUI等各种资源)泄漏是常见的错误,例如异常出口处没有释放内存。
有以下措施防止内存泄漏:
(1)异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全部释放
(2)删除结构指针时,必须从底层向上层顺序删除
(3)使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了
(4)避免重复分配内存
(5)小心使用有return、break语句的宏,确保前面资源已经释放
(6)检查队列中每个成员是否释放
5、禁止引用已经释放了的空间
在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块,而另一模块在随后的某个时刻又使用了它。要防止这种情况发生。
有以下措施防止引用已经释放了的空间:
(1)内存释放后,把指针置为NULL;使用内存指针前进行非空判断。
(2)耦合度较强的模块互相调用时,一定要仔细考虑其调用关系,防止已经删除的对象被再次使用。
(3)避免操作已发送消息的内存。
(4)自动存储对象的地址不应赋值给其他的在第一个对象已经停止存在后仍然保持的对象(具有更大作用域的对象或者静态对象或者从一个函数返回的对象)
6、编程时,要防止差 1 的错误
要防止把 “=” 误写成 “>” 等错误。编完程序后,应对这些操作符进行彻底检查。使用变量时要注意其边界值的情况。
7、所有的 if ... else if 结构应该由 else 子句结束,对没有 else 分支的 if 语句要小心对待 ; switch 语句必须有 default 分支。
8、不要使用 goto 语句。
9、注意表达式是否会上溢、下溢
/*unsigned char 变量大于 0,不会出现小于 0 的情况。当 size 等于 0 时,再减不会小于 0,而是 0xFF,故程序是一个死循环
*/
unsigned char size ;
…
while (size-- >= 0) // 将出现下溢
{
... // program code
}/*应如下修改
*/
char size; // 从unsigned char 改为char
…
while (size-- >= 0)
{
... // program code
}
10、通过对数据结构、程序算法的优化来提高效率
11、避免跳跃式访问数组成员
多维数组在内存中是从最后一维开始逐维展开连续存储的。所以对于多维大数组,避免来回跳跃式访问数组成员
12、创建资源库以减少分配对象的开销
例如,使用线程池机制,避免线程频繁创建、销毁的系统调用;使用内存池,对于频繁申请、释放的小块内存,一次性申请一个大块的内存,当系统申请内存时,从内存池获取小块内存,使用完毕再释放到内存池中,避免内存申请释放的频繁系统调用
13、频繁调用的 “ 小函数” 改为 inline 函数或者宏实现
如果编译器支持 inline,可以采用 inline 函数。否则可以采用宏。
在做这种优化的时候一定要注意下面 inline 函数的优点:
其一编译时不用展开,代码SIZE小;
其二可以加断点,易于定位问题,例如对于引用计数加减的时候;
其三函数编译时,编译器会做语法检查。三思而后行
七、注释
1、优秀的代码可以自注释
2、注释内容要清楚、明了
清楚、明了、含义准确,防止注释二义性;
3、对功能和意识层次进行注释
对代码的功能和意识层次进行注释,不要重复描述代码的功能;
4、持续维护代码的注释
修改代码时,需要维护代码的注释,保证代码与注释的一致性;
5、文件注释
可参考如下规范
/*****************************************************************************
Copyright: 1988-1999, Huawei Tech. Co., Ltd.
File name: 文件名
Description: 用于详细说明此程序文件完成的主要功能,与其他模块或函数的接口,输出
值、取值范围、含义及参数间的控制、顺序、独立或依赖等关系
Author: 作者
Version: 版本
Date: 完成日期
History: 修改历史记录列表,每条修改记录应包括修改日期、修改
者及修改内容简述。
*****************************************************************************/
6、函数注释
函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等;
/*************************************************
Function: // 函数名称
Description: // 函数功能、性能等的描述
Calls: // 被本函数调用的函数清单Input: // 输入参数说明,包括每个参数的作
用、取值说明及参数间关系。
Output: // 对输出参数的说明。
Return: // 函数返回值的说明
Others: // 其它说明
*************************************************/
7、全局变量注释
全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。
/* The ErrorCode when SCCP translate */
/* Global Title failure, as follows */ /* 变量作用、含义*/
/* 0 -SUCCESS 1 -GT Table error */
/* 2 -GT error Others -no use */ /* 变量取值范围*/
/* only function SCCPTranslate() in */
/* this modual can modify it, and other */
/* module can visit it through call */
/* the function GetGTTransErrorCode() */ /* 使用方法*/
BYTE g_GTTranErrorCode;
附录
1、常见缩写
缩写 | 解释 | 缩写 | 解释 |
arg | argument(参数) | cfg | configuration(配置) |
buff | buffer | dev | device(设备) |
clk | clock(时钟) | err | error |
cmd | command(命令) | hex | hexadecimal(十六进制) |
cmp | compare(比较) | inc | increment(增) |
init | initialize(初始化) | prev | previous(上一个) |
max | reg | register(注册) | |
min | sem | semaphore(信号) | |
msg | message(消息) | stat | statistic(统计) |
sync | synchronize(同步) | tmp | temp(临时) |
src | source | dst | destination |
recv | receive(接收) |
2、常见反义词
add/remove | insert/delete | increment/decrement | lock/unlock |
old/new | begin/end | source/destination | first/last |
put/get | open/close | start/stop | show/hide |
copy/paste | create/destroy | get/release | add/delete |
min/max | next/previous | send/receive | up/down |