转帖:完全的C51编程规范(转)(下)

来源:百度文库 编辑:神马文学网 时间:2024/05/04 05:49:35
完全的C51编程规范(转)(下)

华为软件编程规范和范例华为软件编程规范和范例

<规则12> switch语句的程序块中必须有default语句。
对不期望的情况(包括异常情况)进行处理,保证程序逻辑严谨。

<规则13> 当声明用于分布式环境或不同CPU间通信环境的数据结构时,必须考虑机器的字节顺序,使用的位域也要有充分的考虑。
比如Intel CPU与68360 CPU,在处理位域及整数时,其在内存存放的撍承驍,正好相反。
示例:假如有如下短整数及结构。
unsigned short int exam ;
typedef struct _EXAM_BIT_STRU
{ /* Intel 68360 */
unsigned int A1 : 1 ; /* bit 0 2 */
unsigned int A2 : 1 ; /* bit 1 1 */
unsigned int A3 : 1 ; /* bit 2 0 */
} _EXAM_BIT ;

如下是Intel CPU生成短整数及位域的方式。
内存: 0 1 2 ... (从低到高,以字节为单位)
exam exam低字节 exam高字节

内存: 0 bit 1 bit 2 bit ... (字节的各撐粩)
_EXAM_BIT A1 A2 A3

如下是68360 CPU生成短整数及位域的方式。
内存: 0 1 2 ... (从低到高,以字节为单位)
exam exam高字节 exam低字节

内存: 0 bit 1 bit 2 bit ... (字节的各撐粩)
_EXAM_BIT A3 A2 A1

<规则14> 编写可重入函数时,应注意局部变量的使用(如编写C/C++语言的可重入函数时,应使用auto即缺省态局部变量或寄存器变量)。
可重入性是指函数可以被多个任务进程调用。在多任务操作系统中,函数是否具有可重入性是非常重要的,因为这是多个进程可以共用此函数的必要条件。另外,编译器是否提供可重入函数库,与它所服务的操作系统有关,只有操作系统是多任务时,编译器才有可能提供可重入函数库。如DOS下BC和MSC等就不具备可重入函数库,因为DOS是单用户单任务操作系统。
编写C/C++语言的可重入函数时,不应使用static局部变量,否则必须经过特殊处理,才能使函数具有可重入性。

<规则15> 编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即P、V操作)等手段对其加以保护。
<规则16> 结构中的位域应尽可能相邻。结构中的位域在开始处应对齐撟纸跀或撟謹的边界。
这样可减少结构占用的内存空间,减少CPU处理位域的时间,提高程序效率。

示例:如下结构中的位域布局不合理。(假设例子在Intel CPU环境下)
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nExamTwo : 3 ; // 此位域跨越字节摻唤訑处。
unsigned int nExamThree : 4 ;
} _EXAMPLE ;

应改为如下(按字节对齐)。
typedef struct _EXAMPLE_STRU
{
unsigned int nExamOne : 6 ;
unsigned int nFreeOne : 2 ; // 保留bit位,使下个位域从字节开始。
unsigned int nExamTwo : 3 ; // 此位域从新的字节处开始。
unsigned int nExamThree : 4 ;
} _EXAMPLE ;

<规则17> 避免函数中不必要语句,防止程序中的垃圾代码,预留代码应以注释的方式出现。
程序中的垃圾代码不仅占用额外的空间,而且还常常影响程序的功能与性能,很可能给程序的测试、维护等造成不必要的麻烦。

<规则18> 通过对系统数据结构的划分与组织的改进,以及对程序算法的优化来提高空间效率。
这种方式是解决软件空间效率的根本办法。
示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char _UC ;
typedef unsigned int _UI ;

typedef struct _STUDENT_SCORE_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;

因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进(分为两个结构),总的存贮空间将变小,操作也变得更方便。
typedef struct _STUDENT_STRU
{
_UC szName[ 8 ] ;
_UC cAge ;
_UC cSex ;
_UC cClass ;
} _STUDENT ;

