c51编程规范.docx
《c51编程规范.docx》由会员分享,可在线阅读,更多相关《c51编程规范.docx(28页珍藏版)》请在冰点文库上搜索。
c51编程规范
c51编程规范
(1)
引言
今天人们越来越明白软件设计更多地是一种工程,而不是一种个人艺术。
由于大型产品的开
发通常由很多的人协同作战,如果不统一编程规范,最终合到一起的程序,其可读性将较
差,这不仅给代码的理解带来障碍,增加维护阶段的工作量,同时不规范的代码隐含错误的
可能性也比较大。
BELL实验室的研究资料表明,软件错误中18%左右产生于概要设计阶段,15%左右产生于详细
设计阶段,而编码阶段产生的错误占的比例则接近50%;分析表明,编码阶段产生的错误当
中,语法错误大概占20%左右,而由于未严格检查软件逻辑导致的错误、函数(模块)之间
接口错误及由于代码可理解度低导致优化维护阶段对代码的错误修改引起的错误则占了一半
以上。
可见,提高软件质量必须降低编码阶段的错误率。
如何有效降低编码阶段的错误呢?
BELL实
验室的研究人员制定了详细的软件编程规范,并培训每一位程序员,最终的结果把编码阶段
的错误降至10%左右,同时也降低了程序的测试费用,效果相当显著。
本文从代码的可维护性(可读、可理解性、可修改性)、代码逻辑与效率、函数(模块)接
口、可测试性四个方面阐述了软件编程规范,规范分成规则和建议两种,其中规则部分为强
制执行项目,而建议部分则不作强制,可根据习惯取舍。
2.编码规范
2.1.排版风格
<规则1>程序块采用缩进风格编写,缩进为4个空格位。
排版不混合使用空格和TAB键。
<规则2>在两个以上的关键字、变量、常量进行对等操作时,它们之间的操作符之前、之后
或者前后要加空格;进行非对等操作时,如果是关系密切的立即操作符(如->),后不应
加空格。
采用这种松散方式编写代码的目的是使代码更加清晰。
例如:
(1)逗号、分号只在后面加空格
printf("%d%d%d",a,b,c);
(2)比较操作符,赋值操作符"="、"+=",算术操作符"+"、"%",逻辑操作符"&&"、"&",位
域操作符"<<"、"^"等双目操作符的前后加空格
if(lCurrentTime>=MAX_TIME_VALUE)
a=b+c;
a*=2;
a=b^2;
(3)"!
"、"~"、"++"、"--"、"&"(地址运算符)等单目操作符前后不加空格
*pApple='a';//内容操作"*"与内容之间
flag=!
bIsEmpty;//非操作"!
"与内容之间
p=&cMem;//地址操作"&"与内容之间
i++;//"++","--"与内容之间
(4)"->"、"."前后不加空格
p->id=pId;//"->"指针前后不加空格
由于留空格所产生的清晰性是相对的,所以,在已经非常清晰的语句中没有必要再留空格,
如最内层的括号内侧(即左括号后面和右括号前面)不要加空格,因为在C/C++语言中括号已
经是最清晰的标志了。
另外,在长语句中,如果需要加的空格非常多,那么应该保持整体清晰,而在局部不加空
格。
最后,即使留空格,也不要连续留两个以上空格(为了保证缩进和排比留空除外)。
<规则3>函数体的开始,类的定义,结构的定义,if、for、do、while、switch及case语句
中的程序都应采用缩进方式,憑捄蛻}捰禀独占一行并且位于同一列,同时与引用它们的语
句左对齐
例如下例不符合规范。
for(...){
...//程序代码
c51编程规范
(2)
2.2.可理解性
2.2.1.注释
注释的原则是有助于对程序的阅读理解,注释不宜太多也不能太少,太少不利于代码理解,
太多则会对阅读产生干扰,因此只在必要的地方才加注释,而且注释要准确、易懂、尽可能
简洁。
注释量一般控制在30%到50%之间。
<规则1>程序在必要的地方必须有注释,注释要准确、易懂、简洁。
例如如下注释意义不大。
/*如果bReceiveFlag为TRUE*/
if(bReceiveFlag==TRUE)
而如下的注释则给出了额外有用的信息。
/*如果mtp从连接处获得一个消息*/
if(bReceiveFlag==TURE)
<规则2>注释应与其描述的代码相近,对代码的注释应放在其上方或右方(对单条语句的注
释)相邻位置,不可放在下面,如放于上方则需与其上面的代码用空行隔开。
示例:
如下例子不符合规范。
例子1
/*获得系统指针和网络指针的副本*/
nRepssnInd=SsnData[index].nRepssnIndex;
nRepssnNi=SsnData[index].ni;
例子2
nRepssnInd=SsnData[index].nRepssnIndex;
nRepssnNi=SsnData[index].ni;
/*获得系统指针和网络指针的副本*/
应如下书写
/*获得系统指针和网络指针的副本*/
nRepssnInd=SsnData[index].nRepssnIndex;
nRepssnNi=SsnData[index].ni;
<规则3>对于所有的常量,变量,数据结构声明(包括数组、结构、类、枚举等),如果其命
名不是充分自注释的,在声明时都必须加以注释,说明其含义。
示例:
/*活动任务的数量*/
#defineMAX_ACT_TASK_NUMBER1000
#defineMAX_ACT_TASK_NUMBER1000/*活动任务的数量*/
/*带原始用户信息的SCCP接口*/
enumSCCP_USER_PRIMITIVE
{
N_UNITDATA_IND,/*向SCCP用户报告单元数据已经到达*/
N_UNITDATA_REQ,/*SCCP用户的单元数据发送请求*/
};
<规则4>头文件、源文件的头部,应进行注释。
注释必须列出:
文件名、作者、目的、功
能、修改日志等。
例如:
/*********************************************
文件名:
编写者:
编写日期:
简要描述:
修改记录:
********************************************/
说明:
摷蛞枋鰯一项描述本文件的目的和功能等。
撔薷募锹紨是修改日志列表,每条修改
记录应包括修改日期、修改者及修改内容简述。
<规则5>函数头部应进行注释,列出:
函数的目的、功能、输入参数、输出参数、修改日志
等。
形式如下:
/*************************************************
函数名称:
简要描述:
//函数目的、功能等的描述
输入:
//输入参数说明,包括每个参数的作用、取值说明及参数间关
系,
输出:
//输出参数的说明,返回值的说明
修改日志:
*************************************************/
对一些复杂的函数,在注释中最好提供典型用法。
<规则3>宏和常量的命名
宏和常量的命名规则:
单词的字母全部大写,各单词之间用下划线隔开。
命名举例:
#defineMAX_SLOT_NUM8
#defineEI_ENCR_INFO0x07
constintMAX_ARRAY
<规则4>结构和结构成员的命名
结构名各单词的字母均为大写,单词间用下划线连接。
可用或不用typedef,但是要保持一
致,不能有的结构用typedef,有的又不用。
如:
typedefstructLOCAL_SPC_TABLE_STRU
{
charcValid;
intnSpcCode[MAX_NET_NUM];
}LOCAL_SPC_TABLE;
结构成员的命名同变量的命名规则。
<规则5>枚举和枚举成员的命名
枚举名各单词的字母均为大写,单词间用下划线隔开。
枚举成员的命名规则:
单词的字母全部大写,各单词之间用下划线隔开;要求各成员的第一
个单词相同。
命名举例:
typdefenum
{
LAPD_MDL_ASSIGN_REQ,
LAPD_MDL_ASSIGN_IND,
LAPD_DL_DATA_REQ,
LAPD_DL_DATA_IND,
LAPD_DL_UNIT_DATA_REQ,
LAPD_DL_UNIT_DATA_IND,
}LAPD_PRMV_TYPE;
<规则6>类的命名
前缀意义举例
C类CMyClass
COCOM类COMMyObjectClass
CFCOMclassfactoryCFMyClassFactory
ICOMinterfaceclassIMyInterface
CImplCOMimplementationclassCImplMyInterface
<规则7>函数的命名
单词首字母为大写,其余均为小写,单词之间不用下划线。
函数名应以一个动词开头,即函
数名应类似摱鼋峁箶。
命名举例:
voidPerformSelfTest(void);
voidProcChanAct(MSG_CHAN_ACTIV*pMsg,UCMsgLen);
2.3.可维护性
<规则1>在逻辑表达式中使用明确的逻辑判断。
示例:
如下逻辑表达式不规范。
1)if(strlen(strName))
2)for(index=MAX_SSN_NUMBER;index;index--)
3)while(p&&*p)//假设p为字符指针
应改为如下:
1)if(strlen(strName)!
=0)
2)for(index=MAX_SSN_NUMBER;index!
=0;index--)
3)while((p!
=NULL)&&(*p!
='\0'))
<规则2>预编译条件不应分离一完整的语句。
不正确:
if((cond==GLRUN)
#ifdefDEBUG
||(cond==GLWAIT)
#endif
)
{
}
正确:
#ifdefDEBUG
if(cond==GLRUN||cond==GLWAIT)
#else
if(cond==GLRUN)
#endif
{
}
<规则3>在宏定义中合并预编译条件。
不正确:
#ifdefEXPORT
for(i=0;i#else
for(i=0;i#endif
正确:
头文件中:
#ifdefEXPORT
#defineMAX_MS_RSMMAX_MSXRSM
#else
#defineMAX_MS_RSMMAX_MSRSM
#endif
源文件中:
for(i=0;i<规则4>使用宏定义表达式时,要使用完备的括号。
如下的宏定义表达式都存在一定的隐患。
#defineREC_AREA(a,b)a*b
#defineREC_AREA(a,b)(a*b)
#defineREC_AREA(a,b)(a)*(b)
正确的定义为:
#defineREC_AREA(a,b)((a)*(b))
<规则5>宏所定义的多条表达式应放在大括号内。
示例:
下面的语句只有宏中的第一条表达式被执行。
为了说明问题,for语句的书写稍不符
规范。
#defineINIT_RECT_VALUE(a,b)a=0;b=0;
for(index=0;indexINIT_RECT_VALUE(rect.a,rect.b);
正确的用法应为:
#defineINIT_RECT_VALUE(a,b){a=
0;b=0;}
for(index=0;index{
INIT_RECT_VALUE(rect[index].a,rect[index].b);
}
<规则6>宏定义不能隐藏重要的细节,避免有return,break等导致程序流程转向的语句。
如下例子是不规范的应用,其中隐藏了程序的执行流程。
#defineFOR_ALLfor(i=0;i/*数组c置0*/
FOR_ALL
{
c[i]=0;
}
#defineCLOSE_FILE{fclose
(fp_local);fclose
(fp_urban);
return;}
<规则7>使用宏时,不允许参数发生变化。
下面的例子隐藏了重要的细节,隐含了错误。
#defineSQUARE((x)*(x))
.
.
.
w=SQUARE(++value);
这个引用将被展开称:
w=((++value)*(++value));
其中value累加了两次,与设计思想不符。
正确的用法是:
w=SQUARE(x);
x++;
<规则8>当if、while、for等语句的程序块为摽諗时,使用搟}敺拧_
while(*s++==*t++);
以上代码不符合规范,正确的书写方式为:
while(*s++==*t++)
{
/*无循环体*/
}
或
while(*s++==*t++)
{
}
<规则9>结构中元素布局合理,一行只定义一个元素。
如下例子不符合规范,
typedefstruct
{
_UIleft,top,right,bottom;
}RECT;
应书写称:
typedefstruct
{
_UIleft;/*矩形左侧x坐标*/
_UItop;
_UIright;
_UIbottom;
}RECT;
<规则10>枚举值从小到大顺序定义。
<规则11>包含头文件时,使用撓喽月肪稊,不使用摼月肪稊。
如下引用:
#include"c:
\switch\inc\def.inc"
应改为:
#include"inc\def.inc"
或
#include"def.inc"
<规则12>不允许使用复杂的操作符组合等。
下面用法不好,
iMaxVal=((a>b?
a:
b)>c?
(a>b?
a:
b):
c);
应该为:
iTemp=(a>b?
a:
b);
iMaxVal=(iTemp>b?
iTemp:
b);
不要把"++"、"--"操作符与其他如"+="、"-="等组合在一起形成复杂奇怪的表达式。
如下的
表达式那以理解。
*pStatPoi+++=1;
*++pStatPoi+=1;
应分别改为:
*pStatPoi+=1;
pStatPoi++;
和
++pStatPoi;
*pStatPoi+=1;
<规则13>函数和过程中关系较为紧密的代码尽可能相邻。
如初始化代码应放在一起,不应在中间插入实现其它功能的代码。
以下代码不符合规范,
for(uiUserNo=0;uiUserNo{
...;/*初始化用户数据*/
}
pSamplePointer=NULL;
g_uiCurrentUser=0;/*设置当前用户索引号*/
应必为:
for(uiUserNo=0;uiUserNo{
...;/*初始化用户数据*/
}
g_uiCurrentUser=0;/*设置当前用户索引号*/
pSamplePointer=NULL;
<规则14>每个函数的源程序行数原则上应该少于200行。
对于消息分流处理函数,完成的功能统一,但由于消息的种类多,可能超过200行的限制,
不属于违反规定。
<规则15>语句嵌套层次不得超过5层。
嵌套层次太多,增加了代码的复杂度及测试的难度,容易出错,增加代码维护的难度。
<规则16>用sizeof来确定结构、联合或变量占用的空间。
这样可提高程序的可读性、可维护性,同时也增加了程序的可移植性。
<规则17>避免相同的代码段在多个地方出现。
当某段代码需在不同的地方重复使用时,应根据代码段的规模大小使用函数调用或宏调用的
方式代替。
这样,对该代码段的修改就可在一处完成,增强代码的可维护性。
<规则18>使用强制类型转换。
示例:
USER_RECORD*pUser;
pUser=(USER_RECORD*)malloc(MAX_USER*sizeof(USER_RECORD));
<规则19>避免使用goto语句。
<规则20>避免产生摮绦蚪釘(programknots),在循环语句中,尽量避免break、goto的
使用。
如下例子:
for(i=0;i{
bEof=fscanf(pInputFile,"%d;",&x[i]);
if(bEof==EOF)
{
break;
}
nSum+=x[i];
}
最好按以下方式书写,避免程序打摻釘:
for(i=0;i{
bEof=fscanf(pInputFile,"%d;",&x[i]);
if(bEof!
=EOF)
{
nSum+=x[i];
}
}
<规则21>功能相近的一组常量最好使用枚举来定义。
不推荐定义方式:
/*功能寄存器值*/
#defineERR_DATE1/*日期错误*/
#defineERR_TIME2/*时间错误*/
#defineERR_TASK_NO3/*任务号错误*/
推荐按如下方式书写:
/*功能寄存器值*/
enumERR_TYPE
{
ERR_DATE=1,/*日期错误*/
ERR_TIME=2,/*时间错误*/
ERR_TASK_NO=3/*任务号错误*/
}
<规则22>每个函数完成单一的功能,不设计多用途面面俱到的函数。
多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。
使函数功能明确化,增加程序可读性,亦可方便维护、测试。
<建议1>循环、判断语句的程序块部分用花括号括起来,即使只有一条语句。
如:
if(bCondition==TRUE)
bFlag=YES;
建议按以下方式书写:
if(bCondition==TRUE)
{
bFlag=YES;
}
这样做的好处是便于代码的修改、增删。
<建议2>一行只声明一个变量。
不推荐的书写方式:
voidDoSomething(void)
{
intAmicrtmrs,nRC;
intnCode,nStatus;
推荐做法:
voidDoSomething(void)
{
intnAmicrtmrs;/*ICR计时器*/
intnRC;/*返回码*/
intnCode;/*访问码*/
intnStatus;/*处理机状态*/
<建议3>使用专门的初始化函数对所有的公共变量进行初始化。
<建议4>使用可移植的数据类型,尽量不要使用与具体硬件或软件环境关系密切的变量。
<建议5>用明确的函数实现不明确的语句功能
示例:
如下语句的功能不很明显。
value=(a>b)?
a:
b;
改为如下就很清晰了。
intmax(inta,intb)
{
return((a>b)?
a:
b);
}
value=max(a,b);
或改为如下。
#defineMAX(a,b)(((a)>(b))?
(a):
(b))
value=MAX(a,b);
.4.程序正确性、效率
<规则1>严禁使用未经初始化的变量。
引用未经初始化的变量可能会产生不可预知的后果,特别是引用未经初始化的指针经常会导
致系统崩溃,需特别注意。
声明变量的同时初始化,除了能防止引用未经初始化的变量外,
还可能生成更高效的机器代码。
<规则2>定义公共指针的同时对其初始化。
这样便于指针的合法性检查,防止应用未经初始化的指针。
建议对局部指针也在定义的同时
初始化,形成习惯。
<规则3>较大的局部变量(2K以上)应声明成静态类型(static),避免占用太多的堆栈空
间。
避免发生堆栈溢出,出现不可预知的软件故障。
<规则4>防止内存操作越界。
说明:
内存操作主要是指对数组、指针、内存地址等的操作。
内存操作越界是软件系统主要
错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。
A.数组越界。
charaMyArray[10];
for(i=0;i<=10;i++)
{
aMyArray[i]=0;//当i等于10时,将发生越界。
}
B.指针操作越界。
charaMyArray[10];
char*pMyArray;
pMyArray=aMyArray;
--pMyArray;//越界
pMyArray=aMyArray;
pMyArray+=10;//越界
<规则5>减少没必要的指针使用,特别是较复杂的指针,如指针的指针、数组的指针,指针
的数组,函数的指针等。
用指针虽然灵活,但也对程序的稳定性造成一定威胁,主要原因是当要操作一个指针时,此
指针可能正指向一个非法的地址。
安安全全地使用一个指针并不是一件容易的事情。
<规则6>防止引用已经释放的内存空间。
在实际编程过程中,稍不留心就会出现在一个模块中释放了某个内存块(如指针),而另一
模块在随后的某个时刻又使用了它。
要防止这种情况发生。
<规则7>程序中分配的内存、申请的文件句柄,在不用时应及时释放或关闭。
分配的内存不释放以及文件句柄不关闭,是较常见的错误,而且稍不注意就有可能发生。
这
类错误往往会引起很严重后果,且难以定位。
<规则8>注意变量的有效取值范围,防止表达式出现上溢或下溢。
示例:
unsignedcharcIndex=10;
while(cIndex-->=0)
{
}//将出现下溢
当cIndex等于0时,再减1不会小于0,而是0xFF,故程序是一