C++分配内存的途径

1. C++非容器分配内存的途径

如下图所示, 当我们使用如下语句:
Foo* p = new Foo(x);
的时候, 默认走的是2的路径, 也就是编译器会把上面这条语句翻译为:

Foo* p = (Foo*)operator new(sizeof(Fop));
new(p) Foo(x);
...

其中的operator new默认走的是全局的::operator new(size_t);函数, 最终调用malloc来分配内存.


我们想要插手管理内存, 做法就是走1的道路, 也就是在Foo类中重载operator new操作.

在这里插入图片描述

2. C++容器类分配内存的途径

容器类相比非容器类的内存分配, 多了一个"中间商", 也即标准库中的allocator模板类.
如下图, 容器创建内存, 会先调用allocate方法申请内存, allocate方法里面则也是调用全局的::operator new方法然后调用malloc进行内存申请分配.
在这里插入图片描述

3. 重载全局operator new和operator delete

因为要重载全局的, 因此不能在某一个namespace中声明, 只能在全局中声明.
在这里插入图片描述
测试上图左边的代码, 我用了简单的测试程序:

void test05(){
    int* p = new int[5]; //这里会调用全局operator new[]
    delete[] p; //这里会调用全局operator delete[]
    using sanjay02::A;
    A* p_a = new A; //调用全局operator new
    delete p_a; //调用全局operator delete
}

执行的结果是:

sanjay global new[]() #调用了我们重载的operator new[]
sanjay global delete[](ptr)  # 同上delete[]
sanjay global new()  # 调用重载的operator new
default ctor. this=0x55c5eaea5280 id=0 #构造函数
dtor. this=0x55c5eaea5280 id=0 #析构函数
sanajy global delete(ptr)  # delete

4. 重载类内operator new/ operator delete(包括[])

4.1 要点

如下图, 当我们使用Foo* p = new Foo;时, 编译器会转换为右边上面的代码, 当调用operator new时, 会先去类中找是否有这样的函数.
当我们在类内重载operator new的时候, 因为编译器是先调用operator new这个方法的, 所以这个方法必须是静态的, 这样才能可以调用. 如果不是静态的, 在对象创建之前不能调用, 可是编译器要先用这个方法进行内存分配, 这样就矛盾了.
所以这个方法是static的, 我们不写也可以, 因为C++编译器知道这两个必须为静态的, 因此默认是静态的了.
在这里插入图片描述
下面再来看看operator new[]的:
需要注意的点:

  1. 下图的右上, 当编译器使用operator new时, 申请的内存大小是sizeof(Foo) * N + 4, 这个4应该是cookie的大小
  2. 构造函数会调用N次
  3. 析构函数也会调用N次

在这里插入图片描述

4.2 测试例子

//----------------------测试类内operator new------------------------------
#include <cstddef>
#include <iostream>
#include <string>
namespace sanjay06
{

class Foo
{
public:
  int _id; //4
  long _data; //4
  string _str; // 32 ==> 4 + 4 + 32 = 40 ==> 48 //64位 8字节对齐
  
public:
  	static void* operator new(size_t size);
  	static void  operator delete(void* deadObject, size_t size);
  	static void* operator new[](size_t size);
  	static void  operator delete[](void* deadObject, size_t size);
  
