如何实现自己特定的内存管理,如何正确替换C++中的全局运算符new和delete

在谈下面的问题之前,请先看看写的这篇博客:new operator和operator new之间的区别

考虑下面的代码块,能运行,但是存在一些问题,我们将一一解答:

#include <stdio.h>
#include <iostream>

using namespace std;

class A
{
public:
    A()
	{
        throw exception("");
    }
    void operator delete(void *p, size_t s)
	{
        cout << "A::operator delete/n" << endl;
    }
};

class B
{
public:
	virtual ~B(){}
	void operator delete(void* p,size_t n) throw()
	{cout << "operator delete B" << endl;}
	void operator delete[](void* p,size_t n) throw()
	{cout << "operator delete[] B" << endl;}
	void f(void* p,size_t n) throw()
	{cout << "function f" << endl;}
};

class D:public B
{
public:
	void operator delete(void* p) throw()
	{cout << "operator delete D" << endl;}
	void operator delete[](void* p) throw()
	{cout << "operator delete[] D" << endl;}
};

typedef void (B::*PMF)(void*,size_t);

void main()
{
	try
	{
        A *p = new A;         // 抛出异常!调用A::operator delete()
    }
	catch (exception)
	{

    }

	D* pd1 = new D;
	delete pd1;
	B* pb1 = new D;
	delete pb1;
	D* pd2 = new D[10];
	delete[] pd2;
	B* pb2 = new D[10];
	delete[] pb2;

	PMF p1 = &B::f;
	//PMF p2 = &B::operator delete;
}
运行结果如下:
A::operator delete/n
operator delete D
operator delete D
operator delete[] D
operator delete[] D
请按任意键继续. . .

注意:B的operator delete函数中还有第二个参数,而在D中却没有,这只是不同的编程风格问题,B中的这两个delete函数都是普通的内存释放函数,而不是placement delete(我们将在后面详细说明)。

①对于代码块B和D还是存在着一个隐藏的内存错误。在这两个类中都提供了operator delete()和operator delete[]()函数,但却没有提供相应的operator new()和operator new[]()。这是非常危险的,因为默认的operator new()和operator new[]()可能不会正确地实现我们所需要的功能。所以我们需要一对同时定义。

②函数operator new()和operator delete()的所有风格与静态函数的风格都是一致的,即使它们没有被声明为静态的。当我们在声明这些函数时,虽然C++并不会强制我们显式地使用“static”,但是我们通常应该显式的将函数operator new()和operator delete()声明为静态函数。它们永远都不能是非静态成员函数。

③对于下面的代码块

D* pd1 = new D;

delete pd1;

B* pb1 = new D;

delete pb1;

两者都是调用了D::operator delete(void*),这是因为B的析构函数被声明为虚函数,因此D的析构函数将被调用,这也就意味着:虽然B::operator delete()并没有被声明为虚函数(事实上,这是不可能的),但D::operator delete()也一定会被调用。为什么?

通常编译器在为每个析构函数生成代码时,会设置一个标志位,这个标志位的含义是“当对象被销毁后,是否应该销毁它?”(在销毁自动对象时,这个标志位是false,而在销毁动态对象时,这个标志位是true)。然后,在编译器生成的析构函数代码中,所做的最后一件事就是检查这个标志位,如果为true,则会去调用正确地operator delete()函数。这种技术就自动地确保了正确地行为,即虽然函数operator delete()是一个静态的函数并因此不能成为虚函数,但从行为来看却像是虚函数。

④对于下面的代码块

D* pd2 = new D[10];

delete[] pd2;

B* pb2 = new D[10];

delete[] pb2;

由运行结果可知两者都调用了D::operator delete[](void*),但是后者的行为时未定义的,C++规定:传递给函数operator delete[]的指针的静态类型与动态类型必须是相同的,因此我们永远都不要通过多态的方式来处理数组,应优先选择使用vector<>或者deque<>。

⑤对于下面的代码块

PMF p1 = &B::f;

//PMF p2 = &B::operator delete;

第一个赋值运算时合法的,就是简单的把一个成员函数的地址赋给了成员函数指针。

第二个赋值运算时非法的,因为函数void operator delete(void* p,size_t n) throw()并不是B的一个非静态函数,尽管它看上去像是一个非静态函数。

⑥由于A的构造函数会抛出异常,而且A定义了一个与operator new(size_t)匹配的内存释放函数,因此编译器将调用那个内存释放函数然后抛出异常。

