人人范文网 范文大全

C语言程序稳定性

发布时间:2020-03-03 01:25:06 来源:范文大全 收藏本文 下载本文 手机版

提高C语言程序运行稳定性的方法

一、前言

由于C语言的灵活性,用C语言开发出来的程序容易造成内存泄漏、运行异常、运行结果不可预期等程序质量问题,在用C语言开发程序的过程中,必须高度重视程序质量问题,应当把提高程序稳定性的方法加入到项目管理和开发过程中,最大限度地提高程序的稳定性,保证项目的成功开发。在这里总结多年来的C语言开发经验,拿出来共享以期在这方面能够得到更多的指教。

二、影响程序稳定性的因素

1、内存泄漏。造成内存泄漏的原因有:

1)、程序有多个出口,但不能保证在每一个出口能够完全释放掉所有的动态内存,如函数内有多个“return”,但没有在每一个“return”前释放掉在原已申请但必须释放的动态内存;

2)、对于“struct”数据结构,没有完全释放掉每一个指向动态内存的指针,如只释放指向“struct”数据结构指针没有释放“struct”体内的指针或某些指针被漏释放;

3)、对于用动态内存建立的链表在释放时没有一个一个结点去释放; 4)、一段动态内存空间原来只被一个指针引用,但在这个指针引用另外一段内存空间的时候,该段内存没有被释放;

5)、对于在函数内申请但必须在函数外释放的动态内存,在对该内存使用后忽略该动态内存的释放;

6)、用户强行退出程序,程序在退出前不能完全释放掉所有的动态内存; 7)、程序运行过程中发生了异常导致动态内存未被释放。

2、程序运行发生异常。造成异常产生的原因有:

1)、释放指针时该指针为空或是一个已被释放但释放后未被置空的指针;

2)、对于C库中的函数,如字符串操作函数,在调用该类函数时实参为空指针或者改指针没有指向可用的内存地址空间或者所指向的内存空间大小不足以用来实现当前的字符串操作;

3)、对于指向一个“struct”数据结构的指针,当指针为空时使用“struct”的分体数据;

4)、数组或指针发生越界操作;

5)、指针指向一个已被释放但释放后未被置空的指针,如一个全局变量的指针,在一个地方被释放后,但指针值未被置空,这时在另一个地方引用该指针的值时会发生异常;

6)、更改定义为常量的值;

7)、动态申请完一个内存后,未检查是否申请成功就调用了该指针;

8)、对于一块连续的内存块和“struct”数据结构在第一次使用时没有做初始化操作。

9)、在用非ASCII(如中文字符、Unicode)编码时,若使用char*来申请空间,在用C库中的字符串操作函数来操作,会因无法判断字符串结束位置而产生异常。

10)、指针类型强制转换时,当强制转换后指针指向的内存空间大于原来指针指向的内存空间时可能会出现异常(取决于堆或栈空间的结构和大小),如把“INT12*”强制转换成“INT32*”,应当尽量避免指针类型的强制转换;

11)、更改了数据结构,但代码没有相应更新或整个工程中相关文件没有做相应更新;

12)、申请的栈空间或堆空间超出了系统的容量限制;

13)、栈溢出,当函数中定义一个太大的数组时容易造成栈溢出,递归调用太深也容易造成栈举出;

14)、全局变量使用混乱,造成程序错乱;

16)、内存碎片太多,造成内存分配失败而导致程序异常,如建立一个太长的链表容易造成大量内存碎片;

17)、文件操作过于频繁(特别是写操作),系统应付不过来容易造成程序出现异常,这个在嵌入式系统中较常见。

三、内存泄漏预防措施

1、在代码审查时,检查函数体内的每一个“return”前是否有没有释放必须要释放的指针;

2、设计“struct”数据结构时,应当设计相应的释放“struct”指针的函数,并确保所有的“struct”体内的指针都被释放;

3、对于用动态内存建立的链表在释放时要一个一个结点去释放, 对于每一个链表也要有相应的链表内存管理函数,如链表的释放函数;

4、当一个指针变量要指向另一个动态内存地址时先检查一下该指针是否有指向另一个动态内存地址,如果有则应当考虑是否要先释放掉原先的指向的动态内存;

5、在调用一个函数时,对于函数的输出值要确认值的内存空间是否是在函数内部动态申请,如果是则应当考虑是适当的时候把它释放掉;

6、减少程序的出口的数目,最好是一个出口,在出口处理函数中确保释放所有的动态内存;

