实用的C语言编程规范.docx
《实用的C语言编程规范.docx》由会员分享,可在线阅读,更多相关《实用的C语言编程规范.docx(20页珍藏版)》请在冰豆网上搜索。
![实用的C语言编程规范.docx](https://file1.bdocx.com/fileroot1/2023-4/28/bce38261-7a61-4c9e-9447-2ca65b6ceeab/bce38261-7a61-4c9e-9447-2ca65b6ceeab1.gif)
实用的C语言编程规范
实用的C语言编程规范
简介:
在项目团队协作开发的情况下,编程时应该强调的一个重要方面是程序的易读性,在保证软件速度等性能指标能满足用户需求的情况下,能让其他程序员容易读懂你所编写的程序。
若项目小组的所有开发人员都遵循统一的、鲜明的一套编程风格,可以让协作者、后继者和自己一目了然,在很短的时间内看清楚程序结构,理解设计的思路,大大提高代码的可读性、可重用性、程序健壮性、可移植性、可维护性,对彼此交流和协同开发将起到事半功倍的作用。
制定本编程规范的目的是为了提高软件开发效率及所开发软件的可维护性,提高软件的质量。
本规范由程序风格、命名规范、注释规范、可移植性以及软件的模块化规范等部分组成。
用简单的方法去做复杂的事!
!
!
1代码编写总体原则
1.1清晰第一
清晰性是易于维护、易于重构的程序必须具备的特征。
代码首先是给人读的,好的代码应该像好的文章一样发声朗读出来。
目前软件维护期成本占整个软件生命周期成本的40%-90%。
根据业界经验,维护期变更代码的成本,小型系统是开发期的5倍,大型系统(100万行代码以上)可以达到100倍。
业界的调查指出,开发组平均大约一半的人力用于弥补过去的错误,而不是添加新的功能来帮助公司提高竞争力。
一般情况下,代码的可阅读性高于性能,只有确定性能是瓶颈时,才应该主动优化。
“程序必须为阅读它的人而编写,只是顺便用于机器执行。
”
――HaroldAbelson和GeraldJay
“编写程序应该以人为本,计算机第二。
”
――SteveMcConnell
1.2简洁为美
简洁就是易于理解并且易于实现。
代码越长越难于看懂,也越容易在修改时引入错误,写的代码越多,意味着出错的地方越多,也就意味着代码的可靠性越低。
因此,我们提倡大家通过编写简洁明了的代码来提升代码可靠性。
废弃的代码(没有被调用的函数和全局变量)要及时清除,重复代码应该尽可能提炼成函数。
1.3选择合适的风格,与代码原有风格保持一致
产品所有人共同分享同一种风格所带来的好处,远远超出为了统一而付出的代价。
在公司已有编码规范的指导下,审慎地编排代码以使代码尽可能清晰,是一项非常重要的技能。
2文件结构
每个C程序通常分为两个文件。
一个文件用于保存程序的声明(declaration),称为头文件。
另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。
C程序的头文件以“.h”为后缀,C程序的定义文件以“.c”为后缀。
2.1文件信息说明
文件信息声明位于头文件和定义文件的开头(参见示例1),主要内容有:
(1)公司名称;
(2)文件名称;
(3)版权信息;
(4)当前版本,作者/修改者,完成日期;
(5)主要函数描述;
(6)注意事项;
示例1
2.2头文件的结构
头文件由三部分内容组成:
(1)头文件开头处的文件信息说明(参见示例1);
(2)预处理块;
(3)函数和类结构声明等。
原则2.2.1为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块;单词间以下划线“_”连接,例如有头文件名称为“”,则定义如下:
“#ifndef_”。
原则2.2.2用#include<>格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
原则2.2.3用#include“”格式来引用非标准库的头文件(编译器将从用户的工作目录开始搜索)。
原则2.2.4头文件中只存放“声明”而不存放“定义”。
原则2.2.5头文件中应包含所有定义文件所定义的函数声明,如果一个头文件对应多个定义文件,则不同定义文件内实现的函数要分开声明,并作注释以解释所声明的函数从属于那一个定义文件。
原则2.2.6.c/.h文件禁止包含用不到的头文件。
很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。
2.3函数编写规则
函数设计的精髓:
编写整洁函数,同时把代码有效组织起来。
整洁函数要求:
代码简单直接、不隐藏设计者的意图、用干净利落的抽象和直截了当的控制语句将函数有机组织起来。
原则2.3.1一个函数仅完成一件功能。
说明:
一个函数实现多个功能给开发、使用、维护都带来很大的困难。
将没有关联或者关联很弱的语句放到同一函数中,会导致函数职责不明确,代码混乱,难以理解,难以测试和改动。
延伸阅读材料:
《敏捷软件开发:
原则、模式与实践》第八章,单一职责原则(SRP)。
原则2.3.2重复代码应该尽可能提炼成函数。
说明:
重复代码提炼成函数可以带来维护成本的降低。
项目组应当使用代码重复度检查工具,在持续集成环境中持续检查代码重复度指标变化趋势,并对新增重复代码及时重构。
当一段代码重复两次时,即应考虑消除重复,当代码重复超过三次时,应当立刻着手消除重复。
原则2.3.3避免函数过长,新增函数不超过50行(非空非注释行)。
说明:
本规则仅对新增函数做要求,对已有函数修改时,建议不增加代码行。
过长的函数往往意味着函数功能不单一,过于复杂
函数的有效代码行数,即非空非注释行应当在[1,50]区间。
原则2.3.4避免函数的代码块嵌套过深,新增函数的代码块嵌套不超过4层。
说明:
本规则仅对新增函数做要求,对已有的代码建议不增加嵌套层次。
函数的代码块嵌套深度指的是函数中的代码控制块(例如:
if、for、while、switch等)之间互相包含的深度。
每级嵌套都会增加阅读代码时的脑力消耗,因为需要在脑子里维护一个“栈”(比如,进入条件语句、进入循环„„)。
应该做进一步的功能分解,从而避免使代码的阅读者一次记住太多的上下文。
原则2.3.5废弃代码(没有被调用的函数和变量)要及时清除。
说明:
程序中的废弃代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。
原则2.3.6函数不变参数使用const。
说明:
不变的值更易于理解/跟踪和分析,把const作为默认选项,在编译时会对其进行检查,使代码更牢固/更安全。
原则2.3.7函数的参数个数不超过5个。
说明:
函数的参数过多,会使得该函数易于受外部(其他部分的代码)变化的影响,从而影响维护工作。
函数的参数过多同时也会增大测试的工作量。
函数的参数个数不要超过5个,如果超过了建议拆分为不同函数。
原则2.3.8在源文件范围内声明和定义的所有函数,除非外部可见,否则应该增加static关键字。
说明:
如果一个函数只是在同一文件中的其他地方调用,那么就用static声明。
使用static确保只是在声明它的文件中是可见的,并且避免了和其他文件或库中的相同标识符发生混淆的可能性。
3标示符的命名规则
目前比较使用的如下几种命名风格:
unixlike风格:
单词用小写字母,每个单词直接用下划线„_‟分割,例如text_mutex,kernel_text_address。
Windows风格:
大小写字母混用,单词连在一起,每个单词首字母大写。
不过Windows风格如果遇到大写专有用语时会有些别扭,例如命名一个读取RFC文本的函数,命令为ReadRFCText,看起来就没有unixlike的read_rfc_text清晰了。
匈牙利命名法:
是计算机程序设计中的一种命名规则,用这种方法命名的变量显示了其数据类型。
匈牙利命名主要包括三个部分:
基本类型、一个或更多的前缀、一个限定词。
实际上,各种风格都有其优势也有其劣势,而且往往和个人的审美观有关。
我们对标识符定义主要是为了让团队的代码看起来尽可能统一,有利于代码的后续阅读和修改。
原则3.1标识符的命名要清晰、明了,有明确含义,同时使用完整的单词或大家基本可以理解的缩写,避免使人产生误解。
说明:
尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要。
示例:
好的命名:
即使不注释也能理解。
不好的命名:
使用模糊的缩写或随意的字符。
原则3.2除了常见的通用缩写以外,不使用单词缩写,不得使用汉语拼音。
说明:
较短的单词可通过去掉“元音”形成缩写,较长的单词可取单词的头几个字母形成缩写,一些单词有大家公认的缩写,常用单词的缩写必须统一。
协议中的单词的缩写与协议保持一致。
对于某个系统使用的专用缩写应该在注视或者某处做统一说明。
示例:
一些常见可以缩写的例子:
argument可缩写为arg
buffer可缩写为buff
clock可缩写为clk
command可缩写为cmd
compare可缩写为cmp
configuration可缩写为cfg
device可缩写为dev
error可缩写为err
hexadecimal可缩写为hex
increment可缩写为inc、
initialize可缩写为init
maximum可缩写为max
message可缩写为msg
minimum可缩写为min
parameter可缩写为para
previous可缩写为prev
register可缩写为reg
semaphore可缩写为sem
statistic可缩写为stat
synchronize可缩写为sync
temp可缩写为tmp
原则3.3产品/项目组内部应保持统一的命名风格。
说明:
Unixlike和windowslike风格均有其拥趸,产品应根据自己的部署平台,选择其中一种,并在产品内部保持一致。
原则3.4尽量避免名字中出现数字编号,除非逻辑上的确需要编号。
示例:
如下命名,使人产生疑惑。
应改为有意义的单词命名
原则3.5重构/修改部分代码时,应保持和原有代码的命名风格一致。
说明:
根据源代码现有的风格继续编写代码,有利于保持总体一致。
4文件命名规则
建议3.6文件命名统一采用小写字符。
说明:
因为不同系统对文件名大小写处理会不同(如MS的DOS、Windows系统不区分大小写,但是Linux系统则区分),所以代码文件命名建议统一采用全小写字母命名。
5变量命名规则
规则5.1全局变量应增加“g_”前缀。
规则5.2静态变量应增加“s_”前缀。
说明:
增加g_前缀或者s_前缀,原因如下:
首先,全局变量十分危险,通过前缀使得全局变量更加醒目,促使开发人员对这些变量的使用更加小心。
其次,从根本上说,应当尽量不使用全局变量,增加g_和s_前缀,会使得全局变量的名字显得很丑陋,从而促使开发人员尽量少使用全局变量。
规则5.3禁止使用单字节命名变量,但允许定义i、j、k作为局部循环变量。
6函数命名规则
规则6.1函数命名应以函数要执行的动作命名,一般采用动词或者动词+名词的结构。
示例:
找到当前进程的当前目录
7宏命名规则
规则7.1对于数值或者字符串等等常量的定义,建议采用全大写字母,单词之间加下划线„_‟的方式命名(枚举同样建议使用此方式定义)。
示例:
规则7.2除了头文件或编译开关等特殊标识定义,宏定义不能使用下划线„_‟开头和结尾。
说明:
一般来说,“_”开头、结尾的宏都是一些内部的定义,
8变量
原则8.1一个变量只有一个功能,不能把一个变量用作多种用途。
说明:
一个变量只用来表示一个特定功能,不能把一个变量作多种用途,即同一变量取值不同时,其代表的意义也不同。
原则8.2不用或者少用全局变量。
说明:
单个文件内部可以使用static的全局变量,可以将其理解为类的私有成员变量。
全局变量应该是模块的私有数据,不能作用对外的接口使用,使用static类型定义,可以有效防止外部文件的非正常访问,建议定义一个STATIC宏,在调试阶段,将STATIC定义为static,版本发布时,改为空,以便于后续的打补丁等操作。
直接使用其他模块的私有数据,将使模块间的关系逐渐走向“剪不断理还乱”的耦合状态,这种情形是不允许的。
原则8.3防止局部变量与全局变量同名。
说明:
尽管局部变量和全局变量的作用域不同而不会发生语法错误,但容易使人误解。
原则8.4通讯过程中使用的结构,必须注意字节序。
说明:
通讯报文中,字节序是一个重要的问题,我司设备使用的cpu类型复杂多样,大小端、32位/64位的处理器也都有,如果结构会在报文交互过程中使用,必须考虑字节序问题。
由于位域在不同字节序下,表现看起来差别更大,所以更需要注意。
对于这种跨平台的交互,数据成员发送前,都应该进行主机序到网络序的转换;接收时,也必须进行网络序到主机序的转换。
原则8.5严禁使用未经初始化的变量作为右值。
说明:
坚持原则8.6(在首次使用前初始化变量,初始化的地方离使用的地方越近越好。
)可以有效避免未初始化错误。
原则8.6在首次使用前初始化变量,初始化的地方离使用的地方越近越好。
说明:
未初始化变量是C和C++程序中错误的常见来源。
在变量首次使用前确保正确初始化。
在较好的方案中,变量的定义和初始化要做到亲密无间。
示例:
原则8.7明确全局变量的初始化顺序,避免跨模块的初始化依赖。
说明:
系统启动阶段,使用全局变量前,要考虑到该全局变量在什么时候初始化,使用全局变量和初始化全局变量,两者之间的时序关系,谁先谁后,一定要分析清楚,不然后果往往是低级而又灾难性的。
原则8.8尽量减少没有必要的数据类型默认转换与强制转换。
说明:
当进行数据类型强制转换时,其数据的意义、转换后的取值等都有可能发生变化,而这些细节若考虑不周,就很有可能留下隐患。
示例:
如下赋值,多数编译器不产生告警,但值的含义还是稍有变化。
9注释
原则9.1优秀的代码可以自我解释,不通过注释即可轻易读懂。
说明:
优秀的代码不写注释也可轻易读懂,注释无法把糟糕的代码变好,需要很多注释来解释的代码往往存在坏味道,需要重构。
示例:
注释不能消除代码的坏味道:
重构代码后不需要注释
原则9.2注释的内容要清楚、明了,含义准确,防止注释二义性。
说明:
有歧义的注释反而会导致维护者更难看懂代码,正如带两块表反而不知道准确时间。
示例:
注释与代码相矛盾,注释内容也不清楚,前后矛盾。
正确做法:
修改注释描述如下:
原则9.3在代码的功能、意图层次上进行注释,即注释解释代码难以直接表达的意图,而不是重复描述代码。
说明:
注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。
对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。
注释不是为了名词解释(what),而是说明用途(why)。
示例:
如下注释纯属多余。
原则则9.4修改代码时,维护代码周边的所有注释,以保证注释与代码的一致性。
不再有用的注释要删除。
说明:
不要将无用的代码留在注释中,随时可以从源代码配置库中找回代码;即使只是想暂时排除代码,也要留个标注,不然可能会忘记处理它。
原则9.5函数声明处注释描述函数功能、性能及用法,包括输入和输出参数、函数返回值、可重入的要求等;定义处详细描述函数功能和实现要点,如实现的简要步骤、实现的理由、设计约束等。
说明:
重要的、复杂的函数,提供外部使用的接口函数应编写详细的注释。
原则9.6全局变量要有较详细的注释,包括对其功能、取值范围以及存取时注意事项等的说明。
示例:
原则9.7注释应放在其代码上方相邻位置或右方,不可放在下面。
如放于上方则需与其上面的代码用空行隔开,且与下方代码缩进相同。
示例
原则9.8同一产品或项目组统一注释风格。
10排版与格式
规则10.1程序块采用缩进风格编写,每级缩进为4个空格。
说明:
当前各种编辑器/IDE都支持TAB键自动转空格输入,需要打开相关功能并设置相关功能。
编辑器/IDE如果有显示TAB的功能也应该打开,方便及时纠正输入错误。
IDE向导生成的代码可以不用修改。
宏定义、编译开关、条件预处理语句可以顶格。
规则10.2相对独立的程序块之间、变量说明之后必须加空行。
示例:
如下例子不符合规范。
应如下书写
规则10.3一条语句不能过长,如不能拆分需要分行写。
一行到底多少字符换行比较合适,产品可以自行确定。
说明:
对于目前大多数的PC来说,132比较合适(80/132是VTY常见的行宽值);对于新PC宽屏显示器较多的产品来说,可以设置更大的值。
换行时有如下建议:
(1)换行时要增加一级缩进,使代码可读性更好;
(2)低优先级操作符处划分新行;换行时操作符应该也放下来,放在新行首;
(3)换行时建议一个完整的语句放在一行,不要根据字符数断行。
示例:
规则10.4在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。
说明:
采用这种松散方式编写代码的目的是使代码更加清晰。
在已经非常清晰的语句中没有必要再留空格,如括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在C语言中括号已经是最清晰的标志了。
在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。
给操作符留空
格时不要连续留两个以上空格。
示例:
1)逗号、分号只在后面加空格
2)比较操作符,赋值操作符"="、"+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符"<<"、"^"等双目操作符的前后加空格。
3)"!
"、"~"、"++"、"--"、"&"(地址操作符)等单目操作符前后不加空格。
4)"->"、"."前后不加空格。
5)if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。
规则10.5注释符(包括„/*‟„//‟„*/‟)与注释内容之间要用一个空格进行分隔。
说明:
这样可以使注释的内容部分更清晰。
现在很多工具都可以批量生成、删除'//'注释,这样有空格也比较方便统一处理。
规则10.6源程序中关系较为紧密的代码应尽可能相邻。
11.对齐
原则11.1程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐;
原则11.2{}之内的代码块在‘{’右边数格处左对齐;
原则11.3代码的的对齐采用TAB键而不采用空格键对齐,一般TAB键设置为向后空4个空格。
示例
好的代码风格
不好的代码风格
12参数设计规则
原则12.1参数的书写要完整,不要贪图省事只写参数的类型而省略参数名字,如果函数没有参数,则用void填充。
示例
原则12.2参数命名要恰当,顺序要合理;
例如编写字符串拷贝函数StringCopy,它有两个参数,如果把参数名字起为str1和str2,例如:
voidStringCopy(char*str1,char*str2);那么我们很难搞清楚究竟是把str1拷贝到str2中,还是刚好倒过来,可以把参数名字起得更有意义,如叫strSource和strDestination。
这样从名字上就可以看出应该把strSource拷贝到strDestination。
还有一个问题,这两个参数那一个该在前那一个该在后?
参数的顺序要遵循程序员的习惯。
一般地,应将目的参数放在前面,源参数放在后面。
如果将函数声明为:
别人在使用时可能会不假思索地写成如下形式:
原则12.3如果参数是指针,且仅作输入用,则应在类型前加const,以防止该指针在函数体内被意外修改。
示例:
原则12.4如果输入参数以值传递的方式传递对象,则宜改用“const&”方式来传递,这样可以省去临时对象的构造和析构过程,从而提高效率;
原则12.5避免函数有太多的参数,参数个数尽量控制在5个以内。
如果参数太多,在使用时容易将参数类型或顺序搞错。
原则12.6尽量不要使用类型和数目不确定的参数;
C标准库函数printf是采用不确定参数的典型代表,其原型为:
这种风格的函数在编译时丧失了严格的类型安全检查。
13返回值的规则
原则13.1不要省略返回值的类型。
C语言中,凡不加类型说明的函数,一律自动按整型处理,这样做不会有什么好处,却容易被误解为void类型;C++语言有很严格的类型安全检查,不允许上述情况发生。
由于C++程序可以调用C函数,为了避免混乱,规定任何C++/C函数都必须有类型。
如果函数没有返回值,那么应声明为void类型。
示例
原则13.2函数名字与返回值类型在语义上不可冲突;违反这条规则的典型代表是C标准库函数getchar。
示例
按照getchar名字的意思,将变量c声明为char类型是很自然的事情。
但不幸的是getchar的确不是char类型,而是int类型,其原型如下:
由于c是char类型,取值范围是[-128,127],如果宏EOF的值在char的取值范围之外,那么if语句将总是失败,这种“危险”人们一般哪里料得到!
导致本例错误的责任并不在用户,是函数getchar误导了使用者。
原则13.3不要将正常值和错误标志混在一起返回。
正常值用输出参数获得,而错误标志用return语句返回。
原则13.4有时候函数原本不需要返回值,但为了增加灵活性如支持链式表达,可以附加返回值;
例如字符串拷贝函数strcpy的原型:
strcpy函数将strSrc拷贝至输出参数strDest中,同时函数的返回值又是strDest。
这样做并非多此一举,可以获得如下灵活性: