[原] inline operator delete & DLL boundary

之前写在百度空间的这篇文章:

[百度空间] [原] 全局operator delete重载到DLL

首先,纠正一个词“重载”,operator new/delete是替换(replacement),不是重载。只要任意一个编译单元定义了替换函数,并不需要全局声明,就会替换掉全局的默认operator new/delete, 这个行为非常像gcc的strong symbol/weak symbol。

最近翻看了C++03的标准:

3. The program’s de?nitions are used instead of the default versions supplied by the implementation (18.6). Such replacement occurs prior to program startup(3.2, 3.6). The program’s de?nitions shall not be speci?ed as inline. No diagnostic is required.

也就是说标准是不允许inline operator new/delete。原因也很简单, new/delete是replacement,是链接级别的符号替换。如过是inline的话,很有可能没有生成符号,也就无法实现replacement,导致只有包含该头文件,引用声明的编译单元才会有replacement, 这样的行为与“全局替换”相悖。

然而标准又说No diagnostic is required, 估计是因为兼容性的原因,毕竟这个标准出来的时候,已经有很多编译器支持这么做了。所以MSVC最多会给一个警告,Clang后面也改成了可关闭的警告。

所以使用inline operator new/delete是不符合标准的,可能产生问题的,当然,有把握(运气)的话,并不会出错。比如我之前一直在用, 一些有名的开源项目也在用。

但这个方式给我一个很大的便利,就是可以fall back to default operator new:

默认情况下,只要任何一个文件定义了operator new/delete, 就会replacment,所以默认的new/delete已经被覆盖了,已经没有办法可以访问。
在使用了inline operator delete以后, 在自定义的operator delete里,仍然可以调用默认的operator delete。

//global.h
extern void __declspec( dllexport ) BaldeGlobalDelete(void* ptr);

inline void operator delete(void* ptr)
{
    return BaldeGlobalDelete(ptr);
}

//mem.cxx
#include <global.h>

extern void DefaultDelete(void* ptr);

void BaldeGlobalDelete(void* ptr)
{
    //note this is a demo code that not used in practice
    int* p = (int*)ptr-1;
    int magic = *p;
    if(magic == BLADE_MEM_MAGIC)
        BladeInternalFree(p);
    else
        DefaultDelete(ptr);
}

//defaultdelete.cxx
//this file doesn‘t include global.h
//inline operator delete is not visible and no replacement happens!//if define operator delete without inline(and in .cpp), replacement always happens, whether its decl is visible or not (correct/std conformed behavior)
void DefaultDelete(void* ptr)
{
    //call the built in delete
    return ::operator delete(ptr);
}

如过不加inline(并把定义写在cpp里)的话,replacement必然发生,所以DefaultDelete不会调用默认delete,而会递归调用自定义的operator delete,BaldeGlobalDelete,这样栈就溢出了。

有了inline(MSVC的debug build还需要改优化选项启用inline), 诡异的事情就发生了,实际上没有真正的replacment, 只要不包含global.h头文件,就能fall back到默认的delete。

这个是非常tricky的方式, 主要为了保证new/delete的一致性,兼容三方默认的new。其实可移植性和稳定性都不好,算是反面教材。最近为了standards-conformation,去掉了这种hack,使用了更标准的方式, 去掉了global operator delete 的替换。

关于DLL boundary的问题。

有很多方式可以解决这个问题,比如类COM的方式 (virutal destroy), 或者factory + deleter,或者使用统一的allocation routine (比如LocalAlloc之类).

Blade使用以下规范来保证:

1.跨DLL类 必须是纯虚基类, 或者,虚析构+类内自定义operator new/delete, 这样的便利是,可以直接跨DLL delete, C++标准保证。

基础类型的分配和释放,比如new int[count], 不允许跨DLL管理,实际上,不允许跨函数管理。个人认为strdup这种需要用户调用free的设计不是良好的设计。

使用shared_ptr + deleter是最好的方式,但目前暂不考虑,因为现在仍然基于C++03.

2.公共头文件不允许包含<vector> <string>等容器, 以避免不同版本stl对象的内存分布问题

对于跨DLL的情况,不管用以上的哪种方式,对于DLL可卸载的情况,仍然会有问题,比如DLL/so卸载以后, 不管是virtual destory还是deleter,因为二进制可执行代码已经unmap出进程地址空间不可访问,所以需要良好的

生命周期管理和cleanup routine。也可以偷懒,禁止卸载,比如使用GET_MODULE_HANDLE_EX_FLAG_PIN/RTLD_NODELETE。