  Foo() : _id(0)      { cout << "default ctor. this="  << this << " id=" << _id << endl;  }
  Foo(int i) : _id(i) { cout << "ctor. this="  << this << " id=" << _id << endl;  }
  //virtual 
  ~Foo()              { cout << "dtor. this="  << this << " id=" << _id << endl;  }
  
};

void* Foo::operator new(size_t size)
{
  	Foo* p = (Foo*)malloc(size);  
	cout << "Foo::operator new(), size=" << size << "\t  return: " << p << endl;

  	return p;
}

void Foo::operator delete(void* pdead, size_t size)
{
	cout << "Foo::operator delete(), pdead= " << pdead << "  size= " << size << endl;
	free(pdead);
}

void* Foo::operator new[](size_t size)
{
  	Foo* p = (Foo*)malloc(size);  //crash, 問題可能出在這兒 
	cout << "Foo::operator new[](), size=" << size << "\t  return: " << p << endl;  
	
  	return p;
}

void Foo::operator delete[](void* pdead, size_t size)
{
	cout << "Foo::operator delete[](), pdead= " << pdead << "  size= " << size << endl;
	
	free(pdead);
}

//-------------测试重载operator new 类内----------------------
void test_overload_operator_new_and_array_new() 
{	
	cout << "\ntest_overload_operator_new_and_array_new().......... \n";		
	
	cout << "sizeof(Foo)= " << sizeof(Foo) << endl;
	
	{
    Foo* p = new Foo(7);
    delete p;
    
    Foo* pArray = new Foo[5];	//无法给array new参数, 只能使用默认构造函数
    delete [] pArray;
	}
	cout << "====================分割==========================" << endl;
	{
	cout << "testing global expression ::new and ::new[] \n";
	// 這會繞過 overloaded new(), delete(), new[](), delete[]() 
	// 但當然 ctor, dtor 都會被正常呼叫.  
	
    Foo* p = ::new Foo(7);
    ::delete p;
    
    Foo* pArray = ::new Foo[5];	
    ::delete [] pArray;
	}
}
} //namespace

运行的结果如下:

test_overload_operator_new_and_array_new().......... 
sizeof(Foo)= 48
Foo::operator new(), size=48      return: 0x564256454280
ctor. this=0x564256454280 id=7
dtor. this=0x564256454280 id=7
Foo::operator delete(), pdead= 0x564256454280  size= 48
Foo::operator new[](), size=248   return: 0x5642564542c0
default ctor. this=0x5642564542c8 id=0
default ctor. this=0x5642564542f8 id=0
default ctor. this=0x564256454328 id=0
default ctor. this=0x564256454358 id=0
default ctor. this=0x564256454388 id=0
dtor. this=0x564256454388 id=0
dtor. this=0x564256454358 id=0
dtor. this=0x564256454328 id=0
dtor. this=0x5642564542f8 id=0
dtor. this=0x5642564542c8 id=0
Foo::operator delete[](), pdead= 0x5642564542c0  size= 248
====================分割==========================
testing global expression ::new and ::new[] 
ctor. this=0x564256454280 id=7
dtor. this=0x564256454280 id=7
default ctor. this=0x5642564542c8 id=0
default ctor. this=0x5642564542f8 id=0
default ctor. this=0x564256454328 id=0
default ctor. this=0x564256454358 id=0
default ctor. this=0x564256454388 id=0
dtor. this=0x564256454388 id=0
dtor. this=0x564256454358 id=0
dtor. this=0x564256454328 id=0
dtor. this=0x5642564542f8 id=0
dtor. this=0x5642564542c8 id=0

分析:

  1. 由于我的电脑是64位的, 因为内存的对齐是8字节的
  2. 我使用sizeof(string)查看了大小, 发现string的大小是32字节
  3. 因此Foo类的大小: 4 + 4 + 32 = 40, 然后字节对齐, 最终Foo类的大小就是48字节大小了. 这个和sizeof(Foo)= 48输出一致
  4. 当执行Foo* p = new Foo(7);时, 输出为
    Foo::operator new(), size=48      return: 0x564256454280
    ctor. this=0x564256454280 id=7
    
    说明先调用我们重载的operator new函数, 然后再调用构造函数
  5. 接着delete也符合

接下来是调用operator new[]的分析:

  1. 执行Foo* pArray = new Foo[5];, 由于array new无法给定初值, 因此只能调用默认构造函数. 这个语句的输出为:

    Foo::operator new[](), size=248   return: 0x5642564542c0
    default ctor. this=0x5642564542c8 id=0
    default ctor. this=0x5642564542f8 id=0
    default ctor. this=0x564256454328 id=0
    default ctor. this=0x564256454358 id=0
    default ctor. this=0x564256454388 id=0
    

    其中size=248的由来: 48 * 5 = 240, 然后再加8的cookie , 便等于248了. 然后调用5次构造函数.

  2. array delete同理.


当Foo类的析构函数加了virtual之后, 这个类会变大, 变成了sizeof(Foo)= 56, 变大了8个字节, 这是因为加了virtual之后, 需要增加虚函数表来维护, 因为会增大类的大小.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zedjay_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值