指针的安全使用

1 指针的使用场合 Cases using pointers 

    在正常情况下,无论从安全性和编程效率、运行效率上比较,指针比普通声明的变量都要低,特别是new和delete的速度相当慢,因此一般不使用指针变量。以下是COODBLib优化前的几个例子:

在Debug版本中选取2E4个CCustomer过程中,需花费:
    563.414ms完成300032次CObject::operator new()操作
    469.955ms完成140000次delete()操作
而全面优化过后,进行此操作共耗时6~7秒,可见指针空间申请和销毁可能占用大量时间。

    但因为对于多态、引用、变量交换等操作指针有其优势,在此类情况下将不得不使用指针;此时要特别注意安全问题和内存回收问题。

2 指针的安全使用 Using pointers safely 

    以上三条就是指针的使用要点。

初始化

  • 在指针声明时应习惯性初始化为NULL,如
    CRecordset *pRecordset=NULL;
  • 在类的构造器中应对所有成员指针变量初始化。

检查 (Check state and parameters)

  • 检查函数传入参数,如
    BOOL SetName(LPCTSTR lpszName)
        {
        if (lpszName==NULL) 
            return FALSE;
        ...
        }
  • 在类成员函数中检查类的指针变量是否可用,即用于外部服务的指针变量是否已经就位。
  • 删除指针内存时检查指针是否为空,配合下面的复位指针,可防止多次删除同一个指针内存。

    一种例外情况是VC的Collection族类(Array/List/Map)使用时不需要判断,因为其GetAt()函数已经进行了判断。

复位指针

在删除指针内存后,应复位指针为0,否则在下次检查指针是否为空时可能出错。如:
    delete m_pRecordset;
    m_pRecordset=NULL;

但在脱离作用域前可不复位,如在函数末尾删除临时指针变量及在销毁器删除成员指针变量。

3 指针内存回收 Freeing memory of pointers 

    函数内指针

    如果此指针是临时变量或尽管是类的变量,却只在此函数中才会使用,在函数退出时删除,如果函数退出的情况很复杂,则使用函数销毁器来完成,否则极可能发生漏删现象。参考函数结构规范中的例子。

    替别的函数销毁指针是及其危险且难以维护的事情,如:

    void main()
    {
        char *sz=new char[8];
        memset(sz, 0, 8);
        memcpy(sz, "name", 4);
        PrintfName(sz);
        //...
    }

    void PrintName(char *sz)
    {
        printf("%s", sz);
        delete []sz;
    }

    当上面的两个函数不是同一个程序员维护时,极易发生内存泄漏、重复删除内存等问题。

    在函数中回收内存的要点在于:谁申请的内存谁回收。

    类成员指针

    删除类的内存的要点在于“谁的指针谁回收”,而不是“谁申请的内存谁回收”。因而类只负责回收自己的指针,而不管其父类和支类(CCustomer对于CGroup而言是一个支类)的指针内存是否回收。这使得类的内存回收变得简单,即只需要关心当前类有哪些成员指针变量即可。

    类成员指针变量的内存回收一般在销毁器中进行,少量情况下需要在销毁之前删除内存的,可建立FreeMemory()函数,但需要在销毁器中也调用此函数。注意FreeMemoryAll()和销毁器的要求一般略有不同,FreeMemoryAll()是销毁器的一个子集。COODBQuery是个典型的例子。

    不过具体到每个成员指针变量比较复杂,有几种情况:

    ①指针是外部服务的地址,此指针不需要删除,在VC的CRecordset类应用中可看到:

CDatabase Database;
CRecordset *pRecordset=new CRecordset(&Database);

    可以看出此类中必有一个指针来存储&Database,但此指针肯定不需要删除内存的操作。

    为了能清除的发现此类指针,推荐用m_cp作为其前缀,表明此为Const Pointer。

    ②指针是内部存储数据,则需要删除。内部存储数据有两种,一种是普通指针,由类的某个函数(如构造器)申请内存;另外一种是PtrArray/List/Map的元素,在类的外部申请,通过Add函数加入,但也在类的销毁器中回收。例如:

template <class Class>
COODBQuery <Class>::~COODBQuery()
    {
    if (m_pOODBFields)
        delete []m_pOODBFields;
    m_pOODBFields=NULL;
    FreeMemoryAll();
    }

template <class Class>
void COODBQuery <Class>::FreeMemoryAll()
    {
    //cy: Free all memory.
    for (int i=m_ClassArray.GetSize()-1;i>=0;i--)
        delete m_ClassArray.GetAt(i);
    
    RemoveAll(); //cy: This function will transfer class pointers from ClassArray to ClearedArray.
    }

    可以看到,遵循“谁的指针谁回收”的原则,尽管m_ClassArray.GetAt(i)(实际上是一个从COODBRecord继承的类的指针,如CCustomer)中可能还有指针变量,但其回收工作已经交由Class销毁器完成。这样封装的好处是无论Class的内部结构如何,是否发生变化,只要在Class销毁器中做好内存回收工作即可。

    全局指针,静态指针

    由于全局指针首先是一个全局变量,所以一般情况下会随着程序结束而自动被释放内存,但为了在debug下不会因此而混淆程序中其他未得到回收的内存,建议进行内存回收,其位置在ExitInstance()函数内进行。

    大部分全局指针都是静态指针,所以在命名上没有特殊要求,用g_p作为前缀即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值