【问题标题】:Recursive calls when trying to wrap and override the global operator new尝试包装和覆盖全局运算符 new 时的递归调用
【发布时间】:2009-12-13 16:39:18
【问题描述】:

我有一段时间没有编写 C++ 了,在玩弄重载的全局运算符 newdelete 时遇到了一个奇怪的行为。 问题的本质似乎是围绕默认全局 new 构建的包装器并驻留在 separate 源文件中 尽管如此,还是调用了在另一个(并单独编译的)源文件中重载的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 调用,反之亦然,导致以下输出: p>

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

编辑/更新

如果(正如第一个答案和 cmets 建议的那样)全局 operator new 仅通过在单个编译单元中重载它 [运算符] 就有效地为整个可执行文件重新定义,那么:

仅仅是链接一个源文件重载全局operator new 的目标文件是否会使任何应用程序或库更改其(好吧,让我们这么称呼它)内存分配策略?因此,这个重载运算符是否有效地为语言运行时提供了一个钩子(我的意思是那些已经编译的没有任何重载的新目标文件中的链接器符号是什么,是否只能有一个 new)?

附:我知道mallocfree 会这样做,并且在发布到这里之前已经尝试过它们(工作正常),但是,这种行为背后的原因是什么(如果我真的要包装默认的operator new 怎么办?:) ) ?

【问题讨论】:

  • 顾名思义,global operator new 就是 global。
  • @Neil 那么链接呢(对当前编译单元来说不是全局的吗)?
  • mlvljr,您可以在下划线前放置一个 \(反斜杠)以避免将以下字符呈现为斜体。但通常,当您引用变量名时,最好将其呈现为代码,例如 varname
  • 谢谢大家,我已经投票给大家了。

标签: c++ operator-overloading new-operator


【解决方案1】:

如果您阅读了标准中的措辞,您在定义自己的函数时对全局 operator new 函数所做的操作称为 replacement(不是 重载 而不是 覆盖)。大多数时候,当人们谈论更改全局operator new 功能时,他们使用术语“重载”并将new 称为“操作员”。当结果行为与 运算符重载 的正常预期不一致时,这通常会导致混淆。这显然是你的情况。你期望重载行为,但你真正得到的是替换:一旦你在某个翻译单元中定义了全局operator new函数的一个替换版本,它就适用于整个程序。 (而且new 并不是真正的运算符。)

作为旁注,替换 是那些罕见的“不公平”语言功能之一,从某种意义上说,它通常对用户不可用。您不能在 C++ 中编写“可替换”函数。您不能编写任何与全局 operator newoperator new 的默认库实现的行为方式相同的东西。 (替换是弱符号的链接器概念的语言级表现)。

【讨论】:

  • 接受这个作为对尼尔评论的补充:))
  • 但是为什么 gcc 允许在全局命名空间中使用相同的签名覆盖 operator new 呢? VS 不允许(链接器给出错误提示名称已定义)。
  • @Chubsdad:我不明白你在说什么。 GCC 和 MSVC 都允许替换全局 operator new
【解决方案2】:

我认为在 Neil 的评论之后没有什么要补充的,但是如果你重载全局 operator new,你不能只为一个翻译单元这样做,new 将重载整个可执行文件或 dll。

您已经注意到从您自己重载的全局运算符 new 中调用全局运算符 new,您将获得无限递归(嗯,至少直到您用完堆栈空间;)

如果您想玩弄这个,请尝试在包装器中使用 mallocfree 来启动和运行。

如果运算符仅在一个翻译单元本地,如果您将分配有new 的一个版本的对象传递给另一个翻译单元并尝试使用另一个不匹配的运算符@ 删除它,您将遇到麻烦987654325@.

运算符 newdelete 的详细信息在 C++ 标准的第 18.4 节中介绍。

【讨论】:

  • 所以,它是全局的,不同于全局变量,对吧?我在哪里可以阅读此规则(可能还有理由)(即标准,“C++ 编程语言”一书)?
  • P.S. Malloc 和 free 运行良好,这种奇怪的行为让我眼花缭乱。
【解决方案3】:

为了扩展 Neil 的评论,“global” new 可能没有被赋予 static 存储类说明符,因此是“global” - 它对模块外部的链接器可见(可以从任何其他模块,例如)。

无效:

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 而无需递归)。

不要忘记在分配失败的情况下抛出异常,并实现重载的无抛出版本。

【讨论】:

  • 啊哈!所以不同的编译单元就不能依赖自己的new操作符了..
  • 在模块中声明的任何非静态的东西对模块外部的链接器都是可见的。这本质上是“extern”的目的——告诉编译器“这个 var/func/etc 是在另一个模块中声明的”。最好将您不希望从模块(源文件)外部引用的任何内容设为 static ,但在 operator new 的情况下这是不允许的。
【解决方案4】:

放大 AndreyT 所说的话:

当您编写自己的全局operator new 函数时,您提供了链接器可以看到的定义。当涉及到链接时间时,链接器将需要找到new 的定义,因为它被大量使用。它开始在您要求它链接的目标文件中搜索,嘿,它就在那里,所以它可以停止搜索。

如果您没有提供定义,它将继续搜索您的任何其他目标文件,然后搜索您提供的库,最后搜索您的编译器附带的运行时,它必须提供一个或什么都不会链接。

【讨论】:

    【解决方案5】:

    查看 GNU C++ 标准库,您会看到类似这样的装饰,意思是“弱链接”:

    _GLIBCXX_WEAK_DEFINITION void *
    operator new (std::size_t sz) throw (std::bad_alloc)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-08
      • 2011-01-25
      相关资源
      最近更新 更多