C++ Memory System Part2: 自定义new和delete
在第一部分中,我们介绍了new / delete的具体用法和背后的实现细节,这次我们将构建我们自己的小型工具集,可以使用我们自定义的allocator类来创建任意类型的实例(或者实例数组),我们需要做好准备,因为这里面涉及到了函数模板,type-based dispatching,模板黑魔法,以及一些巧妙的宏定义。
理想中,我们准备做的自定义内存系统需要创建实例的语法大概像下面这样:
假如我们定义了一个负责内存分配的类Arena
Arena arena; // one of many memory arenas // ...
Test* test = new (arena, additionalInfo) Test(, , );
delete(test, arena); // no placement-syntax of delete // ...
Test* test = new (arena, additionalInfo) Test[];
delete[] (test, arena); // no placement-syntax of delete[]
我们可以让new operator像这样去工作,只需要重载operator new,然后使用placement new syntax即可。但是delete却不能如上一样,因为delete operator没有placement-syntax,也就是说它只能接收一个参数,如果直接调用operator delete,我们就会遇到上节我们提到的operator delete[]的析构问题,我们无法写出编译器无关的,跨平台的析构调用方法。
另外,我们想要在new的时候传递一些额外的信息,像文件名、行号、类名、内存标签等等,同时还x想尽量保留C++原始的new operator调用语法,所以,我们使用宏的方式来定义new operator,最终希望达到像下面的代码段这样,来使用自定义的new operator:
Test* test = OM_NEW(Test, arena)(, , );
OM_DELETE(test, arena); // ...
Test* test = OM_NEW_ARRAY(Test[], arena);
OM_DELETE_ARRAY(test, arena);
接下来就让我们挨个实现这些宏,以及一些底层的函数,从最简单的开始吧。
OM_NEW
就像最普通的new operator一样,ME_NEW首先需要为给定的类型分配内存,然后在该内存上调用其构造函数。实现起来比较简单,就一行代码:
#define OM_NEW(type, arena) new (arena.Allocate(sizeof(type), __FILE__, __LINE__)) type
我们要做的就是在我们自定义的Arena.Allocate()函数返回的内存地址上使用placement new,同时也传递进去一些我们需要的信息,文件名,行号。另外需要特别注意的是我们最后的type,它的作用就是为了给构造函数提供构造所需的参数,可以在调用时,将参数附在宏的后面,如下所示:
// Test is a class taking 3 ints in the constructor
Test* test = OM_NEW(Test, om)(, , );
// 宏展开后:
Test* test = new (om.Allocate(sizeof(Test), "test.cpp", )) Test(, , );
使用OM_NEW,我们可以使用自定义的内存分配函数,同时传递额外的信息给它。同时也可以保留了new operator原始的语法。
OM_DELETE
每个使用OM_NEW创建的实例,都需要调用OM_DELETE来删除。切记一点,没有placement形式的delete operator,所以我们要么直接调用operator delete,要么就使用完全不同的方法。无论是哪种方法,都要确保调用实例的析构函数。我们可以通过将删除操作延迟给一个help函数去执行来实现:
#define ME_DELETE(object, arena) Delete(object, arena)
help函数使用的是模板函数的方式:
template<typename T, class ARENA>
voidDelete(T* object, ARENA& arena)
{
// call the destructor first...
object->~T(); // ...and free the associated memory
arena.Free(object); }
编译器会帮我们推导出所有的类型参数,不需要我们显式指定任何模板参数。
OM_NEW_ARRAY
到这里事情就变得稍微复杂了一些。我们首先需要一个可以为N个实例分配内存的函数,同时能够使用placement new正确地调用构造函数。因为它需要适用所有类型,所以我们还是用函数模板的方式来实现:
template<typename T, class ARENA>
T* OM_NewArray_Helper(ARENA& arena, size_t N, const char* file, int line)
{
union
{
void* as_void;
size_t* as_size_t;
T* as_T;
}; as_void = arena.Allocate(sizeof(T)*N + sizeof(size_t), file, line); // store number of instances in first size_t bytes
*as_size_t++ = N; // construct instances using placement new
constT* const onePastLast = as_T + N;
while(as_T < onePastLast)
new(as_T++) T; // hand user the pointer to the first instance
return(as_T - N); }
上面的注释基本说明了代码的原理,我这里就提一点,就是我们在给N个实例分配内存的时候,额外分配了大小为sizeof(size_t)的空间,它的目的就是为了保存实例的数量。假如我们的sizeof(T) == 4,sizeof(size_t) == 4,那么我们分配出来的内存的布局如下:
Bytes -: N
Bytes -: T[]
Bytes -: T[]
Bytes -: T[]
返回给用户的是指针式偏移了sizeof(size_t)个字节的地址。最终的使用方法如下:
Test* t = OM_NewArray_Helper<Test>(arena, , __FILE__, __LINE__);
这个还有个小问题,从上面的使用样例可以看出,因为类型T并没有出现在函数的参数列表中(只是用于函数的返回值类型),所以编译器无法帮助我们直接推导出类型,所以我们必须在每次使用时显式指定类型Test,但是如果我们用宏来包裹这个函数的话,在宏里我们并不知道实例的类型,同时在宏里我们也不知道实例的数量,先看下我们设想的宏的使用方式:
Test* test = OM_NEW_ARRAY(Test[], arena);
为了使我们的宏能够像这样工作,该如何定义它呢?
#define ME_NEW_ARRAY(type, arena) OM_NewArray_Helper<?>(arena, ?, __FILE__, __LINE__)
宏里的问号就是我们现在还缺失的信息,那么如何获取到这部分信息呢,这时候就是模板黑魔法发挥作用的时候了:
template<class T>
structTypeAndCount
{
}; template<class T, size_t N>
structTypeAndCount<T[N]>
{
typedefT Type;
staticconstsize_tCount = N;
};
第一个基础模板TypeAndCount只定义了一个模板参数,别的什么都没有做,但是它却提供了部分偏特化的方式将type从T[N]中分离出来,这样N也可以在编译期获取到,最后宏的定义就成了:
#define OM_NEW_ARRAY(type, arena) NewArray<TypeAndCount<type>::Type>(arena, TypeAndCount<type>::Count, __FILE__, __LINE__)
可能很多人对这个黑魔法感觉到有点懵逼,所以下面以OM_NEW_ARRAY(Test[3],arena)为例来说明一下它到底是如何工作的:
首先是预处理的工作:
- 宏的TypeAndCount<type>::Type部分将会替换为TypeAndCount<Test[3]>::Type.
- 宏的TypeAndCount<type>::Count部分将会替换为TypeAndCount<Test[3]>::Count.
接下来是编译器的工作:
- TypeAndCount<type>::Type的局部偏特化会产生Test
- TypeAndCount<type>::Count的局部偏特化会产生3
就这样,我们将类型和数量两个值传递到了宏,从而避免再传递多余的参数给宏。
ME_DELETE_ARRAY
同样的,我们需要一个函数,帮我们实现几个功能:一是按照反序调用实例的析构函数,然后删除相应的内存。废话少说,直接看实现:
template <typename T, class ARENA>
void DeleteArray(T* ptr, ARENA& arena, NonPODType)
{
union
{
size_t* as_size_t;
T* as_T;
}; // user pointer points to first instance...
as_T = ptr; // ...so go back size_t bytes and grab number of instances
const size_t N = as_size_t[-]; // call instances' destructor in reverse order
for (size_t i=N; i>; --i)
as_T[i-].~T(); arena.Free(as_size_t-);
}
根据注释大家基本可以理解原理了,宏的实现也比较简单:
#define OM_DELETE_ARRAY(object, arena) DeleteArray(object, arena)
到这里,我们基本已经完成了我们的目标,实现了POD类型和NON-POD类型的自定义new / delete家族函数,但是这里面其实还有需要优化的地方,比如如果是POD类型的实例,我们不需要调用它的构造/析构函数,所以我们的NewArray和DeleteArray函数模板都可以优化。这可以通过类型派遣来实现(type-based dispatching),这里暂时不展开讨论了,留待下节详细介绍。
参考link:
https://stoyannk.wordpress.com/2018/01/10/generic-memory-allocator-for-c-part-3/
https://bitsquid.blogspot.com/2010/09/custom-memory-allocation-in-c.html
https://blog.molecular-matters.com/
C++ Memory System Part2: 自定义new和delete的更多相关文章
- 自定义new和delete
#include "stdafx.h" #include <stdlib.h> #include <malloc.h> #include <iostr ...
- C++ Memory System Part3 : 优化
前面的系列我们讲了自定义new和delete操作,其中针对deleteArray的问题还有需要优化的地方.我们这次就针对POD类型进行一次优化. 下面的代码是针对POD类型的模板函数实现,分别为New ...
- gem5: 使用ruby memory system中的mesh结构 出现AssertionError错误
问题:在使用ruby memory system中的mesh结构測试时,出现例如以下错误: Traceback (most recent call last): File "<stri ...
- PatentTips - Mechanisms for strong atomicity in a transactional memory system
BACKGROUND Advances in semi-conductor processing and logic design have permitted an increase in the ...
- Bit error testing and training in double data rate (ddr) memory system
DDR PHY interface bit error testing and training is provided for Double Data Rate memory systems. An ...
- Power management in semiconductor memory system
A method for operating a memory module device. The method can include transferring a chip select, co ...
- C++ Memory System Part1: new和delete
在深入探索自定义内存系统之前,我们需要了解一些基础的背景知识,这些知识点是我们接下来自定义内存系统的基础.所以第一部分,让我们来一起深入了解一下C++的new和delete家族,这其中有很多令人吃惊的 ...
- armv8 memory system
在armv8中,由于processor的预取,流水线, 以及多线程并行的执行方式,而且armv8-a中,使用的是一种weakly-ordered memory model, 不保证program or ...
- 自定义UITableViewCell 的delete按钮
自定义UITableViewCell上的delete按钮 滑动列表行(UITableViewCell)出现删除按钮时,默认是英文“delete”,这份代码片段能够将“delete”变成中文”删除“,甚 ...
随机推荐
- C# Task的使用
1.Task的使用 创建一个Task,有三种方式 //第一种 Task t1 = new Task(() => { Console.WriteLine(DateTime.Now.ToString ...
- The 'microsoft.jet.oledb.4.0' provider is not registered on the local machin
1,2选取目标站点,然后3的高级设置,4启用32位的应用程序的属性变为true就可以了.当然,网络上还有其他的版本,你也可以尝试下. 原文地址:http://weblogs.asp.net/kenco ...
- Sample-Code:Bing Search API
Demo link: http://code.msdn.microsoft.com/windowsazure/How-to-use-bing-search-API-4c8b287e Aspx Code ...
- Python3之requests模块
Python标准库中提供了:urllib等模块以供Http请求,但是,它的 API 太渣了.它是为另一个时代.另一个互联网所创建的.它需要巨量的工作,甚至包括各种方法覆盖,来完成最简单的任务. 发送G ...
- 推荐分享一个牛X的自定义PHP加密解密类
通俗点说,用它来进行加密,同一个字符串,每次进行加密,得出的结果都是不一样的,大大加强了数据安全性.同时还可设定加密后数据的有效期,简直牛掰了 #食用方法 将下面的第二份模块代码保存为 Mcrypt. ...
- if __name__ == '__main__'是什么意思?如何理解?看到一个很有用的解答
小明.py 朋友眼中你是小明(__name__ == '小明'), 你自己眼中你是你自己(__name__ == '__main__'), 你编程很好, 朋友调你去帮他写程序(import 小明, 这 ...
- libcaffe.so.1.0.0: cannot open shared object file: No such file or directory 运行时报错
caffe安装好后lib没有配置到/usr/lib或/usr/local/lib中,需手动配置: sudo vim ~/.bashrc export LD_LIBRARY_PATH=your_path ...
- JDK 5 ~ 10 新特性倾情整理!
JDK 5 ~ 10 新特性倾情整理! 最近连 JDK11都在准备发布的路上了,大家都整明白了吗?也许现在大部分人还在用6-8,8的新特性都没用熟,9刚出不久,10-11就不用说了. 为了大家对JDK ...
- angularjs中向html页面添加内容节点元素代码段的两种方法
第一种方式:原生JS向html页面添加内容节点元素代码段: <!DOCTYPE html> <html> <head> <meta charset=" ...
- Android 生成xml文件及xml的解析
1.生成xml文件的两种方式 (1)采用拼接的方式生成xml(不推荐使用) (2)利用XmlSerializer类生成xml文件 package com.example.lucky.test52xml ...