首页 > 范文大全 > 正文

职责分明控指针

开篇:润墨网以专业的文秘视角,为您筛选了一篇职责分明控指针范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

摘要:指针是C/C++语言中至关重要的概念,也是软件界备受争议和讨论的热点。提高指针的使用安全性对于软件的安全具有现实意义。指针是内存的直接管理者,指针做为函数参数在程序中传递导致内存不断拥有新的管理者,程序员弄清不同管理者的职责,进而操控指针控制内存,可提高程序的安全性。结合具体实例分析其有效性。

关键词:指针;安全;管理者;职责

中图分类号:TP311文献标识码:A文章编号:1009-3044(2011)10-2327-03

Control Pointer with Clear Duties

SONG Zhen-fang

(Academy of Information Technology, Luoyang Normal University, Luoyang 471022, China)

Abstract: The pointer is crucial concept in C/C++ language but also is controversial and debated in software. It has realistic meanings for software safety to improve the safety of using a pointer.The pointer is direct manager of memory and it is transferred in a program as function parameters causes memory constantly with new managers. Programmers cognizant of the different responsibilities of managers and manipulate pointers to control memory can improve the program’s bined with concrete example to analysis its effectiveness.

Key words: pointer; safety; manager; duty

指针是C/C++语言区别于其他高级语言的主要标志之一。指针的灵活性赋予程序员强大控制能力的同时也因其难以驾驭而带来软件安全的潜在隐患。知彼知己,百战不殆,欲驾驭指针首先要了解指针,弄清使用指针的目的,然后在控制指针的时候才能真正注意到一些细节问题,而细节常常是安全隐患之所在。对高可信软件需求的增加使得指针程序的验证成为近期的研究热点,不少研究者专注于如何对指针程序进行精确的分析,如文献[1-3]等对此问题进行了深入研究。本文从管理者职责的角度谈指针的控制,由于指针涉及到的概念比较多,需要学习和探讨的内容丰富,在内容上侧重于分析通过指针申请内存和释放内存的问题。

首先介绍指针概念、强调管理理念和提出管理者职责概念,然后结合几个应用背景不同的案例分析如何理清管理者职责,达到明明白白控制指针的目的。

1 认识指针

1.1 指针相关概念

把数据比作演员,内存比作演员表演的舞台,则程序员就相当于导演。导演指挥演员上下舞台需要知道演员的名字,数据的名字就是变量名,变量名实际上传递了内存地址信息,内存地址又被称作变量地址(即指针),存放指针的变量称为指针变量。

程序员是程序的导演,是程序的最高管理者,而指针是内存单元空间的直接控制者。对于程序员来讲,无论是采用结构化方法或面向对象方法进行程序的分析与设计,应该意识到指针做为参数进行传递导致了内存单元的控制者不断增加,究竟谁应该对内存单元空间的申请和释放负责,是指针应用安全性的关键。许多程序员就是因为没有搞清楚这一点,在程序代码编写过程中埋下了许多指针隐患。所以,结构化编程要弄清模块的职责,面向对象编程要弄清类对象的职责,职责分明就可以做到对指针的精确控制,最大程度地避免种种使用指针带来的问题。

1.2 指针常见问题

1) 指针在使用前必须进行初始化

好的习惯是声明一个指针的同时为其赋值NULL,如int *ptr=NULL,否则系统会让指针指向一个随机的内存单元,如果该地址正被系统使用着,程序员冒然去使用该指针做操作就会带来不可预知的错误。

2) 内存泄露

程序员使用指针在堆上创建动态一维或多维动态数组[4],程序结束时忘记释放堆上的内存空间,或者因为指针转而指向其他内存空间导致已被申请的内存单元失去了管理者,均会造成内存泄露。

3) 迷途指针

迷途指针也叫野指针或者悬浮指针。迷途指针的出现常常是因为将delete用于该指针后,删除了它所指的内存但没有将它设置为空引发的。如果程序员随后没有重新对其赋值的情况下使用该指针也会引发错误。

4) 重复delete问题

使用指针控制内存单元时经常会出现多个指针指向同一块内存单元,即拥有多个管理者的情况,如果该内存单元已经被某个管理者释放,其它管理者还要再次去释放就会出现重复delete问题。

一个简单的例子如下所示。

class A{};

void main()

{

A *ptr1=new A;

A *ptr2=ptr1;

delete ptr1;

delete ptr2;

}

上述代码中ptr1是堆上内存单元的管理者,ptr2也是它的管理者,将delete应用于ptr1实现了堆上内存的释放,再将delete应用于ptr2时就导致了重复delete问题的产生。

5) 指针传递的错觉

程序员定义了一个指针变量,然后以该指针变量为函数实参调用函数,以期在该函数体内实现内存空间的申请,但发现函数调用完毕后通过该指针变量去管理内存单元却发生错误。具体代码如下所示。

void Create(int*) ;

void main()

{int *ptr=NULL;

Create(ptr);

*ptr=10;

cout

delete ptr;

ptr=NULL;

}void Create(int* p)

{p=new int;

}

粗看代码,ptr指针变量做了正确的初始化,使用delete ptr回收了内存单元,最后又将ptr赋值为NULL避免了迷途指针,似乎很完美,但运行代码却出现了错误。参看图1所示进行分析。

