C++指针编程你要小心的陷阱——如何优雅的释放指针void*

原创 2017年08月29日 21:29:18
指针操作是C++开发中必备技能。尽管C++11开始引入了智能指针以缓解普通指针的滥用,但是某些场合必须使用普通指针。释放指针在C/C++编程中非常重要,一般推荐释放指针后立即将指针设置为null,防止出现低级的野指针问题(只能避免低级别的野指针)同时方便调试。
一、C语言时代
在C语言编程中,我们由于没有C++模板,函数重载功能,所以一般定义一个统一的宏来用于释放指针。
// 删除指针  
#define SAFE_DELETE(p) { \  
    if (NULL != (p)) { \  
        free((p)); \  
        (p) = NULL;\  
    }\  
} 
二、C++时代
C++相对C语言的改进就是引入了面向对象操作,支持函数重载、类继承、模板、异常处理等等概念。在C++中,一般用函数模板来操作释放指针,这样的好处是可以进行类型检查。
// 删除数组
template <typename T>
inline void safe_delete(T *&target) {
    if (nullptr != target) {
        delete target;
        target = nullptr;
    }
}

// 删除数组指针
template <typename T>
inline void safe_delete_arr(T *&target) {
    if (nullptr != target) {
        delete[] target;
        target = nullptr;
    }
}
三、void *指针问题
在C、C++ 中,void * 指针可以转换为任意类型的指针类型,在删除void*指针时编译器往往会发出如下警告
warning: deleting 'void*' is undefined [enabled by default]
翻译:警告:删除“void *”指针可能引发未知情况(默认打开警告)
永远记住,在C、C++开发中绝对不能忽视警告,一定要重视警告,最好消除警告。有些警告无关紧要,有些警告却是bug的根源;删除void *指针的警告就属于后面一种情况,可能引起严重的bug而且难以发现:
1. 使用delete pointer; 释放void指针void *,系统会以释放普通指针(char, short, int, long, long long)的方式来释放void *指向的内存空间;
2. 如果void *指向一个数组指针,那么由于释放指针时用了delete pointer从而导致内存泄漏,释放指针正确做法是delete[] pointer;
3. 如果void *指向一个class类,那么系统由于认为void *指向一个普通的内存空间,所以释放指针时系统class的析构函数不会调用;

释放void *的解决方案:将void *转换为原来类型的指针,然后再调用delete释放指针,如果原来的指针是数组指针,那么必须使用delete []删除指向的内存空间。
在C++中我们可以使用模板定义内联函数:
template <typename T>
inline void safe_delete_void_ptr(void *&target) {
	if (nullptr != target) {
		T* temp = static_cast<T*>(target);
		delete temp;
		temp = nullptr;
		target = nullptr;
	}
}
调用方法
int *psample = new int(100);
safe_delete_void_ptr<int>(psample);
利用模板实例化参数统一简化过程。
测试代码
safe_delete_demo.cpp
#include <cstddef>
#include <cstdlib>
#include <string>
#include <iostream>

template <typename T>
inline void safe_delete(T *&target) {
    if (nullptr != target) {
        delete target;
        target = nullptr;
    }
}

template <typename T>
inline void safe_delete_void_ptr(void *&target) {
    if (nullptr != target) {
        T* temp = static_cast<T*>(target);
        delete temp;
        temp = nullptr;
        target = nullptr;
    }
}

class A {
public:
    A(std::string name) {
        this->name = name;
    };
    virtual ~A() {
        std::cout<<"base class A's destructor"<<", name: "<<this->name<<std::endl;
    };
public:
    std::string name;
};

class AChild: public A {
public:
    AChild(std::string name, std::string school)
    : A(name){
        this->school = school;
    };

    ~AChild() {
        std::cout<<"child class AChild's destructor"<<", name: "<<this->name
            <<", school: "<<this->school<<std::endl;
    };

public:
    std::string school;
};

int main(int argc, char *argv[]) {
	// 测试safe_delete释放普通类指针
    std::cout<<"safe_delete pointer of type AChild"<<std::endl;
    AChild *a1 = new AChild("jacky", "Shenzhen University");
    safe_delete(a1);
    std::cout<<std::endl;

	// 测试safe_delete释放void*指针
    std::cout<<"safe_delete pointer of type void *"<<std::endl;
    void *vp = new AChild("Polyn", "Southern University of Science and Technology");
    safe_delete(vp);
    std::cout<<std::endl;

	// 测试safe_delete_void_ptr释放模板实例化为基类的void*指针
    std::cout<<"safe_delete_void_ptr pointer of type void * ==> A *"<<std::endl;
    void *vpA = new AChild("Heral", "Renmin University of China");
    safe_delete_void_ptr<A>(vpA);
    std::cout<<std::endl;

	// 测试safe_delete_void_ptr释放模板实例化为子类的void*指针
    std::cout<<"safe_delete_void_ptr pointer of type void * ==> AChild *"<<std::endl;
    void *vpAChild = new AChild("pevly", "Southeast University");
    safe_delete_void_ptr<AChild>(vpAChild);
    return 0;
}
编译及运行
$ g++ -std=c++11 safe_delete_demo.cpp
safe_delete_demo.cpp: In instantiation of 'void safe_delete(T*&) [with T = void]':
safe_delete_demo.cpp:59:16: required from here
safe_delete_demo.cpp:9:9: warning: deleting 'void*' is undefined [enabled by default]
delete target;

