概述
1.作用域变量命名规范
a.作用域越大,例如静态变量,全局变量,宏,类型名等,应该精准描述并全局唯一
b.作用域小,例如局部函数变量,结构体,联合体成员等,在能精确表达含义的前提下,应该尽量简短。
2.命名规则
类别 | 命名风格 | 形式 |
函数 结构体 枚举类型 联合体类型 typedef定义类型 | 大驼峰,或者带前缀的大驼峰 | AaaBbb, XXX AaaBbb |
局部变量,函数參数,宏参数,结构体中 | 小驼峰 | aaabbbb |
全局变量 | 带g_前缀的小驼峰 | g_aaabbb |
宏 枚举类型 goto标签 | 全大写下划线分割 | AAA_BBB |
函数宏定义 | 全大写下划线,或大驼峰,或带前缀的大驼峰 | AAA_BBB AaaBbb xxx_AaaBbb |
常量 | 全大写下划线分割 | AAA_BBB g_aaabb |
3.大括号
K&R风格
struct MyTest { // 紧跟语句末行,注意空一格
...
};
int Func (int a)
{ //函数大括号单独占一行
if (a > 0){
...
}else { //右大括号 else 与左大括号 放在同一行
...
}
};
Allma风格:所有大括号都单独占用一行
struct MyTest
{
...
};
int Func (int a)
{
if (a > 0)
{
...
}
else
{
...
}
}
4 . 行宽不超过 120 字符,但当代码超过120字符时,可以允许换行,但必须要进行同类对其
5.指针类型 *
当出现 restrict const 时, *前后都要空格
char * restrict p char * const VERSION
定义指针变量 int *p=NULL
6. 空格与空行
a.小括号内两侧,不应该加空格
b.2.3.7 空格与空行
c. if, switch, case, do, while,for 等关键字之后应加空格
d.小括号内部的两侧,不应加空格
e.一元操作符 (=4-<>*1%1&^<=>=--1)两侧都应加空格
f.一元操作符(&*+--1)之后不应加空格
g.三元操作符(?:)符号两侧都应加空格
7.头文件
a.在头文件中声明需要对外的接口
b.头文件名只使用.h,不能使用.inc
c.禁止头文件循环依赖,a.h中包含b.h, b.h中包含c.h, c.h中包含 a.h,这样会导致,其中一个头文件更改,会导致所有包含a.h b.h c.h的代码全部编译一遍
d.禁止包含用不到的头文件
e. 头文件应该自包含
f. 头文件应该使用 #define作为包含保护
例如在vos/include/timer.h
#ifndef VOS_INCLUDE_TIMER_H
#define VOS_INCLUDE_TIMER_H
....
#endif
g.仅能在头文件中以声明的方式引用外部函数接口,变量
h.禁止在 extern "C"中包含头文件
I. 合理的顺序包含头文件: C标准库 操作系统库 平台库 项目共享库 自己其他依赖
8.数据类型
a.不要重复定义基础类型
b.能用 typedef 尽量不用 #define
模块A中
例如typedef unsingned long ULONG;
模块B中
#define ULONG UINT32
这里使用与 32位,当移植到64位时,当两个模块有交互交口时,这两个定义会引起混乱
9.常量
a.禁止使用小写 L 作为常量后缀,要不然一般会以为时数字1
b.禁止使用难以理解的常量名
10.变量
a.禁止读取未初始化变量
b.禁止在子作用域重复命名
c.避免大量栈分配:栈的大小是有限的,局部变量过大会导致栈溢出
11.慎用全局变量
全局变量的缺点:
a.破坏代码的独立性和可移植性,使函数对全局产生依赖,存在耦合
b.降低代码的可读性和可维护性,当多个函数读写全局变量 时,某刻值是不确定的,不利于代码阅读和维护
c.在并发编程环境中,使用全局变量会破坏函数的可重入性,需要额外的同步保护处理才能确保数据安全
12.禁止局部变量的地址返回其作用域外,
13.避免只在一个函数中使用的变量声明为全局变量:如果声明的范围比需要的范围大,则会降低代码的可阅读性。
14.资源不在使用时,应该关闭或者释放
15 表达式
a.表达式的值在标准所允许的任何运算次序下都应该是相同的
b.函数调用不要作为另一个函数的参数使用,否则对于代码的调试、阅读都不利:当函数作为参数时,由于参数压栈次数不是代码可以控制的,可能造成未知的输出。
c. 赋值语句不要写在if等语句中,或者作为函数的参数使用:因为if语句中,会根据条件依次判断,如果前一个条件已经可以判定整个条件,则后续条件语句不会再运行,所以可能导致期望的部分赋值没有得到运行。
d. 用括号明确表达式的操作顺序,避免过分依赖默认优先级。
e. 赋值操作符不能使用在产生布尔值的表达式上。
说明:如果布尔值表达式需要赋值操作,那么赋值操作必须在操作数之外分别进行。这可以帮助避免=和= =的混淆,帮助我们静态地检查错误。
16.控制语句:
a. 控制表达式的结果必须是布尔值: 控制表达式的结果必须是布尔值,以及如下表达式的运算结果:
关系表达式:<、<=、>=、>、==、!=
逻辑表达式:&&、||、!
控制表达式应用的语句包括:
if 语句
while 语句
do...while 语句
for 语句
b. && 和 || 操作符的右侧操作数不应包含副作用 : 逻辑与(&&)、逻辑或(||) 表达式中的右操作数是否被求值,取决于左操作数的求值结果,当左操作数的求值结果可以得出整个逻辑表达式的结果时,不会在计算右操作数的结果。如果右操作数包含副作用,则不能确定是否确实发生了副作用
副作用是指:对执行状态产生影响,包括:修改对象、访问volatile对象、修改文件等。在某些情况下副作用会给程序带来不必要的麻烦,其产生的错误十分难以查找定位,并降低程序的可读性。
17 判断语句
a. 控制表达式的结果必须是布尔值,以及如下表达式的运算结果:
关系表达式:<、<=、>=、>、==、!=
逻辑表达式:&&、||、!
b.控制表达式应用的语句包括:
if 语句
while 语句
do...while 语句
for 语句
c.建议4.1.1 && 和 || 操作符的右侧操作数不应包含副作用
逻辑与(&&)、逻辑或(||) 表达式中的右操作数是否被求值,取决于左操作数的求值结果,当左操作数的求值结果可以得出整个逻辑表达式的结果时,不会在计算右操作数的结果。如果右操作数包含副作用,则不能确定是否确实发生了副作用,因此,
副作用是指:对执行状态产生影响,包括:修改对象、访问volatile对象、修改文件等。在某些情况下副作用会给程序带来不必要的麻烦,其产生的错误十分难以查找定位,并降低程序的可读性。
18.循环语句
a.循环必须安全退出
b.禁止浮点数作为循环计数器:因为存储二进制的浮点BIT位有限,所以二进制浮点数表示范围有限,并且无法精准的表示所有实数
19.goto语句
a.goto语句会破坏程序性的结构性,所以除非真的需要,最好不要使用goto语句,使用时,也只允许跳转到本函数内goto语句之后的标签
b.goto语句只能向下跳转
20.switch语句
a,要有default分支:保证遗漏的case标签能够有一个缺省的处理行
b.swith语句中至少有两个条件分支
21.声明与初始化
a.不要声明或定义保留的标识符:如果声明或者定义了一个保留的标识符,那么程序的行为是未定义的。
C11 标准 7.1.3 说明:
1)双下划线开头,或下划线加一个大写字母开头的所有标识符始终保留使用;
2)除 label 名字和结构体、共用体的成员外,其他以下划线开头的标识符都在文件范围内有效;
3)C标准库中已经定义的标识符均保留其用途;
4)errno 和其他C标准库的外部链接标识符始终作为外部链接;
5)C标准库中具有文件范围的标识符都保留用作宏的名字。
22.整数
a.确保有符号运算不会导致溢出
1)指针运算的整数操作数(指针偏移)
2)数组索引
3)变长数组的长度(及长度运算表示士)
4)内存拷贝的长度
5)内存分配的函数参数
6)循环判断条件
23.确保无符号整数运算不回绕
无符号操作数永远不会溢出,因为超出计算范围会按照结果类型可表示的最大值 +1的数取模,这种行为被称为回绕
24.确保除法和余数运算不会导致除零错误
25.整形表达式比较或者赋值为更大类型之前必须用这种更大类型对它求值:由于有符号整数溢出,和无符号整数回绕引起
26.只能对无符号整型进行位运算
27.校验外部数据中整数值的合法性
对于外部数据的所有整数值,应该严格评估,确认是否具有可被识别的上线和下线
28. 注释
a. 注释的内容要清楚、明了,含义准确,防止注释二义性:有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。
b.在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码: 注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
c. 修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。不再有用的注释要删除。
d.文件头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者姓名、工号、内容、功能说明、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。
e.函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
f. 全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。
g. 注释应放在其代码上方相邻位置或右方,不可放在下面。如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。
29.移位操作符的右操作数必须是非负数且小于左操作数的基本类型位宽
30.表示对象大小的整数值或者变量应当使用 size_t类型 :size_t 类型是sizeof操作符结果的无符号整数类型,size_t的最大值由SIZE_MAX宏表示。类型size_t通常能覆盖整个地址空间
31.确保枚举类型映射到唯一值
32.确保整数转换不会造成数据截断或者符号错误:将整数转为宽度较小的类型会导致高位截断,有符号/无符号间转换可能导致符号错误。
33.指针与数组
a.外部数据作为数组索引时,必须确保在数组大小范围内,避免内存泄漏、
b.禁止通过对数组类型的函数参数变量进行sizeof获取数组大小:因为数组作为参数传入的时,但实际会被调整为xx类型的指针类如
int Func(int array[])
{
sizeof(array) //这里不管外部array多大,这里都会等于4,相当于sizeof(int *)
};
c.禁止通过对指针变量进行sizeof操作来获取数组大小
例如 char *p = array ,其中 char array[LEN] sizeof(p) =4,而非数组大小
d.不同类型的对象指针之间不应该进行强制转换
34.不要使用变长数组类型:由于运行时才能确定数组大小,因此分配空间的起始地址也是不确定的,这种不确定性会给程序执行带来非常大的风险
35.声明一个带有外部链接的数组时,必须显示指定它的大小
36.字符串
a.确保字符串有足够的空间容纳字符数据和NULL结束符
b.对字符串进行存储操作,确保字符串有null结束符
c. 为了避免缓冲区溢出,常常会用相对安全的限制字符数量的字符串操作函数代替一些危险函数。如: 用strncpy()代替strcpy() , 用strncat()代替strcat() , 用snprintf()代替sprintf() , 用fgets()代替gets() 这些函数会截断超出指定限制的字符串,但是要注意它们并不能保证目标字符串总是以NULL结尾。如果源字符串的前n个字符中不存在NULL字符,目标字符串就不是以NULL结尾。
37.断言
a 使用断言记录内部假设:断言是对某种内部模块的假设条件进行检查,如果假设不成立,说明存在编程、设计错误。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。
b.不能用断言来检查运行时错误:断言是用来处理内部编程或设计是否符合假设;不能处理对于可能会发生的且必须处理的情况要写防错程序,而不是断言。如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来实现。
c.断言必须使用宏定义,且只能在调试版本中生效
d.避免在代码中直接使用assert():使用assert会使代码发布与NDEBUG宏发生直接联系,为避免发布版本定义受限于NEDBUG,代码中不应该直接使用assert()
e.一个断言只能用于检查一个错误
38.函数设计
a.输入校验:包括但不限于 校验数据长度 校验数据范围 校验数据类型和格式 等
b.合理设计返回值
c.对象或者函数的所有声明必须与定义具有一致的名称和类型限定符
d.设计函数优先使用返回值而不是输出参数
e.函数避免使用 void *类型参数:尽量让编译器在编译阶段就检查出类型不匹配的问题
f.数组作为函数参数时,必须同时将其长度作为函数的参数:要不然很容易造成内存访问越界
g.将字符串或者指针作为函数参数时,在函数体中应检查参数是否为NULL。
h.函数只在一个文件中使用时,应当使用static修饰符
40.内存
a.坚持下列措施可以避免内存越界:
1)数组的大小要考虑最大情况,避免数组分配空间不够。
2) 避免使用危险函数sprintf /vsprintf/strcpy/strcat/gets操作字符串,使用相对安全的函数 snprintf/strncpy/strncat/fgets代替。
3)使用memcpy/memset时一定要确保长度不要越界
4)字符串考虑最后的’ ’, 确保所有字符串是以’ ’结束
5) 指针加减操作时,考虑指针类型长度
6) 数组下标进行检查
7) 使用sizeof或者strlen计算结构/字符串长度
b.禁止内存泄漏: 内存和资源(包括定时器/文件句柄/Socket/队列/信号量/GUI等各种资源)泄漏是常见的错误。
1) 异常出口处检查内存、定时器/文件句柄/Socket/队列/信号量/GUI等资源是否全部释放 2) 删除结构指针时,必须从底层向上层顺序删除
3) 使用指针数组时,确保在释放数组时,数组中的每个元素指针是否已经提前被释放了 4) 避免重复分配内存
4) 小心使用有return、break语句的宏,确保前面资源已经释放
5) 检查队列中每个成员是否释放
c.禁止引用已经释放的内存空间
1)内存释放后,把指针置为NULL;使用内存指针前进行非空判断。
2) 耦合度较强的模块互相调用时,一定要仔细考虑其调用关系,防止已经删除的对象被再次使用。
3) 避免操作已发送消息的内存。
4) 自动存储对象的地址不应赋值给其他的在第一个对象已经停止存在后仍然保持的对象
41.质量:代码质量保证优先原则
a.正确性,指程序要实现设计要求的功能。
b.简洁性,指程序易于理解并且易于实现。
c.可维护性,指程序被修改的能力,包括纠错、改进、新需求或功能规格变化的适应能力。
d.可靠性,指程序在给定时间间隔和环境条件下,按设计要求成功运行程序的概率。
e.代码可测试性,指软件发现故障并隔离、定位故障的能力,以及在一定的时间和成本前提下,进行测试设计、测试执行的能力。
f.代码性能高效,指是尽可能少地占用系统资源,包括内存和执行时间。
g.可移植性,指为了在原来设计的特定环境之外运行,对系统进行修改的能力。
h.个人表达方式/个人方便性,指个人编程习惯。
42.可测性: 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打 印函数,并且要有详细的说明。
a.测试与最终发行的版本是通过编译开关的不同来实现的。并且编译开关要规范统一。统一使用 编译开关来实现测试版本与发行版本的区别
b. 在同一项目组或产品组内,调测打印的日志要有统一的规定:统一的调测日志记录便于集成测试,具体包括:
1) 统一的日志分类以及日志级别;
2)通过命令行、网管等方式可以配置和改变日志输出的内容和格式;
3) 在关键分支要记录日志,日志建议不要记录在原子函数中,否则难以定位;
4) 调试日志记录的内容需要包括文件名/模块名、代码行号、函数名、被调用函数名、错误码、错 误发生的环境等。
c. 使用断言记录内部假设。
d. 不能用断言来检查运行时错误。 断言只能用于程序内部逻辑的条件判断,而不能用于对外部输入数据的判断, 因为在网上实际运行时,是完全有可能出现外部输入非法数据的情况。
e.为单元测试和系统故障注入测试准备好方法和通道。
最后
以上就是矮小眼睛为你收集整理的c语言编程规范的全部内容,希望文章能够帮你解决c语言编程规范所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复