软件接口API规范
当前版本:V1.0
版本日期:2018-01-30
第一章 内存管理
避免动态分配内存,以静态方式分配
说明:为提高内存管理的稳定性和可靠性,算法模块可提出内存申请请求,由外部接口进行统一分配内存,禁止在算法模块内部进行动态内存的申请和释放。
内存分配要求4字节对齐
说明:内存对齐可提高数据的访问速度和效率。模块内部若是开辟大块内存,可注明要求 8 字节对齐、128 字节对齐等信息。具体到数据类型转换或数据赋值时,如 char *p; *(int*)p = 4; 此时的 p 要 32 位对齐
除数据表外,模块内部不允许存在全局变量
说明:为防止在集成中存在局部变量与全局变量同名等现象,要求模块禁止使用全局变量。建议所有变量均应归纳至句柄、参数结构体、输入出结构体三类结构体中。 【注】:模块可允许存在一些数据表作为全局变量,但数据表的命名要要与别的数据表、变量要严格区分,另外,对于大型的表须以段的形式声明,小的表以static形式定义,在模块进行提交时需进行详细的说明。
第二章 函数管理
在函数入口处或者数据对齐处,添加断言语句
说明:在PC 或嵌入式系统均支持断言语句,断言语句 assert 可以看成一个在任何系统 状态下都可以安全使用的无害测试手段,在函数入口处,利用断言可以有效检测输入参数的有效性,特别对于数据内存对齐的地方,添加断言语句能够对关心的位置进行有效的检测,避免很多隐藏的BUG 的产生。
函数内部/参量不许存在大型数组和结构体变量
说明:由于函数内部调用结构体类型变量,需将这个结构体内存空间加载到当前函数堆栈,易引起堆栈溢出等不稳定现象发生,建议在函数里内部使用临时变量或临时结构体变量不要过多。模块函数内部和参量中不允许存在大型数组/结构体类型变量,必须以数组指针/结构体指针形式存在。
内部函数/变量不被外部文件调用需用 static 定义
说明:对于文件内部的函数/变量是指当前函数/变量仅被当前文件的函数所调用,外部 函数不得调用该函数/变量,采用static关键字定义能够让编译器清楚当前函数/变量的引用范围,在外部也不需要对函数/变量进行外部声明。但注意,使用static把它局限在内部文件使用,请详细检查参数的有效性。因为static函数参数/变量,一般不需要参数检查,请调用者自行在调用处保证参数可靠。
建议设计高扇入、合理扇出(小于5 )的函数
说明:扇出是指一个函数直接调用 (控制)其它函数的数目,而扇入是指有多少上级函 数调用它。扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
对函数的错误返回码要仔细、全面地处理
说明:
1. 定义所有函数的错误码信息,函数返回值必须是错误码中的一个
2. 函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误
或忽视错误返回码。
3. 在开发中函数异常返回值必须有相应的处理和告警信息。
尽可能确保接口的简单和明了
说明:模块集成时不关注各个子模块,子模块的相互调用由模块内部进行操作,因此建议各子模块接口并入模块接口相应位置进行处理,当需融合多个子模块时,将子模块对应结构纳入到外部句柄、参数结构体、输入/ 出结构体中。特别的,一个模块只提供一组标准接口。
建议函数功能较为单一,防止循环体过于复杂
说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。不要设计多用途面面俱到的函数。建议一个函数只做一件事情,不要设计多用途的函数,要控制函数体的整体规模,特别注意循环体不要过于复杂
检查函数所有输入参数的有效性
说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入, 即非参数输入。函数在使用输入之前,应进行必要的检查。
编写可重入函数,尽量避免函数带有 “记忆”功能
说明:要保证函数的功能是可以预测且可重入的,这就要求函数只要输入数据相同就应 产生同样的输出。有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种 ‘“记忆状态”。这样的函数既不易于理解又不利于测试和维护。编写C/C++语言的可重入函数时,不应使用 static 局部变量,否则必须经过特殊处理,才能使函数具有可重入性。在 C/C++语言中,函数的 static 局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是STATIC 的局部变量的地址作为返回值,若为 AUTO 类,则返回为野指针。建议尽量少用 static 局部变量,除非必须。
示例:如下函数,其返回值 (即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是 static 类型的。
// 若改为 auto 类型,则函数即变为可预测。
f or (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
防止将函数的参数作为工作变量
说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须 改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。
示例:下函数的实现不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;
for (count = 0; count < num; count++)
{
*sum += data[count]; // sum 成了工作变量,不太好。
}
}
若改为如下,则更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0;
for (count = 0; count < num; count ++)
{
sum_temp += data[count];
}
*sum = sum_temp;
}
无任何数据处理的小函数建议合并到上一级
说明:模块中函数划分的过多,一般会使函数间的接口变得复杂。所以过小的函数,特 别是扇入很低的或功能不明确的函数,不值得单独存在。
不要使用函数本身或函数间的递归调用
说明:递归调用特别是函数间的递归调用 (如A->B->C->A ),影响程序的可理解性; 递归调用一般都占用较多的系统资源 (如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
仔细分析模块的功能及性能需求
说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结 构,关系到模块的最终效率和可维护性、可测性等。优化函数结构时,要遵守以下原则:
(1)不能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)通过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)降低函数间接口的复杂度。
(6)不同层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提高函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。
第三章 代码管理
尽量少(不)用浮点型数据运算
说明:相机多采用定点型数据处理芯片,浮点型数据处理能力相对较弱,若确实需要使用浮点型数据,须注明数据范围和数据精度
程序运算尽量简单,控制逻辑尽量简化
说明:建议采用减,乘,移位,与,或,非等运算;避免使用的如除法,开方,随机函 数,cos/sin/log 等操作,必要情况可用移位,查表或其他数值计算的方法替代。建议避免循环中间嵌套复杂 if~else~, 递归等语句,尽量保证嵌套的条件语句或循环语句在三层以内;避免多重循环,尽量使用简单语句。
注意运算符的优先级
说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。 建议用括号明确表达式的操作顺序,避免使用默认优先级。
示例:下列语句中的表达式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
如果书写为:
high << 8 | low
a | b && a & c
a | b < c & d
由于
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不会出错,但语句不易理解;
a | b < c & d = a | (b < c) & d,(3)造成了判断条件出错。
用枚举或宏来替代不易理解的数字
示例:如下的程序可读性差。 if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
... // program code
}
应改为如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... // program code
}
不要使用难懂的技巧性很高的语句
说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。
示例:如下表达式,考虑不周就可能出问题,也较难理解。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
应分别改为如下:
*stat_poi += 1;
stat_poi++; // 此二语句功能相当于“ * stat_poi ++ += 1; ”
++ stat_poi;
*stat_poi += 1; // 此二语句功能相当于“ * ++ stat_poi += 1; ”
仔细设计结构中元素的布局与排列顺序
说明:合理排列结构中元素顺序,可节省空间并增加可理解性,而且这样会使结构容易 理解、节省占用空间,并减少引起误用现象
示例:如下结构中的位域排列,将占较大空间,可读性也稍差。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
PERSON person;
unsigned int set_flg: 1;
} EXAMPLE;
若改成如下形式,不仅可节省 1 字节空间,可读性也变好了。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
unsigned int set_flg: 1;
PERSON person ;
} EXAMPLE;
注意数据类型的强制转换
说明:因为数据类型转换或多或少存在危险。除非必要,最好不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回,在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转换。
充分利用宏定义,但注意宏的定义格式
说明:定义宏可以大大增加编程效率和程序的可读性,同时也大大方便今后代码的维护对较长变量且存在多次引用时,可以用宏代替。
例如:
1.某过程中较多引用TheReceiveBuffer[FirstSocket].byDataPtr,
则可以通过以下宏定义来代替:
# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr
2. 算法中部分变量数值可能需要全局修改,定义宏可以避免多处变量进行修改。
3. 但在使用过程中,要注意宏的定义格式,因此系统不对参数进行必要的检测,仅进行简单的替换。
特别注意指针在函数调用中的强制
说明
void Fuc(char* pSrc,int* pDst,int n)
{
int i;
for(i=0;i<n;i++)
{
pDst[i]=pSrc[i];
}
}
Void main()
{
int Src[3]={1,2,3};
int Dst[3];
Fuc(Src,Dst,3);
}
第四章 风格管理
头文件必须加入预编译指令
说明:这样就会防止头文件的重复加载,建议统一采取以下头文件定义格式: #ifndef _FILE_MODELA_H_随机字串 #define _FILE_ MODELA _H_随机字串
…….
#endif //_FILE_ MODELA _H_随机字串
请在关键位置添加注释,注释内容要清楚、明了
说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也 不能太少,注释语言必须准确、易懂、简洁。
对外接口头文件必须具有详细的说明
示例:头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、 功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
/*************************************************
Copyright (C), 1988-1999, QianYi Tech. Co., Ltd.
File name: // 文件名
Description: // 用于详细说明此程序文件完成的主要功能,与其他模块
//函数的接口,输出值、取值范围、含义及参数间的控
//顺序、独立或依赖等关系
Version: //版本号
Date: //完成日期
*************************************************/
子模块的头文件应添加必要的注释和说明
示例:头文件需注明版权说明、版本号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系 (函数、表)等。
/**********************************************************
Copyright (C), 1988-1999, Unihz Tech. Co., Ltd.
FileName: // 文件名
Description: // 模块描述
Version: // 版本信息
History: // 历史修改记录
<author> <time> <version > <desc>
David 96/10/12 1.0 build this moudle
**********************************************************/
说明:Description 描述本模块的功能。History 是修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述。
内部子模块函数必须添加函数头
/*************************************************
Function: // 函数名称
Description: // 函数功能、性能等的描述
Input: // 输入参数说明,包括每个参数的作用、取值说明及参数间关系
Output: // 对输出参数的说明
Return: // 函数返回值的说明
Others: // 其它说明
*************************************************/
模块分级及函数命名方式
模块一般情况下分为若干层,每层的函数命名与其对应的模块层相关。具体事例如下:
整个模块分为:接口层,消息处理层,
接口层:模块对外提供的api ,如:模块初始化,模块主函数,模块反初始化
消息处理层:模块接收消息内部处理api
//模块初始化函数
/**********************************************************************
* 函数名称:DevMaintainInit
* 功能描述:设备维护模块初始化
* 输入参数:pParam -模块信息结构指针
* 输出参数:无
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
***********************************************************************/
E_StateCode DevMaintainInit(void *pParam);
//模块主函数
/**********************************************************************
* 函数名称:DevMaintainDestroy
* 功能描述:设备维护模块销毁
* 输入参数:pParam -模块信息结构指针
* 输出参数:无
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
***********************************************************************/
E_StateCode DevMaintainFxn(void *pParam);
//模块反初始化函数
/**********************************************************************
* 函数名称:DevMaintainDestroy
* 功能描述:设备维护模块销毁
* 输入参数:pParam -模块信息结构指针
* 输出参数:pParam -模块信息结构指针
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
***********************************************************************/
E_StateCode DevMaintainDestroy(void *pParam);
变量及结构体命名
变量命名采用匈牙利规则,具体如下:
UINT8* pSrc; //指针前缀 p
UINT8 ucSrcData;
INT16 wSrcData;
UINT16 uwSrcData;
INT32 nSrcData;
UINT32 udwSrcData;
FLOAT fSrcData;
BOOL bFirst;
各模块结构体命名必须前缀模块名称
标识符的命名要清晰、明了,有明确含义
说明:较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。
示例:如下单词的缩写能够被大家基本认可。使用完整的单词或大家基本可以理解的缩 写,避免使人产生误解
temp 可缩写为 tmp ;
flag 可缩写为 flg ;
statistic 可缩写为 stat ;
increment 可缩写为 inc ;
message 可缩写为 msg ;
用具有互斥意义或相反动作的名字定义变量和函数
说明:下面是一些在软件中常用的反义词组。
add / remove begin / end create / destroy
insert / delete first / last g et / release
increment / decrement put / get
add / delete lock / unlock open / close
min / max old / new start / stop
next / previous source / target show / hide
send / receive source / destination
cut / paste up / down
示例:
int min_sum;
int max_sum;
int add_user( BYTE *user_name );
int delete_user( BYTE *user_name );
函数体代码长度不能超过300 行
说明:过长代码请根据程序功能模块的设计进行函数肢解。不包括注释和空格行。
函数不能超过5 层,参量个数不能超过6 个
说明:目的减少函数间接口的复杂度。函数调用层数越少越好,层数过多,导致函数堆 栈的压力很大,易发生堆栈溢出。避免设计多参数函数,不使用的参数从接口中去掉,建议以 2-4 为最佳。
程序块要采用缩进风格编写,缩进的空格数为 4 个
说明:对于由开发工具自动生成的代码可以有不一致。另外,保持适度的空行。
较长的语句(>80 字符)要分成多行书写
说明:长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行 要进行适当的缩进,使排版整齐,语句可读。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER +
index].occupied
= stat_poi[index].occupied;
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
一行只写一条语句
示例:如下例子不符合规范。
rect.length = 0; rect.width = 0;
IVS_parm *pA,pB,pC;
应如下书写
rect.length = 0;
rect.width = 0;
IVS_parm *pA;
IVS_parm *pB;
IVS_parm *pC;
注意部分语句的排版
说明:if、for、do、while、case、switch、default 等语句自占一行,且 if、for、do、while 等语句的执行语句部分无论多少都要加括号{}。 示例:如下例子不符合规范。
if (pUserCR == NULL) return;
应如下书写:
if (pUserCR == NULL)
{
return;
}
谨慎使用64 位数据处理类型和方式
说明:在 PC 环境下存在_int64 而在 嵌入式里需定义 long long 类型,一般建议在 PC 开发环境避免使用 64 为数据操作,除非必要。另外,对于 64 位数据赋值一定要保证数据的起始地址在 64 位数据对齐的位置。
例如:采用如下格式进行 64 位数据赋值,由于 h->mb.cache.mv 的数据指针不能保证 64 位数据对齐,导致数据赋值存在问题。
*(uint64_t*)h->mb.mv[0][i_mb_4x4+y*s4x4+0] =
*(uint64_t*)h->mb.cache.mv[0][x264_scan8[0]+8*y+0];
因此,对于以上问题,解决途径有两个:第一,保证指针地址的 64 位数据对齐;第二, 逐个字节对数据进行赋值。
注: float 与 double 的区别,建议均采用float 类型。
局部变量/指针在定义的同时应当初始化
说明:对于局部变量/指针建议在定义时同时完成初始化,防止使用未初始化的变量/ 指针出现在赋值语句的右边。特别对于 PC 的静态 static 变量或常量在程序的运行时自动初始化为 0,而在嵌入式环境下系统没有进行初始化,为避免这方面导致的差异,要求数据定义时必须同时完成初始化工作。
第五章效率管理
改进数据结构的划分和组织,提高空间效率
说明:这种方式是解决软件空间效率的根本办法。
示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef struct STUDENT_SCORE_STRU
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
BYTE subject;
float score;
} STUDENT_SCORE;
因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进 (分为两个结构),总的存贮空间将变小,操作也变得更方便。
typedef struct STUDENT_STRU
{
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
} STUDENT;
typedef struct STUDENT_SCORE_STRU
{
WORD student_index;
BYTE subject;
float score;
} STUDENT_SCORE;
循环体内工作量最小化
说明:应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
示例:如下代码效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
back_sum = sum; /* backup sum */
}
语句“back_sum = sum;”完全可以放在 for 语句之后,如下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
}
back_sum = sum; /* backup sum */
在多重循环中,应将最忙的循环放在最内层,
说明:减少 CPU 切入循环层的次数。
示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
对于多维数组的循环遍历建议“先行后列”的顺序
说明:对于多维数组来说,“先行后列”的遍历效率肯定好于 “先列后行”遍历,不论 其行数远大于列数还是情况相反甚至接近,即使在最坏的情况下也至少与 “先列后行” 遍历的效率相当。影响效率的实际上主要是大型数组导致的内存页面交换次数以及Cache 的命中率的高低,而不是循环次数本身。
示例:如下代码效率不高。
int array[1024][512]
for (row = 0; row < 1024; row++)
{
for (col = 0; col <512; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col <512; col++)
{
for (row = 0; row < 1024; row++)
{
sum += a[row][col];
}
}
尽量减少循环嵌套层次,避免循环体内含判断语句
说明:目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以,应将循环语句置于判断语句的代码块之中。 示例:如下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
if (data_type == RECT_AREA)
{
area_sum += rect_area[ind];
}
else
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
因为判断语句与循环变量无关,故可如下改进,以减少判断次数。
if (data_type == RECT_AREA)
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
area_sum += rect_area[ind];
}
}
else
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
尽量用乘法或其它方法代替除法
说明:浮点运算除法要占用较多 CPU 资源。
示例:如下表达式运算可能要占较多 CPU 资源。
#define PAI 3.1416
radius = circle_length / (2 * PAI);
应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;
第六章 BUG 管理管理
防止内存操作越界
说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。 示例:假设某软件系统最多可由 10 个用户同时使用,用户号为 1-10,那么如下程序存在问题。
#define MAX_USR_NUM 10
unsigned char usr_login_flg[MAX_USR_NUM]= "";
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
}
当 usr_no 为 10 时,将使用 usr_login_flg 越界。可采用如下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
务必增加严重的异常处理机制
说明:在程序中内存处理和函数返回值的位置处增加数据异常和出错的异常处理机制能 有效跟踪 BUG 的位置。
系统运行之初,要对数据进行一致性检查
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。系统运行之初,要 初始化有关变量及运行环境,防止未经初始化的变量被引用,系统运行之初,要对加载到系统中的数据进行一致性检查
编程时,要防止差1 错误
说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后, 应对这些操作符进行彻底检查。
要时刻注意易混淆的操作符
说明:形式相近的操作符最容易引起误用,如 C/C++中的“=”与“==”、“|”与“||”、“&”与“&&”等,若拼写错了,编译器不一定能够检查出来。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。 示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:
rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
保留switch 语句下的 default 分支和 break 语句
说明:可能的话,if语句尽量加上 else 分支,对没有else 分支的语句要小心对待;switch 语句必须有 default 分支,对于每一个 case 下面均有一个 break 语句。
时刻注意表达式是否会上溢、下溢
示例:如下程序将造成变量下溢。
unsigned char size ;
while (size-- >= 0) // 将出现下溢
{
... // program code
}
当 size 等于 0 时,再减 1 不会小于 0,而是 0xFF,故程序是一个死循环。应如下修改。
char size; // 从 unsigned char 改为 char
while (size-- >= 0)
{
... // program code
}
使用变量时要注意其边界值的情况
示例:如 C 语言中字符型变量,有效值范围为-128 到 127。故以下表达式的计算存在一定风险。 char chr = 127;
int sum = 200; chr += 1; // 127 为 chr 的边界值,再加 1 将使 chr 上溢到-128,而不是 128。
sum += chr; // 故 sum 的结果不是 328,而是 72。
若 chr 与 sum 为同一种类型,或表达式按如下方式书写,可能会好些。
sum = sum + chr + 1;
当前版本:V1.0
版本日期:2018-01-30
第一章 内存管理
避免动态分配内存,以静态方式分配
说明:为提高内存管理的稳定性和可靠性,算法模块可提出内存申请请求,由外部接口进行统一分配内存,禁止在算法模块内部进行动态内存的申请和释放。
内存分配要求4字节对齐
说明:内存对齐可提高数据的访问速度和效率。模块内部若是开辟大块内存,可注明要求 8 字节对齐、128 字节对齐等信息。具体到数据类型转换或数据赋值时,如 char *p; *(int*)p = 4; 此时的 p 要 32 位对齐
除数据表外,模块内部不允许存在全局变量
说明:为防止在集成中存在局部变量与全局变量同名等现象,要求模块禁止使用全局变量。建议所有变量均应归纳至句柄、参数结构体、输入出结构体三类结构体中。 【注】:模块可允许存在一些数据表作为全局变量,但数据表的命名要要与别的数据表、变量要严格区分,另外,对于大型的表须以段的形式声明,小的表以static形式定义,在模块进行提交时需进行详细的说明。
第二章 函数管理
在函数入口处或者数据对齐处,添加断言语句
说明:在PC 或嵌入式系统均支持断言语句,断言语句 assert 可以看成一个在任何系统 状态下都可以安全使用的无害测试手段,在函数入口处,利用断言可以有效检测输入参数的有效性,特别对于数据内存对齐的地方,添加断言语句能够对关心的位置进行有效的检测,避免很多隐藏的BUG 的产生。
函数内部/参量不许存在大型数组和结构体变量
说明:由于函数内部调用结构体类型变量,需将这个结构体内存空间加载到当前函数堆栈,易引起堆栈溢出等不稳定现象发生,建议在函数里内部使用临时变量或临时结构体变量不要过多。模块函数内部和参量中不允许存在大型数组/结构体类型变量,必须以数组指针/结构体指针形式存在。
内部函数/变量不被外部文件调用需用 static 定义
说明:对于文件内部的函数/变量是指当前函数/变量仅被当前文件的函数所调用,外部 函数不得调用该函数/变量,采用static关键字定义能够让编译器清楚当前函数/变量的引用范围,在外部也不需要对函数/变量进行外部声明。但注意,使用static把它局限在内部文件使用,请详细检查参数的有效性。因为static函数参数/变量,一般不需要参数检查,请调用者自行在调用处保证参数可靠。
建议设计高扇入、合理扇出(小于5 )的函数
说明:扇出是指一个函数直接调用 (控制)其它函数的数目,而扇入是指有多少上级函 数调用它。扇出过大,表明函数过分复杂,需要控制和协调过多的下级函数;而扇出过小,如总是1,表明函数的调用层次可能过多,这样不利程序阅读和函数结构的分析,并且程序运行时会对系统资源如堆栈空间等造成压力。函数较合理的扇出(调度函数除外)通常是3-5。扇出太大,一般是由于缺乏中间层次,可适当增加中间层次的函数。扇出太小,可把下级函数进一步分解多个函数,或合并到上级函数中。当然分解或合并函数时,不能改变要实现的功能,也不能违背函数间的独立性。扇入越大,表明使用此函数的上级函数越多,这样的函数使用效率高,但不能违背函数间的独立性而单纯地追求高扇入。公共模块中的函数及底层函数应该有较高的扇入。较良好的软件结构通常是顶层函数的扇出较高,中层函数的扇出较少,而底层函数则扇入到公共模块中。
对函数的错误返回码要仔细、全面地处理
说明:
1. 定义所有函数的错误码信息,函数返回值必须是错误码中的一个
2. 函数的每种出错返回值的意义要清晰、明了、准确,防止使用者误用、理解错误
或忽视错误返回码。
3. 在开发中函数异常返回值必须有相应的处理和告警信息。
尽可能确保接口的简单和明了
说明:模块集成时不关注各个子模块,子模块的相互调用由模块内部进行操作,因此建议各子模块接口并入模块接口相应位置进行处理,当需融合多个子模块时,将子模块对应结构纳入到外部句柄、参数结构体、输入/ 出结构体中。特别的,一个模块只提供一组标准接口。
建议函数功能较为单一,防止循环体过于复杂
说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。不要设计多用途面面俱到的函数。建议一个函数只做一件事情,不要设计多用途的函数,要控制函数体的整体规模,特别注意循环体不要过于复杂
检查函数所有输入参数的有效性
说明:函数的输入主要有两种:一种是参数输入;另一种是全局变量、数据文件的输入, 即非参数输入。函数在使用输入之前,应进行必要的检查。
编写可重入函数,尽量避免函数带有 “记忆”功能
说明:要保证函数的功能是可以预测且可重入的,这就要求函数只要输入数据相同就应 产生同样的输出。有“记忆”功能的函数,其行为可能是不可预测的,因为它的行为可能取决于某种 ‘“记忆状态”。这样的函数既不易于理解又不利于测试和维护。编写C/C++语言的可重入函数时,不应使用 static 局部变量,否则必须经过特殊处理,才能使函数具有可重入性。在 C/C++语言中,函数的 static 局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是STATIC 的局部变量的地址作为返回值,若为 AUTO 类,则返回为野指针。建议尽量少用 static 局部变量,除非必须。
示例:如下函数,其返回值 (即功能)是不可预测的。
unsigned int integer_sum( unsigned int base )
{
unsigned int index;
static unsigned int sum = 0; // 注意,是 static 类型的。
// 若改为 auto 类型,则函数即变为可预测。
f or (index = 1; index <= base; index++)
{
sum += index;
}
return sum;
}
防止将函数的参数作为工作变量
说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须 改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。
示例:下函数的实现不太好。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count;
*sum = 0;
for (count = 0; count < num; count++)
{
*sum += data[count]; // sum 成了工作变量,不太好。
}
}
若改为如下,则更好些。
void sum_data( unsigned int num, int *data, int *sum )
{
unsigned int count ;
int sum_temp;
sum_temp = 0;
for (count = 0; count < num; count ++)
{
sum_temp += data[count];
}
*sum = sum_temp;
}
无任何数据处理的小函数建议合并到上一级
说明:模块中函数划分的过多,一般会使函数间的接口变得复杂。所以过小的函数,特 别是扇入很低的或功能不明确的函数,不值得单独存在。
不要使用函数本身或函数间的递归调用
说明:递归调用特别是函数间的递归调用 (如A->B->C->A ),影响程序的可理解性; 递归调用一般都占用较多的系统资源 (如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。
仔细分析模块的功能及性能需求
说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结 构,关系到模块的最终效率和可维护性、可测性等。优化函数结构时,要遵守以下原则:
(1)不能影响模块功能的实现。
(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。
(3)通过分解或合并函数来改进软件结构。
(4)考查函数的规模,过大的要进行分解。
(5)降低函数间接口的复杂度。
(6)不同层次的函数调用要有较合理的扇入、扇出。
(7)函数功能应可预测。
(8)提高函数内聚。(单一功能的函数内聚最高)
说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。
第三章 代码管理
尽量少(不)用浮点型数据运算
说明:相机多采用定点型数据处理芯片,浮点型数据处理能力相对较弱,若确实需要使用浮点型数据,须注明数据范围和数据精度
程序运算尽量简单,控制逻辑尽量简化
说明:建议采用减,乘,移位,与,或,非等运算;避免使用的如除法,开方,随机函 数,cos/sin/log 等操作,必要情况可用移位,查表或其他数值计算的方法替代。建议避免循环中间嵌套复杂 if~else~, 递归等语句,尽量保证嵌套的条件语句或循环语句在三层以内;避免多重循环,尽量使用简单语句。
注意运算符的优先级
说明:防止阅读程序时产生误解,防止因默认的优先级与设计思想不符而导致程序出错。 建议用括号明确表达式的操作顺序,避免使用默认优先级。
示例:下列语句中的表达式
word = (high << 8) | low (1)
if ((a | b) && (a & c)) (2)
if ((a | b) < (c & d)) (3)
如果书写为:
high << 8 | low
a | b && a & c
a | b < c & d
由于
high << 8 | low = ( high << 8) | low,
a | b && a & c = (a | b) && (a & c),
(1)(2)不会出错,但语句不易理解;
a | b < c & d = a | (b < c) & d,(3)造成了判断条件出错。
用枚举或宏来替代不易理解的数字
示例:如下的程序可读性差。 if (Trunk[index].trunk_state == 0)
{
Trunk[index].trunk_state = 1;
... // program code
}
应改为如下形式。
#define TRUNK_IDLE 0
#define TRUNK_BUSY 1
if (Trunk[index].trunk_state == TRUNK_IDLE)
{
Trunk[index].trunk_state = TRUNK_BUSY;
... // program code
}
不要使用难懂的技巧性很高的语句
说明:高技巧语句不等于高效率的程序,实际上程序的效率关键在于算法。
示例:如下表达式,考虑不周就可能出问题,也较难理解。
* stat_poi ++ += 1;
* ++ stat_poi += 1;
应分别改为如下:
*stat_poi += 1;
stat_poi++; // 此二语句功能相当于“ * stat_poi ++ += 1; ”
++ stat_poi;
*stat_poi += 1; // 此二语句功能相当于“ * ++ stat_poi += 1; ”
仔细设计结构中元素的布局与排列顺序
说明:合理排列结构中元素顺序,可节省空间并增加可理解性,而且这样会使结构容易 理解、节省占用空间,并减少引起误用现象
示例:如下结构中的位域排列,将占较大空间,可读性也稍差。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
PERSON person;
unsigned int set_flg: 1;
} EXAMPLE;
若改成如下形式,不仅可节省 1 字节空间,可读性也变好了。
typedef struct EXAMPLE_STRU
{
unsigned int valid: 1;
unsigned int set_flg: 1;
PERSON person ;
} EXAMPLE;
注意数据类型的强制转换
说明:因为数据类型转换或多或少存在危险。除非必要,最好不要把与函数返回值类型不同的变量,以编译系统默认的转换方式或强制的转换方式作为返回值返回,在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转换。
充分利用宏定义,但注意宏的定义格式
说明:定义宏可以大大增加编程效率和程序的可读性,同时也大大方便今后代码的维护对较长变量且存在多次引用时,可以用宏代替。
例如:
1.某过程中较多引用TheReceiveBuffer[FirstSocket].byDataPtr,
则可以通过以下宏定义来代替:
# define pSOCKDATA TheReceiveBuffer[FirstScoket].byDataPtr
2. 算法中部分变量数值可能需要全局修改,定义宏可以避免多处变量进行修改。
3. 但在使用过程中,要注意宏的定义格式,因此系统不对参数进行必要的检测,仅进行简单的替换。
特别注意指针在函数调用中的强制
说明
void Fuc(char* pSrc,int* pDst,int n)
{
int i;
for(i=0;i<n;i++)
{
pDst[i]=pSrc[i];
}
}
Void main()
{
int Src[3]={1,2,3};
int Dst[3];
Fuc(Src,Dst,3);
}
第四章 风格管理
头文件必须加入预编译指令
说明:这样就会防止头文件的重复加载,建议统一采取以下头文件定义格式: #ifndef _FILE_MODELA_H_随机字串 #define _FILE_ MODELA _H_随机字串
…….
#endif //_FILE_ MODELA _H_随机字串
请在关键位置添加注释,注释内容要清楚、明了
说明:注释的原则是有助于对程序的阅读理解,在该加的地方都加了,注释不宜太多也 不能太少,注释语言必须准确、易懂、简洁。
对外接口头文件必须具有详细的说明
示例:头部应进行注释,注释必须列出:版权说明、版本号、生成日期、作者、内容、 功能、与其它文件的关系、修改日志等,头文件的注释中还应有函数功能简要说明。下面这段头文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。
/*************************************************
Copyright (C), 1988-1999, QianYi Tech. Co., Ltd.
File name: // 文件名
Description: // 用于详细说明此程序文件完成的主要功能,与其他模块
//函数的接口,输出值、取值范围、含义及参数间的控
//顺序、独立或依赖等关系
Version: //版本号
Date: //完成日期
*************************************************/
子模块的头文件应添加必要的注释和说明
示例:头文件需注明版权说明、版本号、生成日期、作者、模块目的/功能、主要函数及其功能、修改日志等。下面这段源文件的头注释比较标准,当然,并不局限于此格式,但上述信息建议要包含在内。函数头部应进行注释,列出:函数的目的/功能、输入参数、输出参数、返回值、调用关系 (函数、表)等。
/**********************************************************
Copyright (C), 1988-1999, Unihz Tech. Co., Ltd.
FileName: // 文件名
Description: // 模块描述
Version: // 版本信息
History: // 历史修改记录
<author> <time> <version > <desc>
David 96/10/12 1.0 build this moudle
**********************************************************/
说明:Description 描述本模块的功能。History 是修改历史记录列表,每条修改记录应包括修改日期、修改者及修改内容简述。
内部子模块函数必须添加函数头
/*************************************************
Function: // 函数名称
Description: // 函数功能、性能等的描述
Input: // 输入参数说明,包括每个参数的作用、取值说明及参数间关系
Output: // 对输出参数的说明
Return: // 函数返回值的说明
Others: // 其它说明
*************************************************/
模块分级及函数命名方式
模块一般情况下分为若干层,每层的函数命名与其对应的模块层相关。具体事例如下:
整个模块分为:接口层,消息处理层,
接口层:模块对外提供的api ,如:模块初始化,模块主函数,模块反初始化
消息处理层:模块接收消息内部处理api
//模块初始化函数
/**********************************************************************
* 函数名称:DevMaintainInit
* 功能描述:设备维护模块初始化
* 输入参数:pParam -模块信息结构指针
* 输出参数:无
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
***********************************************************************/
E_StateCode DevMaintainInit(void *pParam);
//模块主函数
/**********************************************************************
* 函数名称:DevMaintainDestroy
* 功能描述:设备维护模块销毁
* 输入参数:pParam -模块信息结构指针
* 输出参数:无
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
***********************************************************************/
E_StateCode DevMaintainFxn(void *pParam);
//模块反初始化函数
/**********************************************************************
* 函数名称:DevMaintainDestroy
* 功能描述:设备维护模块销毁
* 输入参数:pParam -模块信息结构指针
* 输出参数:pParam -模块信息结构指针
* 返 回 值: 状态码
* 其它说明:
* 修改日期 版本号 修改人 修改内容
* -----------------------------------------------
***********************************************************************/
E_StateCode DevMaintainDestroy(void *pParam);
变量及结构体命名
变量命名采用匈牙利规则,具体如下:
UINT8* pSrc; //指针前缀 p
UINT8 ucSrcData;
INT16 wSrcData;
UINT16 uwSrcData;
INT32 nSrcData;
UINT32 udwSrcData;
FLOAT fSrcData;
BOOL bFirst;
各模块结构体命名必须前缀模块名称
标识符的命名要清晰、明了,有明确含义
说明:较短的单词可通过去掉“元音”形成缩写;较长的单词可取单词的头几个字母形成缩写;一些单词有大家公认的缩写。
示例:如下单词的缩写能够被大家基本认可。使用完整的单词或大家基本可以理解的缩 写,避免使人产生误解
temp 可缩写为 tmp ;
flag 可缩写为 flg ;
statistic 可缩写为 stat ;
increment 可缩写为 inc ;
message 可缩写为 msg ;
用具有互斥意义或相反动作的名字定义变量和函数
说明:下面是一些在软件中常用的反义词组。
add / remove begin / end create / destroy
insert / delete first / last g et / release
increment / decrement put / get
add / delete lock / unlock open / close
min / max old / new start / stop
next / previous source / target show / hide
send / receive source / destination
cut / paste up / down
示例:
int min_sum;
int max_sum;
int add_user( BYTE *user_name );
int delete_user( BYTE *user_name );
函数体代码长度不能超过300 行
说明:过长代码请根据程序功能模块的设计进行函数肢解。不包括注释和空格行。
函数不能超过5 层,参量个数不能超过6 个
说明:目的减少函数间接口的复杂度。函数调用层数越少越好,层数过多,导致函数堆 栈的压力很大,易发生堆栈溢出。避免设计多参数函数,不使用的参数从接口中去掉,建议以 2-4 为最佳。
程序块要采用缩进风格编写,缩进的空格数为 4 个
说明:对于由开发工具自动生成的代码可以有不一致。另外,保持适度的空行。
较长的语句(>80 字符)要分成多行书写
说明:长表达式要在低优先级操作符处划分新行,操作符放在新行之首,划分出的新行 要进行适当的缩进,使排版整齐,语句可读。
示例:
perm_count_msg.head.len = NO7_TO_STAT_PERM_COUNT_LEN
+ STAT_SIZE_PER_FRAM * sizeof( _UL );
act_task_table[frame_id * STAT_TASK_CHECK_NUMBER +
index].occupied
= stat_poi[index].occupied;
act_task_table[taskno].duration_true_or_false
= SYS_get_sccp_statistic_state( stat_item );
report_or_not_flag = ((taskno < MAX_ACT_TASK_NUMBER)
&& (n7stat_stat_item_valid (stat_item))
&& (act_task_table[taskno].result_data != 0));
一行只写一条语句
示例:如下例子不符合规范。
rect.length = 0; rect.width = 0;
IVS_parm *pA,pB,pC;
应如下书写
rect.length = 0;
rect.width = 0;
IVS_parm *pA;
IVS_parm *pB;
IVS_parm *pC;
注意部分语句的排版
说明:if、for、do、while、case、switch、default 等语句自占一行,且 if、for、do、while 等语句的执行语句部分无论多少都要加括号{}。 示例:如下例子不符合规范。
if (pUserCR == NULL) return;
应如下书写:
if (pUserCR == NULL)
{
return;
}
谨慎使用64 位数据处理类型和方式
说明:在 PC 环境下存在_int64 而在 嵌入式里需定义 long long 类型,一般建议在 PC 开发环境避免使用 64 为数据操作,除非必要。另外,对于 64 位数据赋值一定要保证数据的起始地址在 64 位数据对齐的位置。
例如:采用如下格式进行 64 位数据赋值,由于 h->mb.cache.mv 的数据指针不能保证 64 位数据对齐,导致数据赋值存在问题。
*(uint64_t*)h->mb.mv[0][i_mb_4x4+y*s4x4+0] =
*(uint64_t*)h->mb.cache.mv[0][x264_scan8[0]+8*y+0];
因此,对于以上问题,解决途径有两个:第一,保证指针地址的 64 位数据对齐;第二, 逐个字节对数据进行赋值。
注: float 与 double 的区别,建议均采用float 类型。
局部变量/指针在定义的同时应当初始化
说明:对于局部变量/指针建议在定义时同时完成初始化,防止使用未初始化的变量/ 指针出现在赋值语句的右边。特别对于 PC 的静态 static 变量或常量在程序的运行时自动初始化为 0,而在嵌入式环境下系统没有进行初始化,为避免这方面导致的差异,要求数据定义时必须同时完成初始化工作。
第五章效率管理
改进数据结构的划分和组织,提高空间效率
说明:这种方式是解决软件空间效率的根本办法。
示例:如下记录学生学习成绩的结构不合理。
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef struct STUDENT_SCORE_STRU
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
BYTE subject;
float score;
} STUDENT_SCORE;
因为每位学生都有多科学习成绩,故如上结构将占用较大空间。应如下改进 (分为两个结构),总的存贮空间将变小,操作也变得更方便。
typedef struct STUDENT_STRU
{
BYTE name[8];
BYTE age;
BYTE sex;
BYTE class;
} STUDENT;
typedef struct STUDENT_SCORE_STRU
{
WORD student_index;
BYTE subject;
float score;
} STUDENT_SCORE;
循环体内工作量最小化
说明:应仔细考虑循环体内的语句是否可以放在循环体之外,使循环体内工作量最小,从而提高程序的时间效率。
示例:如下代码效率不高。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
back_sum = sum; /* backup sum */
}
语句“back_sum = sum;”完全可以放在 for 语句之后,如下。
for (ind = 0; ind < MAX_ADD_NUMBER; ind++)
{
sum += ind;
}
back_sum = sum; /* backup sum */
在多重循环中,应将最忙的循环放在最内层,
说明:减少 CPU 切入循环层的次数。
示例:如下代码效率不高。
for (row = 0; row < 100; row++)
{
for (col = 0; col < 5; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col < 5; col++)
{
for (row = 0; row < 100; row++)
{
sum += a[row][col];
}
}
对于多维数组的循环遍历建议“先行后列”的顺序
说明:对于多维数组来说,“先行后列”的遍历效率肯定好于 “先列后行”遍历,不论 其行数远大于列数还是情况相反甚至接近,即使在最坏的情况下也至少与 “先列后行” 遍历的效率相当。影响效率的实际上主要是大型数组导致的内存页面交换次数以及Cache 的命中率的高低,而不是循环次数本身。
示例:如下代码效率不高。
int array[1024][512]
for (row = 0; row < 1024; row++)
{
for (col = 0; col <512; col++)
{
sum += a[row][col];
}
}
可以改为如下方式,以提高效率。
for (col = 0; col <512; col++)
{
for (row = 0; row < 1024; row++)
{
sum += a[row][col];
}
}
尽量减少循环嵌套层次,避免循环体内含判断语句
说明:目的是减少判断次数。循环体中的判断语句是否可以移到循环体外,要视程序的具体情况而言,一般情况,与循环变量无关的判断语句可以移到循环体外,而有关的则不可以,应将循环语句置于判断语句的代码块之中。 示例:如下代码效率稍低。
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
if (data_type == RECT_AREA)
{
area_sum += rect_area[ind];
}
else
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
因为判断语句与循环变量无关,故可如下改进,以减少判断次数。
if (data_type == RECT_AREA)
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
area_sum += rect_area[ind];
}
}
else
{
for (ind = 0; ind < MAX_RECT_NUMBER; ind++)
{
rect_length_sum += rect[ind].length;
rect_width_sum += rect[ind].width;
}
}
尽量用乘法或其它方法代替除法
说明:浮点运算除法要占用较多 CPU 资源。
示例:如下表达式运算可能要占较多 CPU 资源。
#define PAI 3.1416
radius = circle_length / (2 * PAI);
应如下把浮点除法改为浮点乘法。
#define PAI_RECIPROCAL (1 / 3.1416 ) // 编译器编译时,将生成具体浮点数
radius = circle_length * PAI_RECIPROCAL / 2;
第六章 BUG 管理管理
防止内存操作越界
说明:内存操作主要是指对数组、指针、内存地址等的操作。内存操作越界是软件系统主要错误之一,后果往往非常严重,所以当我们进行这些操作时一定要仔细小心。 示例:假设某软件系统最多可由 10 个用户同时使用,用户号为 1-10,那么如下程序存在问题。
#define MAX_USR_NUM 10
unsigned char usr_login_flg[MAX_USR_NUM]= "";
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no])
{
usr_login_flg[usr_no]= TRUE;
}
}
当 usr_no 为 10 时,将使用 usr_login_flg 越界。可采用如下方式解决。
void set_usr_login_flg( unsigned char usr_no )
{
if (!usr_login_flg[usr_no - 1])
{
usr_login_flg[usr_no - 1]= TRUE;
}
}
务必增加严重的异常处理机制
说明:在程序中内存处理和函数返回值的位置处增加数据异常和出错的异常处理机制能 有效跟踪 BUG 的位置。
系统运行之初,要对数据进行一致性检查
说明:使用不一致的数据,容易使系统进入混乱状态和不可知状态。系统运行之初,要 初始化有关变量及运行环境,防止未经初始化的变量被引用,系统运行之初,要对加载到系统中的数据进行一致性检查
编程时,要防止差1 错误
说明:此类错误一般是由于把“<=”误写成“<”或“>=”误写成“>”等造成的,由此引起的后果,很多情况下是很严重的,所以编程时,一定要在这些地方小心。当编完程序后, 应对这些操作符进行彻底检查。
要时刻注意易混淆的操作符
说明:形式相近的操作符最容易引起误用,如 C/C++中的“=”与“==”、“|”与“||”、“&”与“&&”等,若拼写错了,编译器不一定能够检查出来。当编完程序后,应从头至尾检查一遍这些操作符,以防止拼写错误。 示例:如把“&”写成“&&”,或反之。
ret_flg = (pmsg->ret_flg & RETURN_MASK);
被写为:
ret_flg = (pmsg->ret_flg && RETURN_MASK);
rpt_flg = (VALID_TASK_NO( taskno ) && DATA_NOT_ZERO( stat_data ));
被写为:
rpt_flg = (VALID_TASK_NO( taskno ) & DATA_NOT_ZERO( stat_data ));
保留switch 语句下的 default 分支和 break 语句
说明:可能的话,if语句尽量加上 else 分支,对没有else 分支的语句要小心对待;switch 语句必须有 default 分支,对于每一个 case 下面均有一个 break 语句。
时刻注意表达式是否会上溢、下溢
示例:如下程序将造成变量下溢。
unsigned char size ;
while (size-- >= 0) // 将出现下溢
{
... // program code
}
当 size 等于 0 时,再减 1 不会小于 0,而是 0xFF,故程序是一个死循环。应如下修改。
char size; // 从 unsigned char 改为 char
while (size-- >= 0)
{
... // program code
}
使用变量时要注意其边界值的情况
示例:如 C 语言中字符型变量,有效值范围为-128 到 127。故以下表达式的计算存在一定风险。 char chr = 127;
int sum = 200; chr += 1; // 127 为 chr 的边界值,再加 1 将使 chr 上溢到-128,而不是 128。
sum += chr; // 故 sum 的结果不是 328,而是 72。
若 chr 与 sum 为同一种类型,或表达式按如下方式书写,可能会好些。
sum = sum + chr + 1;
本文档详细阐述了软件接口API的规范,包括内存管理、函数管理、代码管理和风格管理四大方面。强调内存分配要避免动态分配,保证4字节对齐,限制全局变量的使用,以及在函数中禁止大型数组和结构体。函数管理中提到添加断言语句,限制函数扇出,确保输入参数有效性。代码管理方面,提倡使用枚举和宏,避免递归调用和复杂的运算。风格管理则规定了头文件预编译指令、注释规范、变量命名规则等。此外,还强调了效率管理和BUG管理,如减少循环嵌套、避免除法运算、添加异常处理机制等。
6266

被折叠的 条评论
为什么被折叠?



