在昨天写C++ Json解析库时遇到了很难找的Bug,利用对象池管理不同类型的对象,这些对象不是相互独立的而是互相依赖的。这就导致了在最后对象池执行析构时释放所有的对象出现了问题,具体如下。
- Node类,是一个泛化的值,底层利用union/variant以实现可以容纳Json要求的所有类型的值,Number,Null,True,False,String,Array,Object等等。其中String,Array和Object时特殊的,其中含有指针用来指向分配好的对象,这就涉及到了析构。
- String类,其中有char类型的指针指向用来保存字符串的内存,这些内存由一全局分配器分配(命名为:GlobalMemoryAllocator)。
- Array类,其中存有Node的指针的数组,Node的空间由对象池(NodePool)分配并管理。
- Object类,其中存的是ObjectMember的指针的数组,ObjectMember的空间由对象池(ObjectMemberPool)分配并管理。
- ObjectMember类,其中有String作为对象属性的名字,有Node保存具体的属性的值,前面已经说过,String中有需要GlobalMemoryAllocator分配内存的部分,而Node中又有NodePool和ObjectMemberPool分配并管理内存的部分,这就造成了一定程度上的依赖。
创建对象时没问题
- 创建对象需要GlobalMemoryAllocator、NodePool和ObjectMemberPool分配内存,只需在main函数最前面创建好各自的单例就好了。
析构对象时问题很大
-
考虑下图场景
-
我们没有手动利用对象池的API析构对象,当最后退出程序时,对象池的析构函数析构其中没有析构的对象,并最终释放所有内存。
-
蓝色为NodePool管理,绿色为ObjectMemberPool管理,橙色为GlobalMemoryAllocator分配(不管理)
-
GlobalMemoryAllocator析构
-
ObjectMember执行析构,
objectMember析构 --> node2释放 --> 字符串释放(–Crash–)- 当字符串释放时,全局内存分配器已经无效。
-
NodePool执行析构,node1析构 --> 析构objectMember(-- Crash --)
- 因为ObjectMemberPool已经析构,无法为objectMember提供释放功能。
解决方法
- 手动创建全局Pool/Allocator对象, 手动析构,确保按顺序析构,不能用单例常用的static变量来获得全局对象池/内存管理器,因为这些static对象的析构顺序不能被保证。
- 正确的析构顺序是,
NodePool --> ObjectMemberPool --> GlobalMemoryAllocator