$ ./a.out
safe_delete pointer of type AChild
child class AChild's destructor, name: jacky, school: Shenzhen University
base class A's destructor, name: jacky

safe_delete pointer of type void *
safe_delete_void_ptr pointer of type void * ==> A *
child class AChild's destructor, name: Heral, school: Renmin University of China
base class A's destructor, name: Heral

safe_delete_void_ptr pointer of type void * ==> AChild *
child class AChild's destructor, name: pevly, school: Southeast University
base class A's destructor, name: pevly

通过测试用例我们可以看出。
1. 使用safe_delete释放明确的类会自动触发析构函数(如果析构函数为虚函数,那么先调用子类的析构函数再调用子类的直接基类的析构函数);
2. 使用safe_delete释放void*指针指向的类时,不会触发对应类的析构函数;
3. 如果使用safe_delete_void_ptr内联函数释放void*指针,那么由于在释放指针前,函数会将void*指针转换为特定类型的函数指针,所以最终能够触发调用析构函数,并且不影响虚类的释放行为。

void指针详解

void指针的理解和使用都是比较复杂的,尤其与其他形式定义结合时,就显得更为复杂了。下面就综合网上一些资料,介绍一下void的用法。 1.void指针是一种特别的指针    void *vp ...
  • zycxnanwang
  • zycxnanwang
  • 2016年10月20日 21:13
  • 1450

空指针和void *类型指针

今天,有个师弟问我:“师兄,空指针和void *类型指针是怎样的?” 当时,我大概的说法是: 1、空指针是没有指向的指针,将暂时用不到的指针定义成空指针,能防止误用。 2、而void * 类型的指针是...
  • luo_technically
  • luo_technically
  • 2016年10月01日 01:12
  • 1871

妙用void *型指针(万能指针|泛型指针)

为什么叫万能指针呢?
  • wusuopuBUPT
  • wusuopuBUPT
  • 2014年09月02日 19:15
  • 4345

void*指针的用法

指针有两个属性:指向变量/对象的地址和长度 但是指针只存储地址,长度则取决于指针的类型 编译器根据指针的类型从指针指向的地址向后寻址 指针类型不同则寻址范围也不同,比如: int*从指定地址向...
  • bbs375
  • bbs375
  • 2016年10月02日 15:16
  • 550

void及void指针含义的深刻解析

void的含义void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。void指针使用规范①void指针可以指向任意类型的数据,亦即可用任意数据类型的指针对void指针赋值。例如:...
  • yming0221
  • yming0221
  • 2011年03月14日 20:07
  • 102095

C++指针释放问题

今天看到了一个问题,其实在项目中也经常遇到指针释放的问题,先看看一个简单的指针释放的问题: **************************************问题如下: (1)一般在c++...
  • u011473714
  • u011473714
  • 2016年03月02日 22:46
  • 2815

void * 指针算术运算【-Wpointer-arith】

void * 指针算术运算【-Wpointer-arith】 #ifdef __cplusplus extern "C" { #endif #include #include int...
  • angelzyb
  • angelzyb
  • 2014年03月09日 09:55
  • 1828

指针的引用遇到的内存释放问题

先来一段程序:#define _CRT_SECURE_NO_WARNINGS #include #includeusing namespace std;int getMem(char* &p) { ...
  • maoliran
  • maoliran
  • 2016年08月08日 10:42
  • 680

警惕“delete void*”

对一个void*类型指针进行delete操作会出错,除非指针所指的内容是简单类型内容,因为这个操作只会释放内存,而不会执行析构函数 下面是一个代码示例: //:BadVoidPointerDe...
  • yucan1001
  • yucan1001
  • 2011年11月22日 20:53
  • 3886

void*指针及delete释放void*内存(转)

转自: http://blog.163.com/xuelinxu@126/blog/static/7117728220088102340679/ 在面向对象语言中,明白什么叫“对象”非...
  • u011595231
  • u011595231
  • 2013年11月25日 16:37
  • 2199
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:C++指针编程你要小心的陷阱——如何优雅的释放指针void*
举报原因:
原因补充:

(最多只允许输入30个字)