嵌入式系统程序可移植性设计及性能优化之三
――――函数设计
Sailor_forever sailing_9806@163.com 转载请注明
http://blog.csdn.net/sailor_8318/archive/
【摘要】本章介绍了函数设计的一些基本规则。合理对各种参数进行封装,不但有利于模块的交互,更能够减少参数提高函数调用性能。其次介绍了模块划分的原则,如何减少模块间的耦合度。接着分析了宏函数中参数的基本规则、多宏语句的基本规则、宏和内联的区别以及如何防止宏参数宏语句扩展后的异常效应。最后介绍了如何利用const来进行输入参数的修饰及如何提高程序的可靠性。
【关键词】嵌入式,可移植性,函数设计,模块划分,耦合度,内聚性,内联,宏参数,const,输入参数,可靠性
1 函数设计
1.1 避免过多函数参数,提高调用性能
在函数设计时通常需要传递参数,为了提供函数调用的性能,某些处理器如ARM会利用寄存器来传递参数,这样不需要访问内存,其访问效率更高。但寄存器传递的参数数目有限,对于ARM体系是四个,当多于四个时,剩余的参数需要用栈传递,参数的出栈入栈都需要时间,调用性能下降。当参数之间紧密相连且通常需要在多个模块中联合使用时,应对参数进行封装,这样便于参数的传递和变量的管理。
如:SYS_STATUS DCCM_SetSfnTsn(u16 u16SfnPeriod, u16 u16TsnPeriod, u16 u16Sfn, u16 u16Tsn, u32 u32TodPart1, u32 u32TodPart2)
函数六个参数,对这些参数的解析在函数内部又要定义六个变量与之对应,这给变量的定义个管理带来了不便,如下:
u16 g_u16DCCMSfn;
u
u
u
STRU_DD_TOD_INFO g_struDCCMTod;
因此应将相关联的变量封装为结构体,优势在于:
a) 便于管理、定义、声明,避免零散的变量;
b) 意义明确,结构清晰;
c) 函数调用时避免传递过多参数,提高函数调用性能,参数少不易出错;
不足在于对于结构体的访问效率不如单独的变量,但此性能影响很小;为了代码更好的可读性、可移植可维护性性和可靠性,此处结构体的形式更合适。
MAT、CCM及MCP都需要用到此类信息,因此应单独提炼出结构体便于各模块的交互;模块间进行通信时要遵循一定的协议,即数据交互时要按照一定的数据格式进行传输,为防止各模块对格式的认识不统一,最好的方式是提供统一的数据结构的定义来进行数据收发
BPP端封装为STRU_BPP_TOD_SFN_TSN_PARAM
typedef struct tag_STRU_BPP_TOD_SFN_TSN_PARAM
{
STRU_DD_TOD_INFO struTod;
u16 u16SfnPeriod;
u16 u16TsnPeriod;
u16 u16Sfn;
u16 u16Tsn;
} STRU_BPP_TOD_SFN_TSN_PARAM;
而MCP端对此结构的定义为
typedef struct tag_STRU_DD_MCPBPP_TODTSNSFN_INFO
{
u32 u32TodPart1;
u32 u32TodPart2;
u16 u16SfnPrd;
u16 u16TsnPrd;
u16 u16SfnNum;
u16 u16TsnNum;
} STRU_DD_MCPBPP_TODTSNSFN_INFO;
可以发现,原来BPP和MCP的MAT模块对时隙、子帧、TOD信息的处理并没有采取统一的方式,这样在交互时容易出现问题。并且二者分别定义,有重复劳动。
1.2 合理设计模块,减小耦合度
软件模块设计时需要合理划分模块的层次结构,提高内聚性,降低耦合度,引用全局变量会增强模块之间的耦合度。
如:
DCCM模块定义了g_u16DCCMTsn、g_u16DCCMSfn等全局变量,调用DBSP模块实现的时隙回调函数DBSP_TslotCb();
DBSP模块中DBSP_TslotCb()声明外部变量,通过全局变量引用g_u16DCCMTsn、g_u16DCCMSfn等变量,并且在DBSP_TslotCb()内部只是读取了g_u16DCCMTsn、g_u16DCCMSfn的值。
因此更改函数形式,传递参数,而非引用全局变量;不更改参数值,采用值传递
void DBSP_TslotCb (u16 u16Sfn, u16 u16Tsn)
若DBSP_TslotCb不是由定义g_u16DCCMTsn、g_u16DCCMSfn等变量的DCCM模块调用,则必须声明外部变量然后引用,这种耦合是无法避免的。
另一个典型例子是资源的申请和释放。在具备操作系统的嵌入式系统中,因为嵌入式系统的内存空间往往是十分有限的,定义过多的全局变量将导致系统内存大量减少,因为全局变量在程序的整个运行期间都占据着同块内存;另外栈空间也是有限的,若在函数内部定义大容量的数据结构时,很可能导致栈溢出。上述两种情况都需要动态内存申请释放来解决,但不经意的内存泄露会很快导致系统的崩溃。
所以一定要保证你的malloc 和free 成对出现,如果你写出这样的一段程序:
u8 * function(void) { u8 *p; p = (u8 *)malloc(…); if(p==NULL) …; … /* 一系列针对p 的操作 */ return p; } |
在某处调用function() ,用完function 中动态申请的内存后将其free ,如下:
u8 *q = function(); … free(q); |
上述代码明显是不合理的,因为违反了malloc 和free 成对出现的原则,即"谁申请,就由谁释放"原则。不满足这个原则,会导致代码的耦合度增大,因为用户在调用function 函数时需要知道其内部细节!
正确的做法是在调用处申请内存,并传入function 函数,如下:
u8 *p=malloc(…); if(p==NULL) …; function(p); … free(p); p=NULL; |
而函数function 则接收参数p,如下:
void function(u8 *p) { … /* 一系列针对p 的操作 */ } |
另外一些公用处理模块,为了满足各种不同的调用需要,往往在内部采用了大量的if-then-else结构,这样很不好,判断语句如果太复杂,会消耗大量的时间的,应该尽量减少公用代码块的使用,避免控制耦合。
1.3 用宏函数提高时间效率
在嵌入(inline)操作符变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的实时性能,嵌入代码经常是必须的方法。但是使用这种方法在优化程序速度的同时,程序长度变大了,因此需要更多的ROM空间。使用这种优化在宏函数频繁调用并且只包含几行代码的时候,对提高运行效率是最有效的。
函数的调用必须要将程序执行的顺序转移到函数所存放在内存中的某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去执行前要保存现场并记忆执行的地址,转回后要恢复现场,并按原来保存地址继续执行。同时函数调用是要使用系统的栈来保存数据的。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。
而宏函数不存在这个问题,其只是在预处理的地方将预先写好的代码嵌入到当前程序,不会产生函数调用,所以仅仅是占用了空间,不需要额外时间方面的开销。所以调用一个宏比调用一个函数更有时间效率,在频繁调用同一个宏函数的时候,该现象尤其突出。
1.3.1 宏参数的基本规则
a) 在引用宏参数时必须括起来,如参数为A,则引用时(A);
b) 若是宏表达式,则整个表达式必须括起来,如((A)+3);
采用上述规则定义一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个,如下:
#define MIN(A,B) ((A)<= (B) ? (A) : (B))
参数和表达式都必须括起来是为了防止在宏参数替换后由于运算符优先级导致的问题,因为宏参数本身可以是表达式,而且当宏在表达式中展开时,你不知道表达式里还有没级别更高的运算
1.3.2 宏语句的基本规则
a) 对于多个语句一起构成的宏函数,必须保证在宏函数替换后其为一个完整的执行序列,即任何时候都是全部顺序执行完毕。
b) 宏语句的最后一句没有语句结束符“;”,而是在引用宏函数时添加。
如:
#define MacroA if(conditon); / Sentence1 #define MacroB sentence2; / Sentence3 |
|