27

是否可以创建宏来替换所有形式的operator new重载,包括额外的 args...say__FILE____LINE__

问题似乎是operator new可以使用或不使用括号进行编码,因此:

  • 类对象宏:

    #define new new(__FILE__, __LINE__)
    

    将替换如下声明:

    A* a = new A();
    
  • 类似函数的宏:

    #define new(A) new (A, __FILE__, __LINE__)
    

    将替换如下声明:

    A* a = new(std::nothrow) A();
    

不幸的是,尝试用相同的标识符声明两个宏是错误的,即使它们是不同的类型,所以以下失败:

#define new new(__FILE__, __LINE__)
#define new(A) new (A, __FILE__, __LINE__) // Error: "new" already defined

由于我使用的是 g++,我希望使用他们的可变参数宏语法会取得成功,但不幸的是没有。以下:

#define new(...) new(__FILE__, __LINE__, ## __VA_ARGS__)

只匹配new(xyx) A(),不匹配new A()

我知道有文章写过为什么这是不可能的,但我觉得我已经很接近了,一定有办法。有什么明显的我失踪了吗?

4

7 回答 7

27

这是我使用的:

在新的.cpp

const char* __file__ = "unknown";
size_t __line__ = 0;

void* operator new(size_t size) {
    void *ptr = malloc(size);
    record_alloc(ptr,__file__,__line__);
    __file__ = "unknown";
    __line__ = 0;
    return ptr;
}

void delete(void *ptr)
{
   unrecord_alloc(ptr);
   free(ptr);
}

为了简洁起见,我省略了 new 和 delete 的其他定义。“record_alloc”和“unrecord_alloc”是维护包含ptr、行和文件的结构的链接列表的函数)。

在新的.hpp

extern const char* __file__;
extern size_t __line__;
#define new (__file__=__FILE__,__line__=__LINE__) && 0 ? NULL : new

对于 g++,“new”只扩展一次。关键是“&& 0”,它使它为假并导致使用真正的新的。例如,

char *str = new char[100];

由预处理器扩展为

char *str = (__file__="somefile.c",__line__=some_number) && 0 ? NULL : new char [100];

因此文件和行号被记录并调用您的自定义新函数。

这适用于任何形式的 new——只要在 new.cpp 中有相应的形式

于 2009-09-02T00:57:42.403 回答
8

您应该查看我的同事 Calvin 撰写的这篇出色的博客文章。我们最近遇到了一种情况,我们希望启用这种类型的修复,以便将内存泄漏与在诊断/调试构建中分配它们的行相关联。这是一个有趣的技巧

https://docs.microsoft.com/en-us/archive/blogs/calvin_hsia/overload-operator-new-to-detect-memory-leaks

于 2009-03-06T16:40:40.033 回答
4

3.7.4 动态存储时长

2 该库为全局分配和释放函数提供默认定义。一些全局分配和释放函数是可替换的 (18.5.1)。C++ 程序应最多提供一个可替换分配或解除分配函数的定义。任何此类函数定义都会替换库中提供的默认版本 (17.6.4.6) [...]

17.6.4.6 替换函数

  1. C++ 程序可以为头文件(3.7.4,第 18 条)中声明的八个动态内存分配函数签名中的任何一个提供定义:

    • 运算符新(std::size_t)
    • 运算符新(std::size_t,常量 std::nothrow_t&)
    • 运算符 new[](std::size_t)
    • 运算符 new[](std::size_t, const std::nothrow_t&)
    • 运算符删除(无效*)
    • 运算符 delete(void*, const std::nothrow_t&)
    • 运算符 delete[](void*)
    • 运算符 delete[](void*, const std::nothrow_t&)

希望这能澄清什么是法律超载,什么不是。

这里的一些人可能会感兴趣:

#define delete cout <<  "delete called at: " << __LINE__ << " of " << __FILE__  << endl, delete 

using namespace std;

void *operator new(size_t size, ostream& o, char *f, unsigned l) {
    o << "new called at: " << l << " of " << f << endl;
    return ::new char[size];
}

int main() {
    int *a = new(cout, __FILE__, __LINE__) int;
    delete a;
}

警告讲师:我在这里做的是一件坏事(TM)——在全球范围内超载 new/delete。

于 2009-03-06T16:56:38.280 回答
2

我发现以下库“ nvwa ”对于跟踪新/删除内存泄漏非常有用——查看文件“debug_new”作为示例,或者直接使用它。

于 2009-03-10T12:02:35.363 回答
1

您没有说您使用的是什么编译器,但至少使用 GCC,您可以覆盖 new 并记录调用者地址,然后使用 addr2line 将其转换为文件/行信息(或使用 BFD 库立即执行此操作)。

于 2009-03-06T16:40:59.690 回答
1

您可以做的是重载运算符 new 并在那里获取堆栈跟踪(特定于平台)并使用堆栈信息推断调用 new 的位置。

于 2009-04-06T20:43:31.310 回答
-2

不,没有办法。

你可以在过去的糟糕日子里做到这一点,malloc()/free()但不能做到new

您可以通过全局覆盖运算符来替换内存分配器new,但不能注入您正在谈论的特殊变量。

于 2009-03-06T16:31:50.197 回答