《C语言编程规范学习笔记》
代码总体原则:
1. 清晰:
程序必须为阅读它的人而编写,只是顺便用于机器执行。
编写程序应该以人为本,计算机第二。
2. 简洁:
废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
3. 风格:
如果重构/修改其他风格的代码时,比较明智的做法是根据现有代码的现有风格继续编写代码。
头文件:
我们倾向于减少包含头文件,尤其是在头文件中包含头文件,以控制改动代码后的编译时间。
1. 头文件中适合放置接口的声明,不适合放置实现。
2. 头文件应当职责单一。
3. 头文件应向稳定的方向包含。
规则:
1. 每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口。
2. 禁止头文件循环依赖。
3. .c/.h文件禁止包含用不到的头文件。
4. 头文件应当自包含。
说明:
简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。
5. 总是编写内部#include保护符(#define 保护)。
6. 禁止在头文件中定义变量。
7. 只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量。
8. 禁止在extern
"C"中包含头文件。
建议:
1. 一个模块通常包含多个.c文件,建议放在同一个目录下,目录名即为模块名。为方便外部使用者,建议每一个模块提供一个.h,文件名为目录名。
2. 如果一个模块包含多个子模块,则建议每一个子模块提供一个对外的.h,文件名为子模块名。
说明:降低接口使用者的编写难度。
3. 头文件不要使用非习惯用法的扩展名,如.inc。
4. 同一产品统一包含头文件排列方式。
函数:
函数设计的精髓:编写整洁函数,同时把代码有效组织起来。
1. 一个函数仅完成一项功能。
2. 重复代码应该尽可能提炼成函数。
3. 避免函数过长,新增函数不超过50行(非空非注释行)。
4. 避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
5. 可重入函数应避免使用共享变量;若需要使用,则应通过互斥手段(关中断、信号量)对其加以保护。
说明:可重入函数是指可能被多个任务并发调用的函数。在多任务操作系统中,函数具有可重入性是多个任务可以共用此函数的必要条件。共享变量指的全局变量和static变量。
编写C语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。
6. 对参数的合法性检查,由调用者负责还是由接口函数负责,应在项目组/模块内应统一规定。缺省由调用者负责。
7. 对函数的错误返回码要全面处理。
示例写法:
FILE *fp = fopen(
"./writeAlarmLastTime.log","r");
if(fp == NULL)
{
return;
}
char buff[128] = "";
if (fscanf(fp,“%s”,buff) == EOF) //检查函数fscanf的返回值,确保读到数据
{
fclose(fp);
return;
}
fclose(fp);
long fileTime = getAlarmTime(buff); //解析获取最新的告警时间;
8. 设计高扇入,合理扇出(小于7)的函数。
说明:扇出是指一个函数直接调用(控制)其它函数的数目,而扇入是指有多少上级函数调用它。
9. 废弃代码(没有被调用的函数和变量)要及时清除。
10. 函数不变参数使用const。
说明:不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。
11. 函数应避免使用全局变量、静态局部变量和I/O操作,不可避免的地方应集中使用。
12. 检查函数所有非参数输入的有效性,如数据文件、公共变量等。
13. 函数的参数个数不超过5个。
14. 除打印类函数外,不要使用可变长参函数。
15. 在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。
标识符通用命名规则:
1. 标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
2. 除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。
3. 产品/项目组内部应保持统一的命名风格。
4. 用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
5. 尽量避免名字中出现数字编号,除非逻辑上的确需要编号。
6. 标识符前不应添加模块、项目、产品、部门的名称作为前缀。
7. 平台/驱动等适配代码的标识符命名风格保持和平台/驱动一致。
8. 重构/修改部分代码时,应保持和原有代码的命名风格一致。
文件命名规则:
1. 文件命名统一采用小写字符。
变量命名规则:
1. 全局变量应增加“g_”前缀。
2. 静态变量应增加“s_”前缀。
3. 禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。
4. 不建议使用匈牙利命名法。
5. 使用名词或者形容词+名词方式命名变量。
函数命名规则:
1. 函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。
2. 函数指针除了前缀,其他按照函数的命名规则命名。
宏的命名规则:
1. 对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线‘_’的方式命名(枚举同样建议使用此方式定义)。
2. 除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线‘_’开头和结尾。
变量:
1. 一个变量只有一个功能,不能把一个变量用作多种用途。
2. 结构功能单一;不要设计面面俱到的数据结构。
3. 不用或者少用全局变量。
4. 防止局部变量与全局变量同名。
5. 通讯过程中使用的结构,必须注意字节序。
6. 严禁使用未经初始化的变量作为右值。
7. 构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的全局变量,防止多个不同模块或函数都可以修改、创建同一全局变量的现象。
8. 使用面向接口编程思想,通过API访问数据:如果本模块的数据需要对外部模块开放,应提供接口函数来设置、获取,同时注意全局数据的访问互斥。
9. 在首次使用前初始化变量,初始化的地方离使用的地方越近越好。
10. 明确全局变量的初始化顺序,避免跨模块的初始化依赖。
11. 尽量减少没有必要的数据类型默认转换与强制转换。
宏、常量:
1. 用宏定义表达式时,要使用完备的括号。
2. 将宏所定义的多条表达式放在大括号中。
3. 使用宏时,不允许参数发生变化。
4. 不允许直接使用魔鬼数字。
5. 除非必要,应尽可能使用函数代替宏。
6. 常量建议使用const定义代替宏。
7. 宏定义中尽量不使用return、goto、continue、break等改变程序流程的语句。
质量保证:
1. 代码质量保证优先原则
2. 要时刻注意易混淆的操作符。
3. 必须了解编译系统的内存分配方式,特别是编译系统对不同类型的变量的内存分配规则,如局部变量在何处分配、静态变量在何处分配等。
4. 不仅关注接口,同样要关注实现。
5. 禁止内存操作越界。
6. 禁止内存泄漏。
7. 禁止引用已经释放的内存空间。
8. 编程时,要防止差1错误。
9. 所有的if ... else
if结构应该由else子句结束
;switch语句必须有default分支。
10. 函数中分配的内存,在函数退出之前要释放。
11. if语句尽量加上else分支,对没有else分支的语句要小心对待。
12. 不要滥用goto语句。
13. 时刻注意表达式是否会上溢、下溢。
程序效率:
1. 在保证软件系统的正确性、简洁、可维护性、可靠性及可测性的前提下,提高代码效率。
2. 通过对数据结构、程序算法的优化来提高效率。
3. 将不变条件的计算移到循环体外。
4. 对于多维大数组,避免来回跳跃式访问数组成员。
5. 创建资源库,以减少分配对象的开销。
6. 将多次被调用的
“小函数”改为inline函数或者宏实现。
注释:
1. 优秀的代码可以自我解释,不通过注释即可轻易读懂。
2. 注释的内容要清楚、明了,含义准确,防止注释二义性。
3. 在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码。
4. 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。
5. 文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
6. 函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
7. 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。
8. 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。
9. 对于switch语句下的case语句,如果因为特殊情况需要处理完一个case后进入下一个case处理,必须在该case语句处理完、下一个case语句前加上明确的注释。
10. 避免在注释中使用缩写,除非是业界通用或子系统内标准化的缩写。
11. 同一产品或项目组统一注释风格。
12. 避免在一行代码或表达式的中间插入注释。
13. 注释应考虑程序易读及外观排版的因素,使用的语言若是中、英兼有的,建议多使用中文,除非能用非常流利准确的英文表达。对于有外籍员工的,由产品确定注释语言。
14. 文件头、函数头、全局常量变量、类型定义的注释格式采用工具可识别的格式。
排版与格式:
1. 程序块采用缩进风格编写,每级缩进为4个空格。
2. 相对独立的程序块之间、变量说明之后必须加空行。
3. 一条语句不能过长,如不能拆分需要分行写。一行到底多少字符换行比较合适,产品可以自行确定。
4. 多个短语句(包括赋值语句)不允许写在同一行内,即一行只写一条语句。
5. if、for、do、while、case、switch、default等语句独占一行。
6. 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
7. 注释符(包括‘’)与注释内容之间要用一个空格进行分隔。
8. 源程序中关系较为紧密的代码应尽可能相邻。
表达式:
1. 表达式的值在标准所允许的任何运算次序下都应该是相同的。
2. 函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利。
3. 赋值语句不要写在if等语句中,或者作为函数的参数使用。
4. 赋值操作符不能使用在产生布尔值的表达式上。
代码编辑、编译:
1. 使用编译器的最高告警级别,理解所有的告警,通过修改代码而不是降低告警级别来消除所有告警。
2. 在产品软件(项目组)中,要统一编译开关、静态检查选项以及相应告警清除策略。
3. 本地构建工具(如PC-Lint)的配置应该和持续集成的一致。
4. 使用版本控制(配置管理)系统,及时签入通过本地构建的代码,确保签入的代码不会影响构建成功。
5. 要小心地使用编辑器提供的块拷贝功能编程。
可测性:
1. 模块划分清晰,接口明确,耦合性小,有明确输入和输出,否则单元测试实施困难。
2. 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。
3. 在同一项目组或产品组内,调测打印的日志要有统一的规定。
4. 使用断言记录内部假设。
5. 不能用断言来检查运行时错误。
6. 为单元测试和系统故障注入测试准备好方法和通道。
安全性:
1. 对用户输入进行检查。
2. 确保所有字符串是以NULL结束。
3. 不要将边界不明确的字符串写到固定长度的数组中。
4. 避免整数溢出。
5. 避免符号错误。
6. 避免截断错误。
7. 确保格式字符和参数匹配。
8. 避免将用户输入作为格式化字符串的一部分或者全部。
9. 避免使用strlen()计算二进制数据的长度。
10. 使用int类型变量来接受字符I/O函数的返回值。
11. 防止命令注入。
单元测试:
1. 在编写代码的同时,或者编写代码前,编写单元测试用例验证软件设计/编码的正确。
2. 单元测试关注单元的行为而不是实现,避免针对函数的测试。
可移植性:
1. 不能定义、重定义或取消定义标准库/平台中保留的标识符、宏和函数。
2. 不使用与硬件或操作系统关系很大的语句,而使用建议的标准语句,以提高软件的可移植性和可重用性。
3. 除非为了满足特殊需求,避免使用嵌入式汇编。
业界编程规范:
google C++编程指南
汽车业C语言使用规范(MISRA)