7、当用户强行退出时,要考虑在每一个退出点是否能够释放所有的动态内存;

8、释放掉一个指针所指的内存空间后,就立即把改指针置为空;

9、少用动态申请内存,能用数组代替的就用数组的形式;

10、尽量减少全局变量的使用,避免指针指向的混乱;

11、封装动态内存申请和释放的底层函数,便于检查内存泄漏问题;

12、把内存泄漏的检查方法放进设计代码中,便于发现内存泄漏。

四、程序运行异常预防措施

1、在释放指针前先检查指针是否为空;

2、当把指针作为参数传入C库函数中的参数时,先检查指针是否为空;

3、在函数体内,当要调用指针参数时,先判断该指针是否为空;

4、当要调用“struct”指针数据结构中的分体时要先判断该指针是否为空;

5、当做指针移动操作时要考虑指针是否会发生越界;

6、当一个函数体内可能会改变参数中的值时,要避免传入常量形式的值,在设计函数时要尽量避免试图去改变参数中的值;

7、动态申请完一个内存后要先检查是否申请成功;

8、对于一块连续的内存块和“struct”数据结构在第一次使用时要做初始化操作,如申请完内存后,记得用memset清空内存;

9、备案所有的全局变量,考虑全局变量对程序可能产生的影响,尽量少用全局变量。对于全局变量的定义最好使用“static”来申明,不让其它模块直接访问该全局变量,并且设计好相应的操作该全局变量的方法函数,在定义全局变量时要充分考虑好全局变量的初始化方法和程序结束时的处理方法,对于整个工程中的全局变量要进行登记管理,登记内容包括变量名、类型名、定义位置、使用范围、使用目的、初始化方法、程序结束时的处理方法及其它注意事项。

10、在用非ASCII(如中文字符、Unicode)编码时,要使用unsigned char*来申请空间,并记住申请空间大小,不要用C库中的字符串操作函数来操作。

11、记得申请足够的内存,比如,储存年份应该是5个空间而不是4个,记得保留‘\\0’的空间;

12、在函数中最好不要定义占用内存太大的局部变量,否则容易造成栈溢出,对于较大内存的使用最好是使用堆内存空间的方法。由于栈溢出这种情况比较不常见,容易被人忽视,所以在发生因栈溢出而产生问题时往往不容易被发现原因所在;

13、尽量不频繁分配小块的内存;

14、在设计递归调用时要考虑递归调用可能的深度,防止出现栈溢出;

15、不要定义太多的局部变量,如果要定义一个数组类型的局部变量,数组不要太长,以防止出现栈溢出;

16、减少读写文件的次数,优化文件的读写方法。

指针是C/C++的精华,也是最难的部分。本书中规中矩地讲解了指针的概念、定义与初始化、操作等。

指针的灵活性可以把大量的工作化繁为易,前提是必须首很把足够繁的指针弄懂。听起来有点像绕口令,事实就是这样,你现在把难懂的东西弄懂了,日后可以把难事化简,大事化小。

从VB过来的人一定会熟悉“值传递”和“地址传递”这两个概念,实际上,“地址传递”这种说法正是为了弥补VB没有指针却有类似的需要才发明的。我认为C/C++程序员要想深入理解指针,首先要抛弃这个概念。在C/C++程序中,即使在函数调用中传递指针,也不能说“地址传递”,还应该说是值传递,只不过这次传递的值有点特殊,特殊在于借用这个值,可以找到其它值。就好像我给你一把钥匙一样,你通过钥匙可以间接获得更多,但是我给你的只不过是钥匙。

我前阵子曾写过一篇关于指针的文章,之所以写那篇文章,是因为看到一大堆初学者在论坛上提问。通过对他们提的问题的分析,我总结了几点。下面,首先就先引用我自己写的《关于指针》中的片段吧(完整的文章请到我的个人主页查找):

一、指针就是变量:

虽然申明指针的时候也提类型,如:

char *p1;

int *p2;

float *p3;

double *p4;

.....

但是,这只表示该指针指向某类型的数据,而不表示该指针的类型。说白了,指针都是一个类型:四字节无符号整数(将来的64位系统中可能有变化)。

二、指针的加减运算很特殊:

p++、p--之类的运算并不是让p这个“四字节无符号整数”加一或减一,而是让它指向下一个或上一个存储单元,它实际加减的值就是它所指类型的值的size。

比如:

char *型指针,每次加减的改变量都是1;

float *型的指针,每次加减的改变量都是4;

void *型指针无法加减。

还要注意的是:指针不能相加,指针相减的差为int型。