这里需要理解的是:若new运算符在初始化对象时抛出异常并且存在与operator new匹配的内存释放函数,那么这个内存释放函数将被调用以释放内存,异常在new表达式的环境下继续传播。delete运算符没有放置形式,但是存在放置形式的内存释放函数。非放置形式的内存释放函数与非放置形式的内存分配函数是匹配的;放置形式的内存释放函数与放置形式的内存分配函数,若它们的第二个及后续参数一致,则是匹配的。

⑦对于最开始所说的B中的两个delete函数都是普通的内存释放函数作详细说明:

正常的operator new签名式:

void* operator new(std::size_t) throw (std::bad_alloc);

对应的正常的operator delete签名式:

void operator delete(void* raw_memory)throw();//global 作用域中的正常签名式

void operator delete(void* raw_memory,std::size_t size)throw();//class作用域中典型的签名式

类似于new的placement版本,operator delete如果接受额外参数,便称为placement delete,例如:

struct Widget

{

static void* operator new(std::size_t size, std::ostream& log_stream)throw(std::bad_alloc);

static void  operator delete(void* memory) throw();

static void  operator delete(void* memory,std::ostream& log_stream)throw();

...

};

如果以下语句引发Widget构造函数抛出异常:

Widget* new_widget = new (std::cerr) Widget; //一如既往,但这次就不在发生泄漏。

然而如果没有抛出异常(大部分是这样的),客户代码中有个对应的delete,会发生什么事情:

delete pw; //call normal operator delete

调用的是正常形式的operator delete,而非其placement版本。请记住:placement delete只有在‘伴随placement

new调用而触发的构造函数‘出现异常时才会被调用。对着一个指针施行delete绝不会导致调用placement delete。

还有一点你需要注意的是:由于成员函数的名称会遮盖其外围作用域中的相同名称,你必须小心避免让class专属

news遮盖客户期望的其它news(包括正常版本)。默认情况下,C++在global作用域内提供以下形式的operator new:

void* operator new(std::size_t)throw(std::bad_alloc);//normal new.

void* operator new(std::size_t,void*)throw();//placement new

void* operator new(std::size_t, const std::nothrow_t&)throw();//nothrow new.see Item 49.

如果你在class内声明任何operator news,它会遮掩上述这些标准形式。除非你的意思就是要阻止class的客户使

用这些形式,否则请确保它们在你所生成的任何定制型operator new之外还可用。对于每一个可用的operator new也

请确定提供对应的operator delete。如果希望这些函数都有着平常的行为,只要令你的class专属版本调用global版

本即可。

为了完成以上所言的一个简单做法就是建立一个base class,内含所有正常形式的new和delete:

struct StandardNewDeleteForms

{

//normal new/delete

static void* operator new(std::size_t size)throw(std::bad_alloc)

{ return ::operator new( size ); }

static void operator delete(void* memory) throw()

{ ::operator delete( memory ); }

//placement new/delete

static void* operator new(std::size_t size,void* pointer)throw()

{ return ::operator new( size, pointer );}

static void operator delete(void* memory,void* pointer)throw()

{ return ::operator delete( memory, pointer ); }

//nothrow new/delete

static void* operator new(std::size_t size,const std::nothrow_t& no_throw)throw()

{ return ::operator new( size, no_throw ); }

static void operator delete(void* memory,const std::nothrow_t&)throw()

{ ::operator delete( memory ); }

};

凡是想自定形式扩充标准形式的客户,可利用继承机制及using声明式(Item 33)取得标准形式:

struct Widget:public StandardNewDeleteForms

{

using StandardNewDeleteForms::operator new;

using StandardNewDeleteForms::operator delete;

static void* operator new(std::size_t size, std::ostream& log_stream) throw(std::bad_alloc);

static void  operator delete(void* memory,std::ostream& log_stream) throw();

...

};



如何实现自己特定的内存管理,如何正确替换C++中的全局运算符new和delete

时间: 2024-10-21 13:02:00

如何实现自己特定的内存管理,如何正确替换C++中的全局运算符new和delete的相关文章

C++内存管理机制(持续更新中......)

C++内存管理: 名称 分配 栈区 函数内部局部变量的存储单元在栈区,函数执行结束时,这些存储单元被释放.效率高,但是分配的内存容量有限. 堆区 就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete.如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收. 自由存储区 就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的. 全局/静态存储区 全局变量和静态变量被分配到同一块内存中,在以前

内存管理:栈区,堆区,全局区,文字常量区,程序代码区