从图中可以清晰看出ptr在函数调用前后始终是值为NULL的空指针。解决该问题方法参见后面的2.1节内容。

关于指针涉及到的问题很多,本文无意于面面俱到,接下来结合不同应用背景的案例从管理者职责的角度分析通过指针申请内存和释放内存的过程中如何避免上述问题的发生。

2 指针应用案例

2.1 指针传递错觉问题的解决

为了叙述的方便,提出客户方和服务方的概念,本文后面将沿用该称谓。客户方是指需求的产生方或者是调用方,服务方是指满足需求的一方或者是被调用方。如在前图1所示的案例中,main()函数称为客户方,Create()函数称为服务方。

在图1所示的案例中,客户方提出要使用堆上内存空间的需求,服务方负责申请堆上内存用于满足客户方。通过分析图1发现客户方的需求并没有得到满足。弄清了客户方和服务方彼此的职责后,应首先画出如图2所示的正确内存状况图。

然后如下所示进行代码的改写。

void Create(int**);

void main()

{int *ptr=NULL;

Create(&ptr);

*ptr=10;

cout

}void Create(int** p)

{*p=new int;

}

2.2 动态链表讨论

指针在动态链表的实现中起着举足轻重的作用。常规的动态链表[5]是由两个密切配合的类相互协调实现的。这两个类是链表结点类和链表类,分别用ListNode和List表示。为了突出两者角色的不同,称ListNode为ListNode“员工类”、List为List“管理者类”。List“管理者类”和ListNode“员工类”进行协调共同为客户方提供服务。ListNode“员工类”的两个数据成员分别用int data和ListNode* link表示,List“管理者类”的数据成员用ListNode* head表示。常规的动态链表应用案例如下代码所示。

#include "list.h"

void main()

{

List alist;

for (int i=0;i

alist.InsertTail(i+1);

alist.Print();

}

在代码中main()函数代表客户方,alist对象代表服务方,那么内存空间的申请和释放是如何分工实现的呢?

客户方在栈上生成了alist对象,并负责析构alist对象所占据的内存空间。服务方alist对象负责堆上内存空间的申请和释放,服务方alist对象在被销毁时,其析构函数负责了堆上内存空间的释放。接下来我们将main()函数做如下所示的代码修改。

#include "list.h"//list.h中有#include "listnode.h"语句

void main()

{

List alist;

ListNode* pListNode=NULL;

for (int i=0;i

pListNode=new ListNode(i+1);

alist.InsertTail(pListNode);

}

pListNode=NULL;

alist.Print();

}

观察客户方的需求,发现有以下变化:①客户方直接操控ListNode“员工类”,在堆上申请了内存空间;②客户方负责将pListNode指针置空值,防止迷途指针的出现;③服务方alist 对象不用考虑堆上内存空间的申请,而只考虑堆上内存空间的释放。

2.3 动态链表的进一步讨论

客户方和服务方是一个相对的概念,应该动态灵活地看待它。前面对动态链表的讨论限于最高层,现在我们把注意力集中在动态链表的内部,考虑List“管理者类”和ListNode“员工类”的协调配合关系。它们两者均拥有指针,都是内存的管理者,所以弄清List“管理者类”和ListNode“员工类”各自的职责也就弄清了两者的配合关系。在常规的动态链表中,L istNode“员工类”不考虑堆上内存空间的申请和释放问题,这些工作都交给List“管理者类”去完成。但是我们可以改变设计的方案,比如,让L istNode“员工类”和List“管理者类”配合共同完成堆上内存空间的释放。L istNode“员工类”和List“管理者类”的析构函数代码如下所示。

ListNode::~ListNode()

{

if (this->pNextListNode)

delete pNextListNode;

}

List::~List()

{

if (this->head)

delete this->head;

}

从代码中可以清晰地看出,链表类对象alist在栈上被销毁时调用它的析构函数,该析构函数非常简单,只是对链表的头指针应用delete操作,该操作导致链表的第一个结点被销毁,因而又会调用L istNode“员工类”对象(结点)的析构函数,该过程一直传递下去构成递归关系,非常优雅地实现了内存空间的释放功能。

3 结束语

指针是C/C++语言中灵活而重要的工具,使用指针可以实现灵活的数据结构。本文概述了指针常见问题,并重点分析了用管理者职责的概念去帮助程序员理清动态链表中控制指针的思路。指针涉及到的内容丰富,但只要程序员抓住指针控制内存的本质,以职责为线索进行思索,就比较容易建立清晰的思路控制指针,提高程序的安全性。该方法在编程实践中具有良好的效果。

参考文献:

[1] 张广梅,李晓维.动态内存错误的静态检测[J].计算机辅助设计与图形学学报,2005(3).

[2] 徐厚峰.空指针解引用静态检测方法研究[D].长沙:国防科学技术大学,2008.

[3] 柯平. 内存泄漏静态检测模型的设计与实现[D].北京:北京邮电大学,2009.

[4] 陈凤祥, 李汪根.C++动态数组的实现与重用[J].计算机技术与发展,2010(2).

[5] 加娜尔・玉素甫.用C语言中的指针处理数据结构中的链表的方法[J].伊犁教育学院学报,2002(3).