时间: 2024-08-09 01:58:27

[原] inline operator delete & DLL boundary的相关文章

[百度空间] [原] 全局operator delete重载到DLL

由于很久没有搞内存管理了,很多细节都忘记了今天项目要用到operator delete重载到DLL,发现了问题,网上搜索以后,再对比以前写的代码,发现了问题:原来MSVC默认的operator new(size_t) 和operator delete(void*)虽然可以重载, 但是不能封装到DLL中,编译会报C2375 DLL linkage错误,解决方法很简单: 用inline函数包一下DLL导出就可以了 同时为了不跟MFC等三方库的operator new 冲突,最好加一个自定义参数:ty

C++中的new/delete与operator new/operator delete

new operator/delete operator就是new和delete操作符,而operator new/operator delete是函数. new operator(1)调用operator new分配足够的空间,并调用相关对象的构造函数(2)不可以被重载 operator new(1)只分配所要求的空间,不调用相关对象的构造函数.当无法满足所要求分配的空间时,则        ->如果有new_handler,则调用new_handler,否则        ->如果没要求不

C++面向对象高级编程(九)Reference与重载operator new和operator delete

摘要: 技术在于交流.沟通,转载请注明出处并保持作品的完整性. 一 Reference 引用:之前提及过,他的主要作用就是取别名,与指针很相似,实现也是基于指针. 1.引用必须有初值,且不能引用nullptr 2.引用之后不能再引用别人 3.引用通常不用于声明变量,多用于参数类型,和返回值类型 见下面代码 int main(int argc, const char * argv[]) { int x=0; //p is a pointer to x int* p = &x; // r is a

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳、operator new 和 operator delete 实现一个简单内存泄漏跟踪器

C++ Primer 学习笔记_29_操作符重载与转换(4)--转换构造函数和类型转换运算符归纳.operator new 和 operator delete 实现一个简单内存泄漏跟踪器 一.转换构造函数 可以用单个实参来调用的构造函数定义从形参类型到该类型的一个隐式转换.如下: class Integral { public: Integral (int = 0); //转换构造函数 private: int real; }; Integral A = 1; //调用转换构造函数将1转换为In

微信SDK导入报错 Undefined symbols for architecture i386:&quot;operator delete[](void*)&quot;, referenced from:

异常信息: Undefined symbols for architecture i386:  "operator delete[](void*)", referenced from:      +[WeChatApiUtil EncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)      +[WeChatApiUtil NsDataEncodeBase64:] in libWeChatSDK.a(WeChatApiUtil.o)    

operator delete异常分析

C 中delete表达式执行的操作是:1,调用析构函数:2,释放对象内存(operator delete(…)). 如果父类的析构函数没有声明为virtual函数,且子类中至少存在一个virtual函数,此时将子类的对象地址赋值给父类指针.当对父类的指针执行delete操作时,会调用父类析构函数,然后在释放内存时(即delete表达式执行的操作的2,释放对象内存)出现崩溃. 然而如果子类中不存在一个virtual函数时,执行上面同样的操作就不会出现崩溃. 原因分析如下: //已知本示例 父类的析

C++ new operator, delete operator, operator new, operator delete, new placement

http://www.younfor.com/cpp-new-placement-new-operator-new.html http://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html http://kelvinh.github.io/blog/2014/04/19/research-on-operator-new-and-delete/ new operator 就是C++中定义的关键字new,调用new而触发的行为,del

C++中的::operator new, ::operator delete

一般在使用new  和 delete的时候,做了两件事情,一是空间的配置( new 是分配,delete是回收),而是调用对象的析构函数 但是也有办法将这两个过程分开 那就是显式的调用::operator new, ::operator delete,它们只进行空间配置,并不调用对象的析构函数 具体的可以参看下面这个例子: // operator new[] example #include <iostream> // std::cout #include <new> // ::o

条款八: 写operator new和operator delete时要遵循常规

自己重写operator new时(条款10解释了为什么有时要重写它),很重要的一点是函数提供的行为要和系统缺省的operator new一致.实际做起来也就是:要有正确的返回值:可用内存不够时要调用出错处理函数(见条款7):处理好0字节内存请求的情况.此外,还要避免不小心隐藏了标准形式的new,不过这是条款9的话题. 有关返回值的部分很简单.如果内存分配请求成功,就返回指向内存的指针:如果失败,则遵循条款7的规定抛出一个std::bad_alloc类型的异常.operator new实际上会不