我没有编程C ++一段时间,并且在使用重载的全局运算符new
和delete
进行操作时遇到了奇怪的行为。
问题的本质似乎是围绕默认全局new
并且驻留在单独的源文件中的包装器构建
然而,在另一个(并单独编译)源文件中调用operator new
重载。
为什么会这样,即我违反/滥用哪种语言规则/功能?
提前致谢,详情如下。
项目结构:
.
..
main.cpp
mem_wrappers.h
mem_wrappers.cpp
项目文件内容:
的main.cpp
#include "./mem_wrappers.h"
#include <iostream>
#include <cstring>
void* operator new[] (size_t sz) throw (std::bad_alloc) {
std::cout << "overloaded new()[]" << std::endl;
return default_arr_new_wrapper(sz);
}
int main() {
const unsigned num = 5;
int * i_arr = new int [num];
return 0;
}
mem_wrappers.h
#include <cstring>
void * default_arr_new_wrapper(size_t sz);
mem_wrappers.cpp
#include <new>
#include <cstring>
#include <iostream>
void * default_arr_new_wrapper(size_t sz) {
std::cout << "default_arr_new wrapper()" << std::endl;
return ::operator new[](sz);
}
在运行时遵循 g ++ main.cpp mem_wrappers.cpp --ansi --pedantic -Wall 给operator new[]
default_arr_new_wrapper
无休止的呼叫,反之亦然输出:
overloaded new()[]
default_arr_new_wrapper()
overloaded new()[]
default_arr_new_wrapper()
...
,最后,在SO中(MS Visual Studio Express编译器的行为相似)。
我使用的编译器: gcc版本3.4.2(mingw-special)和 MS Visual Studio 2008版本9.0.30729.1 SP 。
编辑/更新
如果(作为第一个答案和评论建议)全局operator new
只是通过在单个编译单元中重载[运算符]而有效地重新定义整个可执行文件,那么:
仅仅链接一个源重载全局operator new
的目标文件会使任何应用程序或库改变它(好吧,让它们调用它)内存分配策略?因此,这个重载运算符有效地为语言运行时提供了一个钩子(我的意思是在已经编译的,没有任何重载的新对象文件中使用那些链接器符号,是否只有一个new
)?
P.S。我知道malloc
和free
会这样做,并且在发布之前已经尝试过了(工作正常),但是,这种行为的背后是什么(如果我真的要包装默认{{1无论如何?:))?
答案 0 :(得分:8)
如果您阅读标准中的措辞,那么在定义自己的时候对全局operator new
功能所做的操作称为替换(不是重载而不是覆盖)。大多数情况下,当人们谈论更改全局operator new
功能时,他们会使用术语“重载”,并将new
称为“运算符”。当结果行为与运算符重载通常所期望的行为不一致时,这通常会导致混淆。这显然是你的情况。您希望重载行为,但您真正得到的是替换:一旦您在某个翻译单元中定义了一个替换版本的全局operator new
函数,它就适用于整个程序。 (并且new
实际上不是运营商。)
作为旁注,替换是一种罕见的“不公平”语言功能,从某种意义上说,用户通常无法使用。你不能在C ++中编写“可替换”函数。您不能编写任何与全局operator new
和operator new
的默认库实现方式相同的行为。 (替换是弱符号的链接器概念的语言级表现形式。)
答案 1 :(得分:2)
我认为在Neil的评论之后还有很多内容需要补充,但是如果你重载全局operator new,你不能只为一个翻译单元做,new
将为整个可执行文件或dll重载。
正如您已经注意到在您自己的重载全局运算符new中调用全局运算符new,您将获得无限递归(好吧,直到您至少耗尽堆栈空间;)
如果您想玩这个,请尝试使用包装内的malloc
和free
启动并运行。
如果操作员只是一个翻译单元的本地操作员,如果您将一个版本为new
的对象传递给另一个翻译单元并尝试将其与另一个翻译单元一起删除,则会遇到麻烦。 operator delete
。
运算符new
和delete
的详细信息将在C ++标准的第18.4节中介绍。
答案 2 :(得分:1)
为了扩展Neil的注释,“global”new可能不会被赋予static
存储类说明符,因此是“全局的” - 它对于模块外部的链接器是可见的(可以是extern'd例如,来自任何其他模块。
无效:
static void* operator new(size_t sz) {}
有效:
//in main.cpp
void* operator new(size_t sz) {}
//in foo.cpp
extern void* operator new(size_t sz);
根据您的目的,最好的方法是在您的定义中使用malloc / new,或者在您的覆盖中添加一个参数,以便签名发生变化(然后您可以回退到默认的全局new而不会递归)。
不要忘记在分配失败的情况下抛出异常,并实现重载的无抛出版本。
答案 3 :(得分:1)
扩大AndreyT所说的内容:
当您编写自己的全局operator new
函数时,您提供了链接器可以看到的定义。当谈到链接时,链接器需要找到new
的定义,因为它使用了很多。它开始在您要求它链接的目标文件中进行搜索,并且它会在那里进行搜索,因此它可以停止搜索。
如果您没有提供定义,它将继续搜索您的任何其他目标文件,然后通过您提供的库,最后通过编译器附带的运行时,必须提供一个或什么都没有联系。
答案 4 :(得分:1)
查看GNU C ++标准库,你会看到像这样的装饰,意思是“弱链接”:
_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) throw (std::bad_alloc)