C++内存管理原始工具(primitives)

在开始内存管理学习之前,可以先看一下C++的强制转型工具:C++强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast



1. C++使用内存可用的原始工具

1.1 C++应用(application)使用内存(memory)的途径

1.2 C++ memory primitives 类别及是否可以重载
分配释放类属可否重载
malloc()free()C函数不可
newdeleteC++表达式(expression)不可
::operator new()::operator delete()C++函数
allocator::allocate()allocator::deallocate()C++标准库(std)可自由设计使其搭配任何容器

1.3 primitives 的使用
#include <iostream>
#include <complex>
#include <memory>				 //std::allocator  
#include <ext\pool_allocator.h>	 //欲使用 std::allocator 以外的 allocator, 就得自行 #include <ext/...> 
using namespace std;

namespace jj01
{
void test_primitives()
{
	cout << "\ntest_primitives().......... \n";
	
    void* p1 = malloc(512);	//512 bytes
    free(p1);

    complex<int>* p2 = new complex<int>; //one object
    delete p2;             

    void* p3 = ::operator new(512); //512 bytes
    ::operator delete(p3);

//以下使用 C++ 標準庫提供的 allocators。
//其接口雖有標準規格,但實現廠商並未完全遵守;下面三者形式略異。
//以下分别是MicroSoft C,Borland C和GNU C的调用实现
#ifdef _MSC_VER
    //以下兩函數都是 non-static,定要通過 object 調用。以下分配 3 個 ints.
    //注意由于使用的是object调用,且object是栈中变量,所以语句结束即调用~object()函数,即自动调用dtor
    //下边使用object调用的同理
    int* p4 = allocator<int>().allocate(3, (int*)0); 
    allocator<int>().deallocate(p4,3);           
#endif
#ifdef __BORLANDC__
    //以下兩函數都是 non-static,定要通過 object 調用。以下分配 5 個 ints.
    int* p4 = allocator<int>().allocate(5);  
    allocator<int>().deallocate(p4,5);       
#endif
#ifdef __GNUC__
    //以下兩函數都是 static,可通過全名調用之。以下分配 512 bytes.
    //GNU 2.9 version下的分配器
    //void* p4 = alloc::allocate(512); 
    //alloc::deallocate(p4,512);   
    
    //GNU 4.9 version下的分配器
    //以下兩函數都是 non-static,定要通過 object 調用。以下分配 7 個 ints.    
	void* p4 = allocator<int>().allocate(7); 
    allocator<int>().deallocate((int*)p4,7);     
	
    //以下兩函數都是 non-static,定要通過 object 調用。以下分配 9 個 ints.	
    //对应GNU2.9分配器的内容
	void* p5 = __gnu_cxx::__pool_alloc<int>().allocate(9); 
    __gnu_cxx::__pool_alloc<int>().deallocate((int*)p5,9);	
#endif
}	
} //namespace


2. 原始工具详解

2.1 new / delete expression

只有在编译器中才能够使用类作用域符号对类型的构造函数/ctor进行调用。若非如下图所示,而是使用Complex* pc = new Complex::Complex(1,2)则会报错(在VC6的内置编译器中不会)。

new expression
delete expression

new 和 delete 表达式即是C++语言中所提供的 new 和 delete 关键字,在编译过程中,编译器将它们分别转化为调用 ::operator new() (即global new)和 ::operator delete()来完成对内存空间的 分配 和 回收 目的。

对于 std::string (类型可以理解为 basic_string 的适配器?待商榷)其成员函数中的构造函数如下。即是 string::basic_string() 而不是 string::string(),这点需要注意。

(constructor) Construct basic_string object (public member function )

此外,除了针对单一对象的 new 和 delete,还有针对对象数组的 array new(new[])和 array delete(delete[])。

对于 array new 和 array delete 它们需要成对使用,详见《Effective C++》条款:16. new 和 delete 要成对使用且形式相同,否则对于对象数组的情况,回收内存时没有使用 delete[] ,则对于没有指针成员的类型不会有问题,但对于有指针成员的类型,则实际上只调用一次 dtor,会导致内存泄漏。