正是因为指针有着不同于其它变量的运算方式,所以,在任何时候用到指针都必须明确“指针的类型”(即指针所指的变量的类型)。这就不难理解为什么函数声明时必须用“int abc(char *p)”而调用的时候却成了“a = abc(p);”这样的形式了。

三、用指针做参数传递的是指针值,不是指针本身:

要理解参数传递,首先必须把“形参”与“实参”弄明白。

函数A在调用函数B时,如果要传递一个参数C,实际是在函数B中重新建立一个变量C,并将函数A中的C值传入其中,于是函数B就可以使用这个值了,在函数B中,无论有没有修改这个C值,对于函数A中的C都没有影响。函数B结束时,会将所有内存收回,局部变量C被销毁,函数B对变量C所做的一切修改都将被抛弃。

以上示例中,函数A中的变量C称为“实参”,函数B中的变量C被称为“形参”,调用函数时,会在B函数体内建立一个形参,该形参的值与实参的值是相同的,但是形参的改变不影响实参,函数结束时,形参被销毁,实参依然没有发生变化。

指针也是一个变量,所以它也符合以上的规定,但是,指针存放的不仅仅是一个值,而是一个内存地址。B函数对这个地址进行了改动,改动的并不是形参,而是形参所指的内存。由于形参的值与实参的值完全相同,所以,实参所指的内存也被修改。函数结束时,虽然这个形参会被销毁,指针的变化无法影响实参,但此前对它所指的内存的修改会持续有效。所以,把指针作为参数可以在被调函数(B)中改变主调函数(A)中的变量,好像形参影响了实参一样。

注意:是“好像”。在这过程中,函数B影响的不是参数,而是内存。

下面再来看刚才的例子:“int abc(char *p)”和“a = abc(p);”。为什么申请中要用*号,因为函数必须知道这是指针;为什么调用时不加*号,因为传递的是“指针值”,而不是“指针所指内存的值”。

四、指向指针的指针:

正因为指针也是一个变量,它一样要尊守形参与实参的规定。所以,虽然指针做参数可以将函数内对变量的修改带到函数外,但是,函数体内对指针本身作任何修都将被丢弃。如果除了对变量的修改带到函数外,还要让指针本身被修改而且要影响函数外,那么,被调函数就应该知道“该指针所在的内存地址”。这时,指针不再是指针,而是“普通变量”。作为参数传递的不是这个“普通变量”,而是指向这个“普通变量”的指针。即“指向指针的指针”。

如果p是一个指向指针的指针,那么*p就是一个指针,我们不妨就把它看成q。要访问q指针所指的内存,只要*q就是了。用初中数学的“等量代换”一换就知道,*q就是**p。

五、指针数组。

之所以要把“指针数组”单独提出来,是因为数组本身就与指针有着千丝万缕的关系。即使你不想用指针,只要你使用了数组,实际就在与指针打交道了。

只要理解了指针本身就是变量,就不难理解“指针数组”,我们可以暂且把它当成普通数组来处理,a[0]、a[1]、a[2]……就是数组的元素,只是,a[0]是一个指针,a[1]、a[2]也是一个指针。那a呢?当然也是指针,但这是两码事。你可以完全无视a的存在,只去管a[0]等元素。*a[0]与*p没有什么本质的区别。

还有一个东西不得不提一下,它比较重要:

指针的定义有两个可取的方式,它们各有优缺点:“int *p;”和“int* p;”是完全等价的,后者的好处是让人体会到p是一个“指向int的”指针,前者会让人误解为*p是一个int型变量(这里没有定义int型变量);但是前者的好处是不会产生混淆,如“int *p, *q;”让人一眼就看出定义了两个指针,而“int* p,q;”会让人误解成定义了两个指针(实际上q不是指针)。

C是一个结构化语言,如谭老爷子所说:它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

所以C与C++的最大区别在于它们的用于解决问题的思想方法不一样。之所以说C++比C更先进,是因为“ 设计这个概念已经被融入到C++之中 ”,而就语言本身而言,在C中更多的是算法的概念。

c语言通讯录程序

C语言课程设计程序

C语言程序总结

红绿灯C语言程序

c语言实习程序

C语言程序教学新探

C语言程序:求平均数

C语言程序课程设计心得体会

c语言程序分类总结

dht11的c语言程序

C语言程序稳定性
《C语言程序稳定性.doc》
将本文的Word文档下载到电脑,方便编辑。
推荐度:
点击下载文档
点击下载本文文档