typedef struct _STUDENT_SCORE_STRU
{
_UI iStudentIndex ;
_UC cSubject ;
float fScore ;
} _STUDENT_SCORE ;

<规则19> 循环体内工作量最小化。
应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
示例:如下代码效率不高。
for ( i= 0 ; i< MAX_ADD_NUMBER ; i++ )
{
nSum += i;
nBackSum = nSum ; /* 备份和 */
}

语句搉BackSum = nSum ;斖耆 梢苑旁趂or语句之后,如下。
for ( i = 0 ; i < MAX_ADD_NUMBER ; i ++ )
{
nSum += i ;
}
nBackSum = nSum ; /*备份和 */

<规则20> 在多重循环中,应将最忙的循环放在最内层。

<规则21> 避免循环体内含判断语句,将与循环变量无关的判断语句移到循环体外。
目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以。

<规则22> 尽量用乘法或其它方法代替除法,特别是浮点运算中的除法,在时间效率要求不是特别严格时,要优先保证程序的可读性。
说明:浮点运算除法要占用较多CPU资源。
示例:如下表达式运算可能要占较多CPU资源。
#define PAI 3.1416
fRadius = fCircleLength / ( 2 * PAI ) ;

应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL ( 1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
fRadius = fCircleLength * PAI_RECIPROCAL / 2 ;

<规则23> 用“++敚瑩--敳僮鞔 鎿+=1敚瑩-=1敚 岣叱绦蛩俣取_

<规则24> 系统输入(如用户输入)、系统输出(如信息包输出)、系统资源操作(如内存分配、文件及目录操作)、网络操作(如通信、调用等)、任务之间的操作(如通信、调用等)时必须进行错误、超时或者异常处理。

<建议 1> 定义字符串变量的同时将其初始化为空即摂,以避免无限长字符串。
<建议 2> 在switch语句中将经常性的处理放在前面。

2.5. 接口
<规则1> 头文件应采用 #ifndef / #define / #endif 的方式来防止多次被嵌入。
示例如下:
假设头文件为揇EF.INC",则其内容应为:
#ifndef __DEF_INC
#define __DEF_INC
...
#endif

<规则2> 去掉没有必要的公共变量,编程时应尽量少用公共变量。
公共变量是增大模块间耦合的原因之一,故应减少没必要的公共变量以降低模块间的耦合度。应该构造仅有一个模块或函数可以修改、创建,而其余有关模块或函数只访问的公共变量,防止多个不同模块或函数都可以修改、创建同一公共变量的现象。

<规则3> 当向公共变量传递数据时,要防止越界现象发生。
对公共变量赋值时,若有必要应进行合法性检查,以提高代码的可靠性、稳定性。

<规则4> 返回值为指针的函数,不可将局部变量的地址作为返回值。
当函数退出时,非static局部变量将消失,所以引用返回的指针将可能引起严重后果。下例将不能完成正确的功能。
char *GetFilename(int nFileNo)
{
char szFileName[20];

sprintf( szFileName, "COUNT%d", nFileNo);
return szFileName;
}

<规则5> 尽量不设计多参数函数,将不使用的参数从接口中去掉,降低接口复杂度。
减少函数间接口的复杂度。

<规则6> 对所调用函数的返回码要仔细、全面地处理。
防止把错误传递到后面的处理流程。如有意不检查其返回码,应明确指明。 如:
(void)fclose(fp);

<规则7> 显示地给出函数的返回值类型。无返回值函数定义为void。
C、C++语言的编译系统默认无显示返回值函数的返回值类型为int。

<规则8> 声明函数原型时给出参数名称和类型,并且与实现此函数时的参数名称、类型保持一致,无参数的函数,用void声明。
示例:下面声明不正确。
int CheckData( ) ;
int SetPoint( int, int ) ;
int SetPoint( x, y )
int x, y;

应改为如下声明:
int CheckData( void ) ;
int SetPoint( int x, int y ) ;

<规则9> 检查接口函数所有输入参数的有效性。
可直接检查或使用断言进行检查,尤其是指针参数。只在本模块内使用的函数可不检查。

<规则10> 检查函数的所有非参数输入,如数据文件、公共变量等。
可直接检查或使用断言进行检查,尤其是指针变量。

<规则11> 声明函数原型时,对于数组型参数,不要声明为指针,维护函数接口的清晰性。
示例:假设函数SortInt()完成的功能是对一组整数排序,接受的参数是一整数数组及数组中的元素个数,以下声明不符合规范。
void SortInt(int num, int *data);

应声明为:
void SortInt(int num, int data[]);


2.6.代码可测性
<规则1> 模块编写应该有完善的测试方面的考虑。

<规则2> 源代码中应该设计了代码测试的内容,如打印宏开关、变量值、函数名称、函数值等。
在编写代码之前,应预先设计好程序调试与测试的方法和手段,并设计好各种调测开关及相应测试代码如打印函数等。
程序的调试与测试是软件生存周期中很重要的一个阶段,如何对软件进行较全面、高率的测试并尽可能地找出软件中的错误就成为很关键的问题。因此在编写源代码之前,除了要有一套比较完善的测试计划外,还应设计出一系列代码测试手段,为单元测试、集成测试及系统联调提供方便。

<规则3> 在同一项目组或产品组内,要有一套统一的为集成测试与系统联调准备的调测开关及相应打印函数,并且要有详细的说明。
本规则是针对项目组或产品组的。
示例:.ext文件示例,文件名为:EXAMPLE.EXT。
/* 头文件开始 */
#ifndef __EXAMPLE_EXT
#define __EXAMPLE_EXT

#define _EXAMPLE_DEBUG_ // 模块测试总开关。打开开关的含义是模块可以
// 进行单元测试或其它功能、目的等的测试。
#ifdef _EXAMPLE_DEBUG_
#define _EXAMPLE_UNIT_TEST_ // 单元测试宏开关
#define _EXAMPLE_ASSERT_TEST_ // 断言测试开关
... // 其它测试开关
#endif

#ifndef _EXAMPLE_UNIT_TEST_ // 若没有定义单元测试
#i nclude // 各模块共用的头文件
#i nclude // 系统接口头文件

#ifndef _SYSTEM_DEBUG_VERSION_ // 如果是发行版本(即非DEBUG版)
#undef _EXAMPLE_UNIT_TEST_
#undef _EXAMPLE_ASSERT_TEST_
... // 将所有与测试有关的开关都关掉,即编译时不含任何测试代码
#endif

#i nclude // 与另一模块的接口头文件
... // 其它接口头文件
#else // 若定义了单元测试,则应构造单元测试所需的环境、结构等。
typdef unsigned char _UC ;
typdef unsigned long _UL ;
#define TRUE 1
... // 所有为单元测试准备的环境,如宏、枚举、结构、联合等。
#endif

#endif /* EXAMPLE.EXT结束 */
/* 头文件结束 */

<规则4> 在同一项目组或产品组内,调测打印出的信息串的格式要有统一的形式。信息串中至少要有所在模块名(或源文件名)及行号。
统一的调测信息格式便于集成测试。

<规则5> 使用断言来发现软件问题,提高代码可测性。
断言是对某种假设条件进行检查(可理解为若条件成立则无动作,否则应报告),它可以快速发现并定位软件问题,同时对系统错误进行自动报警。断言可以对在系统中隐藏很深,用其它手段极难发现的问题进行定位,从而缩短软件问题定位时间,提高系统的可测性。实际应用时,可根据具体情况灵活地设计断言。
示例:下面是C语言中的一个断言,用宏来设计的。(其中NULL为0L)
#ifdef _EXAM_ASSERT_TEST_ // 若使用断言测试

void ExamAssert( char * szFileName, unsigned int nLineNo )
{
printf( "\n[EXAM] Assert failed: %s, line %u\n",
szFileName, nLineNo ) ;
abort( ) ;
}

#define EXAM_ASSERT( condition ) \
if ( condition ) \ // 若条件成立,则无动作
NULL ; \
else \ // 否则报告
ExamAssert( __FILE__, __LINE__ )

#else // 若不使用断言测试

#define EXAM_ASSERT( condition ) NULL

#endif /* ASSERT结束 */

<规则6> 用断言来检查程序正常运行时不应发生但在调测时有可能发生的非法情况。

<规则7> 不能用断言代替错误处理来检查最终产品肯定会出现且必须处理的错误情况。
如某模块收到其它模块或链路上的消息后,要对消息的合理性进行检查,此过程为正常的错误检查,不能用断言来代替。

<规则8> 用断言确认函数的参数。
示例:假设某函数参数中有一个指针,那么使用指针前可对它检查,如下。
int ExamFunc( unsigned char *str )
{
EXAM_ASSERT( str != NULL ) ; // 用断言检查摷偕柚刚氩晃 諗这个条件

... // 其它程序代码
}

<规则9> 用断言保证没有定义的特性或功能不被使用。
示例:假设某通信模块在设计时,准备提供撐蘖 訑和摿 訑 这两种业务。但当前的版本中仅实现了撐蘖 訑业务,且在此版本的正式发行版中,用户(上层模块)不应产生摿 訑业务的请求,那么在测试时可用断言检查用户是否使用摿 訑业务。如下。
#define EXAM_CONNECTIONLESS 0 // 无连接业务
#define EXAM_CONNECTION 1 // 连接业务

int MsgProcess( _EXAM_MESSAGE *msg )
{
unsigned char cService ; /* 消息服务类 */

EXAM_ASSERT( msg != NULL ) ;

cService = GetMsgServiceClass( msg ) ;

EXAM_ASSERT( service != EXAM_CONNECTION ) ; // 假设不使用连接业务

... // 其它程序代码
}

<规则10> 用断言对程序开发环境(OS/Compiler/Hardware)的假设进行检查。
程序运行时所需的软硬件环境及配置要求,不能用断言来检查,而必须由一段专门代码处理。用断言仅可对程序开发环境中的假设及所配置的某版本软硬件是否具有某种功能的假设进行检查。如某网卡是否在系统运行环境中配置了,应由程序中正式代码来检查;而此网卡是否具有某设想的功能,则可由断言来检查。
对编译器提供的功能及特性假设可用断言检查,原因是软件最终产品(即运行代码或机器码)与编译器已没有任何直接关系,即软件运行过程中(注意不是编译过程中)不会也不应该对编译器的功能提出任何需求。
示例:用断言检查编译器的int型数据占用的内存空间是否为2,如下。
EXAM_ASSERT( sizeof( int ) == 2 ) ;

<规则11> 正式软件产品中应把断言及其它调测代码去掉(即把有关的调测开关关掉)。

<规则12> 用调测开关来切换软件的DEBUG版和正式版,而不要同时存在正式版本和DEBUG版本的不同源文件,以减少维护的难度。

<规则13> 在软件系统中设置与取消有关测试手段,不能对软件实现的功能等产生影响。
即有测试代码的软件和关掉测试代码的软件,在功能行为上应一致。

<规则14> 发现错误应该立即修改,并且若有必要记录下来。

<规则15> 开发人员应坚持对代码进行彻底的测试(单元测试),而不依靠他人或测试组来发现问题。
<规则16> 清理、整理或优化后的代码要经过审查及测试。
<规则17> 代码版本升级要经过严格测试。

2.7. 代码编译

<规则1> 打开编译器的所有告警开关对程序进行编译。
防止隐藏可能是错误的告警。

<规则2> 在同一项目组或产品组中,要统一编译开关选项。

<规则3> 某些语句经编译后产生告警,但如果你认为它是正确的,那么应通过某种手段去掉告警信息