《函数可重入性及编写规范描述》由会员分享,可在线阅读,更多相关《函数可重入性及编写规范描述(9页珍藏版)》请在金锄头文库上搜索。
1、函数可重入性及编写规范函数可重入性及编写规范一、可重入函数1)什么是可重入性?可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反, 不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用本地变量,要么在使用全局变量时保护自己的数据。2)可重入函数:不为连续的调用持有静态数据。 不返回指向静态数据的指针;所有数据都由函数的调用者提供。 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。如果必
2、须访问全局变量,记住利用互斥信号量来保护全局变量。绝不调用任何不可重入函数。3)不可重入函数:函数中使用了静态变量,无论是全局静态变量还是局部静态变量。 函数返回静态变量。 函数中调用了不可重入函数。函数体内使用了静态的数据结构;函数体内调用了 malloc()或者 free()函数;函数体内调用了其他标准 I/O 函数。函数是 singleton 中的成员函数而且使用了不使用线程独立存储的成员变量 。总的来说,如果一个函数在重入条件下使用了未受保护的共享的资源,那么它是不可重入的。4)示例在多线程条件下,函数应当是线程安全的,进一步,更强的条件是可重入的。可重入函数保证了在多线程条件下,函数
3、的状态不会出现错误。以下分别是一个不可重入和可重入函数的示例:/c codestatic int tmp;void func1(int* x, int* y) tmp=*x; *x=*y; *y=tmp;void func2(int* x, int* y) int tmp; tmp=*x; *x=*y; *y=tmp;func1 是不可重入的,func2 是可重入的。因为在多线程条件下,操作系统会在 func1 还没有执行完的情况下,切换到另一个线程中,那个线程可能再次调用 func1,这样状态就错了。二、函数编写规范1 :对所调用函数的错误返回码要仔细、全面地处理 2 :明确函数功能,精确(
4、而不是近似)地实现函数设计 3 :编写可重入函数时,应注意局部变量的使用(如编写 C/C+ 语言的可重入函数时,应使用 auto 即缺省态局部变量或寄存器变量) 说明:编写 C/C+语言的可重入函数时,不应使用 static 局部变量,否则必须经过特殊处理,才能使函数具有可重入性。4 :编写可重入函数时,若使用全局变量,则应通过关中断、信号量(即 P 、V 操作)等手段对其加以保护 说明:若对所使用的全局变量不加以保护,则此函数就不具有可重入性,即当多个进程调用此函数时,很有可能使有关全局变量变为不可知状态。 示例:假设 Exam 是 int 型全局变量,函数 Squre_Exam 返回 Ex
5、am 平方值。那么如下函数不具有可重入性。unsigned int example( int para )unsigned int temp;Exam = para; / (*)temp = Square_Exam( );return temp;此函数若被多个进程调用的话,其结果可能是未知的,因为当(*)语句刚执行完后,另外一个使用本函数的进程可能正好被激活,那么当新激活的进程执行到此函数时,将使 Exam 赋与另一个不同的 para 值,所以当控制重新回到“temp = Square_Exam( )”后,计算出的temp 很可能不是预想中的结果。此函数应如下改进。unsigned int e
6、xample( int para )unsigned int temp;申请信号量操作 / 若申请不到“信号量”,说明另外的进程正处于Exam = para; / 给 Exam 赋值并计算其平方过程中(即正在使用此temp = Square_Exam( ); / 信号),本进程必须等待其释放信号后,才可继释放信号量操作 / 续执行。若申请到信号,则可继续执行,但其/ 它进程必须等待本进程释放信号量后,才能再使/ 用本信号。return temp;5 :在同一项目组应明确规定对接口函数参数的合法性检查应由函数的调用者负责还是由接口函数本身负责,缺省是由函数调用者负责 说明:对于模块间接口函数的参
7、数的合法性检查这一问题,往往有两个极端现象,即:要么是调用者和被调用者对参数均不作合法性检查,结果就遗漏了合法性检查这一必要的处理过程,造成问题隐患;要么就是调用者和被调用者均对参数进行合法性检查,这种情况虽不会造成问题,但产生了冗余代码,降低了效率。 6 :防止将函数的参数作为工作变量 说明:将函数的参数作为工作变量,有可能错误地改变参数内容,所以很危险。对必须改变的参数,最好先用局部变量代之,最后再将该局部变量的内容赋给该参数。 示例:如下函数的实现就不太好。 void sum_data( unsigned int num, int *data, int *sum )unsigned in
8、t count;*sum = 0;for (count = 0; count b ) ? a : b ;改为如下就很清晰了。 int max (int a, int b)return (a b) ? a : b);value = max (a, b);或改为如下。 #define MAX (a, b) (a) (b) ? (a) : (b)value = MAX (a, b);10:不要设计多用途面面俱到的函数 说明:多功能集于一身的函数,很可能使函数的理解、测试、维护等变得困难。 11:函数的功能应该是可以预测的,也就是只要输入数据相同就应产生同样的输出 说明:带有内部“存储器”的函数的功能
9、可能是不可预测的,因为它的输出可能取决于内部存储器(如某标记)的状态。这样的函数既不易于理解又不利于测试和维护。在 C/C+语言中,函数的 static 局部变量是函数的内部存储器,有可能使函数的功能不可预测,然而,当某函数的返回值为指针类型时,则必须是 STATIC 的局部变量的地址作为返回值,若为 AUTO 类,则返回为错针。示例:如下函数,其返回值(即功能)是不可预测的。 unsigned int integer_sum( unsigned int base )unsigned int index;static unsigned int sum = 0; / 注意,是 static 类型
10、的。/ 若改为 auto 类型,则函数即变为可预测。for (index = 1; index B-C-A),影响程序的可理解性;递归调用一般都占用较多的系统资源(如栈空间);递归调用对程序的测试有一定影响。故除非为某些算法或功能的实现方便,应减少没必要的递归调用。30 :仔细分析模块的功能及性能需求,并进一步细分,同时若有必要画出有关数据流图,据此来进行模块的函数划分与组织 说明:函数的划分与组织是模块的实现过程中很关键的步骤,如何划分出合理的函数结构,关系到模块的最终效率和可维护性、可测性等。根据模块的功能图或/及数据流图映射出函数结构是常用方法之一。31 :改进模块中函数的结构,降低函数
11、间的耦合度,并提高函数的独立性以及代码可读性、效率和可维护性 优化函数结构时,要遵守以下原则: (1)不能影响模块功能的实现。(2)仔细考查模块或函数出错处理及模块的性能要求并进行完善。(3)通过分解或合并函数来改进软件结构。(4)考查函数的规模,过大的要进行分解。(5)降低函数间接口的复杂度。(6)不同层次的函数调用要有较合理的扇入、扇出。(7)函数功能应可预测。(8)提高函数内聚。(单一功能的函数内聚最高)说明:对初步划分后的函数结构应进行改进、优化,使之更为合理。 32 :在多任务操作系统的环境下编程,要注意函数可重入性的构造 说明:可重入性是指函数可以被多个任务进程调用。在多任务操作系
12、统中,函数是否具有可重入性是非常重要的,因为这是多个进程可以共用此函数的必要条件。另外,编译器是否提供可重入函数库,与它所服务的操作系统有关,只有操作系统是多任务时,编译器才有可能提供可重入函数库。如 DOS 下 BC 和 MSC 等就不具备可重入函数库,因为 DOS 是单用户单任务操作系统。33 :避免使用 BOOL 参数 说明:原因有二,其一是 BOOL 参数值无意义,TURE/FALSE 的含义是非常模糊的,在调用时很难知道该参数到底传达的是什么意思;其二是 BOOL 参数值不利于扩充。还有 NULL 也是一个无意义的单词。34 : 对于提供了返回值的函数,在引用时最好使用其返回值 35 :当一个过程(函数)中对较长变量(一般是结构的成员)有较多引用时,可以用一个意义相当的宏代替 说明:这样可以增加编程效率和程序的可读性。 示例:在某过程中较多引用 TheReceiveBufferFirstSocket.byDataPtr,则可以通过以下宏定义来代替: # define pSOCKDATA TheReceiveBufferFirstScoket.byDataPtrTrackback: http:/