C语言初学者编程规范对于程序员来说,能工作的代码并不等于"好"的代码。“好"代码的指标很多,包括易读、易维护易移植和可靠等。其中,可靠性对嵌入式系统非常重要,尤其是在那些对安全性要求很高的系统中,如飞行器、汽车和工业控制中。这些系统的特点是:只要工作稍有偏差,就有可能造成重大损失或者人员伤亡。一个不容易出错的系统,除了要有很好的硬件设计(如电磁兼容性),还要有很健壮或者说安全"的程序。然而,很少有程序员知道什么样的程序是安全的程序。很多程序只是表面上可以干活,还存在着大量的隐患。当然,这其中也有C语言自身的原因。因为C语言灵活的编程方式和语法规则,对于个新手来说很可能会成为机关重重的陷阱。同时,C语言的定义还并不完全,即使是国际通用的C语言标准,也还存在着很多未完全定义的地方。要求所有程序员都成为C语言专家,避开所有可能带来危险的编程方式,是不现实的。最好的方法是有一个针对安全性的C语言编程规范,告诉程序员该如何做。本规范在制定过程中,主要参考了业界比较推崇的《华为软件编程规范和范例》和《MISRA2004规则》,适合于非计算机专业的C语言初学者使用,目的在于在教学中培养良好的编程规范和意识素质,促进所设计程序安全、健壮、可靠、可读与可维护(程序简单、清晰)。考虑到面向的是初学者,为便于教学和课程考核操作,本规范中的要求比较基本。事实上,很多公司都有自己已规定的代码风格,包括命名规则、缩进规则等,参加工作后,应再进一步学习和应用公司的规范建议在学习本规范的同时,花点时间阅读本规范的参考文献原文,深刻理解编程规范与程序安全、健壮、可靠、可读、可维护间的关系和作用,在学习和工作中养成良好的编程风格。1排版1.1严格采用阶梯层次组织程序代码函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case语句下的情况处理语句也要遵从语句缩进要求。程序块的分界符(如C/C++语言的大括号和})应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。各层次缩进的风格采用TAB缩进(TAB宽度原则上使用系统默认值,TC使用8空格宽度,VC使用4空格宽度)。示例:if (x is true)1wedo y1else1if (a >b)(1.else17和:if (x == y)1
C语言初学者编程规范 对于程序员来说,能工作的代码并不等于"好"的代码。"好"代码的指标很多,包括易读、易维护、 易移植和可靠等。其中,可靠性对嵌入式系统非常重要,尤其是在那些对安全性要求很高的系统 中,如飞行器、汽车和工业控制中。这些系统的特点是:只要工作稍有偏差,就有可能造成重大损 失或者人员伤亡。一个不容易出错的系统,除了要有很好的硬件设计(如电磁兼容性),还要有很健壮 或者说"安全"的程序。 然而,很少有程序员知道什么样的程序是安全的程序。很多程序只是表面上可以干活,还存在着 大量的隐患。当然,这其中也有C语言自身的原因。因为C语言灵活的编程方式和语法规则,对于一 个新手来说很可能会成为机关重重的陷阱。同时,C语言的定义还并不完全,即使是国际通用的C语 言标准,也还存在着很多未完全定义的地方。要求所有程序员都成为C语言专家,避开所有可能带来 危险的编程方式,是不现实的。最好的方法是有一个针对安全性的C语言编程规范,告诉程序员该如 何做。 本规范在制定过程中,主要参考了业界比较推崇的《华为软件编程规范和范例》和《MISRA 2004 规则》,适合于非计算机专业的C语言初学者使用,目的在于在教学中培养良好的编程规范和意识、 素质,促进所设计程序安全、健壮、可靠、可读与可维护(程序简单、清晰)。考虑到面向的是初学 者,为便于教学和课程考核操作,本规范中的要求比较基本。事实上,很多公司都有自己规定的代 码风格,包括命名规则、缩进规则等,参加工作后,应再进一步学习和应用公司的规范。 建议在学习本规范的同时,花点时间阅读本规范的参考文献原文,深刻理解编程规范与程序安 全、健壮、可靠、可读、可维护间的关系和作用,在学习和工作中养成良好的编程风格。 1 排版 1.1 严格采用阶梯层次组织程序代码 函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风格,case 语句下的情 况处理语句也要遵从语句缩进要求。 程序块的分界符(如C/C++ 语言的大括号‘{’ 和‘}’)应各独占一行并且位于同一列,同时与引用它们的语 句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if 、for 、do 、while 、 switch 、case 语句中的程序都要采用如上的缩进方式。 各层次缩进的风格采用TAB缩进(TAB宽度原则上使用系统默认值,TC使用8空格宽度,VC使用4空格 宽度)。示例: if (x is true) { we do y } else { if (a > b) { . } else { . } } 和: if (x == y) {
1else if (x >y)1else(1注意,右括号所在的行不应当有其它东西,除非跟随着一个条件判断。也就是do-while语句中的"while",象这样do(body of do-loop1while (condition);说明:代码离不开缩进,缩进背后的思想是:清楚地定义一个控制块从哪里开始,到哪里结束。尤其是在你连续不断的町了20个小时的屏幕后,如果你有大尺寸的缩进。你将更容易发现缩进的好处。关于缩进主要有两个争论,一个是该用空格(Space)还是用制表符(Tab),另外一个是该用4格缩进还是8格缩进甚至都不是。建议总是使用Tab缩进,因为几乎所有的代码(不仅仅是C代码)都在使用Tab缩进。现在,有些人说8个字符大小的缩进导致代码太偏右了,并且在一个80字符宽的终端屏幕上看着很不舒服。对这个问题的回答是:如果你有超过3个级别的缩进,你就有点犯糊涂了,应当修改你的程序。简而言之,8个字符的缩进使程序更易读,而且当你把功能隐藏的太深时,多层次的缩进还会对此很直观的给出警告。要留心这种警告信息。例外:对于由开发工具自动生成的代码可以有不一致。1.2及时折行较长的语句(>80字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行要进行适当的缩进(至少1个TAB位置),使排版整齐,语句可读。示例:report_or_not_flag=(taskno<MAX_ACT_TASK_NUMBER)&&(n7statstat_item_valid (stat_item))&&(acttask_table[taskno].result_data !=0));循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作符处划分新行,操作符放在新行之首。示例:if (taskno<max_act_task number)&& (n7stat_stat item_valid (stat item)))(...//programcode7for (i= 0,j= O; (i< BufferKeyword[word index).word length)&&(j<NewKeyword.word_length);i++,j++){...l programcodefor (i= 0,j= 0(i<first_word_length)&&(j<second_word_length);i++, j++){../l programcode1
} else if (x > y) { . } else { . } 注意,右括号所在的行不应当有其它东西,除非跟随着一个条件判断。也就是do-while语句中 的"while",象这样: do { body of do-loop } while (condition); 说明:代码离不开缩进,缩进背后的思想是:清楚地定义一个控制块从哪里开始,到哪里结束。尤 其是在你连续不断的盯了20个小时的屏幕后,如果你有大尺寸的缩进。你将更容易发现缩进的好 处。 关于缩进主要有两个争论,一个是该用空格(Space)还是用制表符(Tab),另外一个是该用4格缩进还 是8格缩进甚至都不是。建议总是使用Tab缩进,因为几乎所有的代码(不仅仅是C代码)都在使用Tab 缩进。 现在,有些人说8个字符大小的缩进导致代码太偏右了,并且在一个80字符宽的终端屏幕上看着很不 舒服。对这个问题的回答是:如果你有超过3个级别的缩进,你就有点犯糊涂了,应当修改你的程 序。简而言之,8个字符的缩进使程序更易读,而且当你把功能隐藏的太深时,多层次的缩进还会对 此很直观的给出警告。要留心这种警告信息。 例外:对于由开发工具自动生成的代码可以有不一致。 1.2 及时折行 较长的语句(>80 字符)要分成多行书写,长表达式要在低优先级操作符处划分新行,操作符放在新行 之首,划分出的新行要进行适当的缩进(至少1个TAB位置),使排版整齐,语句可读。示例: report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER) && (n7stat_stat_item_valid (stat_item)) && (act_task_table[taskno].result_data != 0)); 循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分,长表达式要在低优先级操作 符处划分新行,操作符放在新行之首。示例: if ((taskno < max_act_task_number) && (n7stat_stat_item_valid (stat_item))) { . // program code } for (i = 0, j = 0; (i < BufferKeyword[word_index].word_length) && (j < NewKeyword.word_length); i++, j++) { . // program code } for (i = 0, j = 0; (i < first_word_length) && (j < second_word_length); i++, j++) { . // program code }
若函数或过程中的参数较长,则要进行适当的划分。示例n7stat_str_compare((BYTE*)&stat_object,(BYTE*)& (act_task_table[taskno].stat_object),sizeof(STAT_OBJECT));n7stat_flash_act_duration(stat_item,frame_id*STAT_TASK_CHECK_NUMBER+index,stat_object);1.3一行只写一条语句不允许把多个短语句写在一行中,即一行只写一条语句。示例,如下例子不符合规范rect.length = O; rect.width= O;应如下书写rect.length = O;rect.width=O;1.4if、for、do、while等语句格式规定if、for、do、while、case、switch、default等语句自占一行,且if、for、do、while等语句的执行语句部分无论多少都要加花括号。1.5空行(1)变量说明之后必须加空行。(2)相对独立的程序块之间应加空行。1.6空格在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应加空格。采用这种松散方式编写代码的目的是使代码更加清晰。由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为在CIC++语言中括号已经是最清晰的标志了在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留空格时不要连续留两个以上空格。(1)逗号、分号只在后面加空格。int a, b, C;(2)比较操作符,赋值操作符"="、"+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符<<"、"^"等双目操作符的前后加空格。if (current_time >=MAX_TIME_VALUE)La=b+c;a *= 2;a=b^2;(3)"!"、"~"、"++"、"-"、"&"(地址运算符)等单目操作符前后不加空格。*p=a';I内容操作"*"与内容之间flag=!isEmpty:l/非操作"!"与内容之间p=&mem;Ⅱ/地址操作"&"与内容之间ⅡI"++""-"与内容之间i++;(4)"->"、"."前后不加空格。p->id=pid;I/"->"指针前后不加空格(5)if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。if (a>=b &&c>d)
若函数或过程中的参数较长,则要进行适当的划分。示例: n7stat_str_compare((BYTE *) & stat_object, (BYTE *) & (act_task_table[taskno].stat_object), sizeof (_STAT_OBJECT)); n7stat_flash_act_duration( stat_item, frame_id *STAT_TASK_CHECK_NUMBER + index, stat_object ); 1.3 一行只写一条语句 不允许把多个短语句写在一行中,即一行只写一条语句。示例,如下例子不符合规范: rect.length = 0; rect.width = 0; 应如下书写 rect.length = 0; rect.width = 0; 1.4 if、for、do、while等语句格式规定 if 、for 、do 、while 、case 、switch 、default 等语句自占一行,且if 、for 、do 、while 等语句的 执行语句部分无论多少都要加花括号{}。 1.5 空行 (1)变量说明之后必须加空行。 (2)相对独立的程序块之间应加空行。 1.6 空格 在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后或者前后要加空 格;进行非对等操作时,如果是关系密切的立即操作符(如-> ),后不应加空格。采用这种松散方式 编写代码的目的是使代码更加清晰。 由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,如果语 句已足够清晰则括号内侧(即左括号后面和右括号前面)不需要加空格,多重括号间不必加空格,因为 在C/C++语言中括号已经是最清晰的标志了。 在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空格。给操作符留 空格时不要连续留两个以上空格。 (1)逗号、分号只在后面加空格。 int a, b, c; (2)比较操作符, 赋值操作符"="、 "+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位域操作符" <<"、"^"等双目操作符的前后加空格。 if (current_time >= MAX_TIME_VALUE) { a = b + c; } a *= 2; a = b ^ 2; (3)"!"、"~"、"++"、"-"、"&"(地址运算符)等单目操作符前后不加空格。 *p = 'a'; // 内容操作"*"与内容之间 flag = !isEmpty; // 非操作"!"与内容之间 p = &mem; // 地址操作"&" 与内容之间 i++; // "++","-"与内容之间 (4)"->"、"."前后不加空格。 p->id = pid; // "->"指针前后不加空格 (5) if、for、while、switch等与后面的括号间应加空格,使if等关键字更为突出、明显。 if (a >= b && c > d)
1.7对变量的定义,尽量位于函数的开始位置(1)应避免分散定义变量。(2)同一行内不要定义过多变量。(3)同一类的变量在同一行内定义,或者在相邻行定义。(4)数组、指针等复杂类型的定义放在定义区的最后。(5)变量定义区不做较复杂的变量赋值。1.8程序各部分的放置顺序在较小的项目中,按如下顺序组织安排程序各部分:(1)#include<C的标准头文件>。(2)#include”用户自定义的文件”。(3)#define宏定义。(4)全局变量定义。(5)函数原型声明。(6)main函数定义。(7)用户自定义函数。以上各部分之间、用户自定义的函数之间应加空行。注意,函数原型声明统一集中放在main函数之前,不放在某个函数内部。2注释2.1注释的原则和目的注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语言必须准确、易懂、简洁。通过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结构,使代码成为自注释的一一清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要的注释—一过量的注释则是有害的。注释的自的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防止没必要的重复注释信息。示例:如下注释意义不大。/*ifreceiveflagisTRUE*/if (receive_flag)而如下的注释则给出了额外有用的信息。1* if mtp receive a message from links */if(receive_flag)2.2函数头部应进行注释函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系(函数表)等。示例1:下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。/**Function:函数名称Description:Il函数功能、性能等的描述Calls:Ⅱ被本函数调用的函数清单Called By:Ⅱ调用本函数的函数清单Input:I输入参数说明,包括每个参数的作II用、取值说明及参数间关系。Output:I对输出参数的说明。Return:/函数返回值的说明Others:Ⅱ其它说明
1.7 对变量的定义,尽量位于函数的开始位置 (1)应避免分散定义变量。 (2)同一行内不要定义过多变量。 (3)同一类的变量在同一行内定义,或者在相邻行定义。 (4)数组、指针等复杂类型的定义放在定义区的最后。 (5)变量定义区不做较复杂的变量赋值。 1.8 程序各部分的放置顺序 在较小的项目中,按如下顺序组织安排程序各部分: (1)#include <C的标准头文件>。 (2)#include 〞用户自定义的文件〞。 (3)#define 宏定义。 (4)全局变量定义。 (5)函数原型声明。 (6)main函数定义。 (7)用户自定义函数。 以上各部分之间、用户自定义的函数之间应加空行。注意,函数原型声明统一集中放在main函数之 前,不放在某个函数内部。 2 注释 2.1 注释的原则和目的 注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也不能太少,注释语 言必须准确、易懂、简洁。通过对函数或过程、变量、结构等正确的命名以及合理地组织代码的结 构,使代码成为自注释的——清晰准确的函数、变量等的命名,可增加代码可读性,并减少不必要 的注释——过量的注释则是有害的。 注释的目的是解释代码的目的、功能和采用的方法,提供代码以外的信息,帮助读者理解代码,防 止没必要的重复注释信息。 示例:如下注释意义不大。 /* if receive_flag is TRUE */ if (receive_flag) 而如下的注释则给出了额外有用的信息。 /* if mtp receive a message from links */ if (receive_flag) 2.2 函数头部应进行注释 函数头部应进行注释,列出:函数的目的/ 功能、输入参数、输出参数、返回值、调用关系(函数、 表)等。 示例1:下面这段函数的注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。 /************************************************* Function: // 函数名称 Description: // 函数功能、性能等的描述 Calls: // 被本函数调用的函数清单 Called By: // 调用本函数的函数清单 Input: // 输入参数说明,包括每个参数的作 // 用、取值说明及参数间关系。 Output: // 对输出参数的说明。 Return: // 函数返回值的说明 Others: // 其它说明
********★********对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位)对既是入口又是出口的变量应该在入口和出口处同时标明。等等。在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。2.3进行注释时的注意事项(1)建议边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的注释要删除(2)注释的内容要清楚、明了,含义准确,防止注释二义性。说明:错误的注释不但无益反而有害。3)避免在注释中使用缩写,特别是非常用缩写。在使用缩写时或之前,应对缩写进行必要的说明(4)注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不可放在下面。除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。示例:如下例子不符合规范。例1:/*getreplicatesubsystemindexandnetindicator*/repssn_ind=ssn_data[index].repssn_indexrepssn_ni=ssn_data[index].ni;例2:repssn_ind=ssn_data[index].repssn_index;repssn_ni=ssn_data[index].ni;* get replicate sub system index and net indicator *应如下书写I*get replicate sub system index and net indicator */repssn_ind=ssn_data[index].repssn_index;repssnni=ssndata[index].ni;5)对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须以注释说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。示例:/*activestatistictasknumber*/#defineMAXACTTASKNUMBER1000#defineMAX_ACT_TASKNUMBER1000/*activestatistictasknumber*(6)数据结构声明(包括数组、结构、类、枚举等),如果其命名不是充分自注释的,必须加以注释。对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右方。示例:可按如下形式说明枚举/数据/联合结构。* sccp interface with sccp user primitivemessage name*/enumSCCP_USER_PRIMITIVE1N_UNITDATA_IND, /* sccp notify sccp user unit data come */N_NOTICE_IND,/* sccpnotify usertheNo.7network can not*//*transmissionthismessage*N_UNITDATA_REQ,/*sccpuser'sunitdatatransmissionrequest*/3;(7)全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意事项等的说明
*************************************************/ 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口 参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说 明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标 明。等等。 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险 方面,可能出错的地方。 2.3 进行注释时的注意事项 (1)建议边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。不再有用的 注释要删除。 (2)注释的内容要清楚、明了,含义准确,防止注释二义性。说明:错误的注释不但无益反而有害。 (3)避免在注释中使用缩写,特别是非常用缩写。在使用缩写时或之前,应对缩写进行必要的说明。 (4)注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注释)相邻位置,不 可放在下面。除非必要,不应在代码或表达中间插入注释,否则容易使代码可理解性变差。 示例:如下例子不符合规范。 例1: /* get replicate sub system index and net indicator */ repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni; 例2: repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni; /* get replicate sub system index and net indicator */ 应如下书写 /* get replicate sub system index and net indicator */ repssn_ind = ssn_data[index].repssn_index; repssn_ni = ssn_data[index].ni; (5)对于所有有物理含义的变量、常量,如果其命名不是充分自注释的,在声明时都必须加以注释, 说明其物理含义。变量、常量、宏的注释应放在其上方相邻位置或右方。 示例: /* active statistic task number */ #define MAX_ACT_TASK_NUMBER 1000 #define MAX_ACT_TASK_NUMBER 1000 /* active statistic task number */ (6)数据结构声明( 包括数组、结构、类、枚举等) ,如果其命名不是充分自注释的,必须加以注释。 对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域的注释放在此域的右 方。 示例:可按如下形式说明枚举/数据/联合结构。 /* sccp interface with sccp user primitive message name */ enum SCCP_USER_PRIMITIVE { N_UNITDATA_IND, /* sccp notify sccp user unit data come */ N_NOTICE_IND, /* sccp notify user the No.7 network can not */ /* transmission this message */ N_UNITDATA_REQ, /* sccp user's unit data transmission request*/ }; (7)全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取它以及存取时注意 事项等的说明