一.预备知识-程序的内存分配 一个由C/C++编译的程序占用的内存分为以下几个部分 1.栈区(stack)- 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等.其 操作方式类似于数据结构中的栈. 2.堆区(heap) - 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回 收 .注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵. 3.全局区(静态区)(static)-,全局变量和静态变量的存储是放在一块的,初始化的 全局变量和静态变量在一块区域, 未初始化的全局变

Kernel那些事儿之内存管理(12) --- 内核映射(中)

内核地址空间中后面这128MB的最后一部分,是固定映射 (fixed mappings). 固定映射是什么意思?为什么要有固定映射?Kernel源代码的注释里有一句话,可谓一语中的:The point is to have a constant address at compile time, but to set the physical address only in the boot process. 一个固定映射的线性地址是个常量,例如0xffffc000,且该常量在编译阶段就可以确定.

RTX——第18章 内存管理

内存管理介绍在 ANSI C 中,可以用 malloc()和 free()2 个函数动态的分配内存和释放内存,但是,在嵌入式实时操作系统中,调用 malloc()和 free()却是危险的,因为多次调用这两个函数会把原来很大的一块连续内场区域逐渐地分割成许多非常小而且彼此又不相邻的内存块,也就是内存碎片.由于这些内存碎片的大量存在,使得程序到后来连一段非常小的连续内存也分配不到.另外,由于内存管理算法上的原因,malloc()和 free()函数的执行时间是不确定的.在 RTX 中,操作系统把连

操作系统思考 第六章 内存管理

第六章 内存管理 作者:Allen B. Downey 原文:Chapter 6 Memory management 译者:飞龙 协议:CC BY-NC-SA 4.0 C提供了4种用于动态内存分配的函数: malloc,它接受表示字节单位的大小的整数,返回指向新分配的.(至少)为指定大小的内存块的指针.如果不能满足要求,它会返回特殊的值为NULL的指针. calloc,它和malloc一样,除了它会清空新分配的空间.也就是说,它会设置块中所有字节为0. free,它接受指向之前分配的内存块的指针

[百度空间] [原]基于内存生命周期的内存管理

如果根据声明周期划分的话,内存可以大至划分为3类 1.静态内存 2.临时内存 3.普通内存 静态内存的特点是,程序开始(通常是初始化期间)分配的,而后就不会释放,直到程序结束,典型的如singleton,相信还有很多类,其实例只会被分配一次,而且理论上,生存期是贯穿整个程序的. 临时内存的特点通常如下: void function(){    ...    char* buffer = new char[size]; ... delete [] buffer;} 从上面可以看到,此种内存分配后,

iOS基础 ----- 内存管理

Objective-C 的内存管理方式有引用计数机制,垃圾回收机制,自动释放池.有alloc,就有释放.iOS应?程序出现Crash(闪退),90%的原因是因为内存问 题.在?个拥有数?个甚?是上百个类的?程?,查找内存问 题极其困难,学会内存管理,能帮我们减少出错的?率.内存问题体现在两个??:内存溢出.野指针异常. 引用计数器 在Xcode4.2及之后的版本中由于引入了ARC(Automatic Reference Counting)机制,程序编译时Xcode可以自动给你的代码添加内存释放代

iOS内存管理(一)

最近有时间,正好把iOS相关的基础知识好好的梳理了一下,记录一下内存相关方面的知识. 在理解内存管理之前我觉得先对堆区和栈区有一定的了解是非常有必要的. 栈区:就是由编译器自动管理内存分配,释放过程的区域,存放函数的参数值,局部变量等.栈是内存中一块连续的区域,它的大小是确定的. 堆区:需要我们来动态的分配,释放,也就是我们内存管理的主角. 我们通过一个简单的例子来看看. NSString *string = [NSString alloc] init]; 我们声明了一个NSString类型的变

iOS内存管理机制

概述 我们知道在程序运行过程中要创建大量的对象,和其他高级语言类似,在ObjC中对象时存储在堆中的,系统并不会自动释放堆中的内存(注意基本类型是由系统自己管理的,放在栈上).如果一个对象创建并使用后没有得到及时释放那么就会占用大量内存.其他高级语言如C#.Java都是通过垃圾回收来(GC)解决这个问题的,但在OjbC中并没有类似的垃圾回收机制,因此它的内存管理就需要由开发人员手动维护.今天将着重介绍ObjC内存管理: 引用计数器 属性参数 自动释放池 引用计数器 在Xcode4.2及之后的版本中