在使用 new[] 时,内存中会含有 cookie 块,记录数组中对象的数量等信息,有时还会有两端都有cookie的情况。同时对于有指针变量的类型的对象数组,new[]得到的指针指向第一个对象的内存地址,使用对应的delete[]时,指针所指位置则直到记录数组内对象数目位置的内存地址。

new expression
delete expression

delete[]对对象调用dtor的顺序和new[]创建对象调用ctor的顺序是相反的。

在内存分配中,每次分配内存bytes数若是4的倍数则直接划分,若不是要进行padding补齐为4的倍数?,此外类的大小其中也涉及字节对齐(padding),见:C++类空间大小


2.2 placement new() & operator delete()

placement new 允许我们将 object 构建于 allocated memory 当中去;(可以理解为定点 new,在指定内存位置进行操作,这也是在标准库所提供的 placement new() 的重载形式)

没有所谓 placement delete,因为 placement new 根本没有分配/获取新的内存,亦或称呼与 placement new 对应的 operator delete 为 placement delete


2.3 operator new() & operator delete()

对原始工具再进行细分,使用new expression得到指向新的构建对象的内存地址指针的整个过程可分为三步:

  • 调用operator new函数;
  • 运行对应的构造函数并传初值;
  • 分配空间并构造完成后,返回一个指向该对象的指针;

而对于new/delete expression是不可重载的,则意味着我们无法对其进行定制化。而对于operator new()函数是可以重载的,那么可以在其上做文章,同时要注意operator new()又可分为 type::operator new()(member functions)和 global ::operator new()(global functions),后者虽同样可重载但是并不建议,通常作为 type::operator new() 尝试失败后的备选方案,而且其为全局资源,设置重载后可能导致其它类型内存分配的问题。此外,若是更改了::operator new(),则须使用new_handler set_new_handler (new_handler new_p) noexcept;进行复原。见《Effective C++》条款49. 了解 new-handler 的行为

expression new的过程,operator new() 的作用时机和分类见下图。

对于全局 ::operator new / ::operator delete() 的重载

对于成员 operator new() / operator delete() 的重载

对于 class member operator new() 的重载,可以写出多个版本,前提是每个版本的声明都有独特的参数列表,参数列表中的第一个参数必须是 size_t,其余参数则以 new 所指定的 placement argument 为初值(即出现在 new(size_t, …)中的…即是所谓的 placement argument)。

对于 class member operator delete() (亦即是placement operator delete)的重载,也可写出多个版本。但是它们绝不会被 delete 调用。只有当 new 所调用的 ctor 抛出 exception 时,才会调用这些重载版的 operator delete(),其只会这样被调用。其用处是用来归还未能完全创建成功的 object 所占用的 memory,可以理解为保持原子性。

若是没有对 operator new(size_t size, …) 对应写出相同 placement argument 的 operator delete(void*, …),也不会有报错,只会有编译警告,同时编译器会默认放弃处理 ctor 所发出的异常。



3. new handler & set_new_handler函数

当 operator new 无法满足分配出所需 memory 大小的需求,会抛出一个 std::bad_alloc exception,而某些老旧编译器则是返回0。同时也可以使用 new(nothrow) 形式的 new operator,即不抛出异常。相关内容可见《Effective C++》条款49. 了解 new-handler 的行为

设计良好的 new handler 主要有两个目的:

  • 让更多的memory可用,也即是内存空间利用率更高;
  • 调用 abort() 或 exit();

new handler 中若无调用 abort() 或 exit(),则若是malloc无法分配出所需内存空间,则会重复调用 new handler 函数,若是new handler 也不能跳脱出来,则不断进行 new handler 尝试,造成死循环。使用全局 set_new_handler(void* (*func)()) 函数对 new handler 进行设置。

同时C++ 11中引入了 =default 和 =delete ,分别指的是 使用默认功能 和 禁用成员函数,具体内容可见:C++中的 =default和=delete;对其用处的描述为:it is not only for constructors and assignments, but also applies to operator new/new[],operator delete/delete[] and their overloads.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值