检测动态分配的对象?

4

我能够检查通过指针或引用传递的对象是否是动态分配的吗?

示例:

T t;
T* pt = new T();
is_tmp(&t); // false
is_tmp(pt); // true

上下文

我非常清楚这看起来像是糟糕的设计,实际上也确实如此,但我正在尝试扩展我不能(或者不应该)修改的代码(当然我会责怪那些不是我的代码 ;) )。它调用一个方法(我可以覆盖),该方法将删除传递的对象,还有其他仅适用于动态分配对象的事项。现在,我想检查我是否有可以删除的东西,或者它是一个临时的。

我永远不会传递全局(或静态)变量,所以我在这里留下了未定义的部分。


可能是重复的问题:如何在构造函数中判断对象是静态分配还是动态分配?(http://stackoverflow.com/questions/1014440/how-can-i-tell-if-an-object-is-statically-or-dynamically-allocated-on-the-constr) - BЈовић
请注意,另一个(虽然较旧的)问题已被关闭为此问题的重复,因此将此问题关闭为其他问题的重复可能不是一个好主意(循环引用)。 - Damon
enum class Alloc { by_static, by_thread, dynamic, automatic }; then T t{Alloc::automatic}; or T* pt = new T{Alloc::dynamic}; - Eljay
5个回答

6
不能移植。在PC上的Solaris或Linux系统(至少是32位Linux)中,堆栈位于可用内存的顶部,因此您可以将传入的地址与本地变量的地址进行比较:如果传入的地址高于本地变量的地址,则指向它的对象可能是一个局部变量或临时变量,或者是局部变量或临时变量的一部分。然而,这种技术引发了未定义行为 - 它只在我提到的两个平台上奏效(并且在所有堆栈位于可用内存顶部且向下增长的平台上可能都奏效)。
顺便说一句,在这些机器上还可以检查静态数据。所有的静态数据都位于内存底部,链接器会在它们的末尾插入一个名称为end的符号。因此,声明一个带有此名称的外部数据(任何类型),并将其地址与之比较。
然而,关于可能删除对象...仅仅知道对象不在堆栈(也不是静态数据)上是不够的。该对象可能是更大的动态分配对象的成员。

嗯,我也有类似的想法,但我的潜意识立刻将其丢弃为“危险和可怕”。谢谢你确认了这一点 :) - bitmask
检查指针的值是一个非常有趣的想法,但不知为何感觉非常错误 :-) 如果对象是动态分配对象的成员,会怎样呢? - Kerrek SB
@bitmask 这绝对不是我想在可移植代码中尝试的事情,或者如果我的应用程序依赖于它。 我曾经在一些assert中使用过它,以确保对象是动态分配的; 有更好的方法来做到这一点,但那时我只使用了C++ 6个月,并不知道它们。 当然,如果测试曾经引起问题,我们可以删除assert - James Kanze
1
如果您正在运行多线程,则可能会遇到问题,此时额外线程的堆栈通常分配在堆上... - Chris Dodd
@Chris Dodd,这也是一个问题。虽然你可能可以通过一个公共点运行所有线程启动函数并跟踪它们的堆栈地址。但在线程出现之前,我就已经使用过它了,所以这个问题从未出现过。(正如我所说:我没有在生产代码中使用它——只用于调试。我绝对不建议这样做。) - James Kanze

3
一般来说,像DeadMG所说的那样,你无法从指针中得知它的来源。然而,作为一种调试、移植或分析措施,你可以在你的类中添加一个成员operator new来跟踪动态分配(前提是没有人使用显式全局::new,这包括容器,我很抱歉)。然后,你可以建立一个set<T*>的动态分配内存,并在其中搜索。
这对于任何严肃的应用程序都不太适用,但也许这可以帮助你追踪东西的来源。你甚至可以在你的操作符中添加带有行号的调试消息。

当然对于调试很有用,但我已经知道谁调用了什么,所以这属于严肃应用 - bitmask
@bitmask: 在生产代码中,您对一个函数的要求,以可选方式删除对象,会带来很多麻烦。(例如,您可能永远无法在容器中安全地使用您的对象。)new重载的问题是,您无法阻止人们使用::new。因此,真正没有好的解决方案,而任何您所做的事情都会取决于您能否保证所有绕过您跟踪的事情都不会发生。 - Kerrek SB
我知道,“麻烦”这个词甚至无法准确描述我在这里所做的事情。 - bitmask
你可以重载全局的 ::new(以及 ::new[] 和 ::delete 和 ::delete[]),并跟踪每个分配在堆上的内存块,但这既不便宜也不容易。 - Chris Dodd
@Chris:你不能重载全局的placement new。(有非常好的理由!) - Kerrek SB

0
不,这是不可能知道的。你应该修复这个错误。在最少的情况下,你可以使用智能指针(如shared_ptr),并给它一个空的自定义析构函数,如果你不想让它被删除。

我正在扩展/接口与我无法更改的代码(特别是不更改签名)。因此,我的选择非常有限。但是,我可以随意装饰对象,所以我会选择这个,但如果您可以使用一些语言内置功能来检查,我希望避免这种开销。 - bitmask

0

如果你可以访问动态内存分配器的代码本身,你可以扫描内部结构,看看当前指针是否在其分配列表/堆栈/区域或任何存储方式中。通常它们被存储为链表样式的结构体,并且扫描变量地址也不是太难。


0
在我看来,这应该是可能的,因为你可以检查内存是在堆还是栈上。这将成为高度平台依赖的代码。
首先,你必须获取堆的范围,然后你必须检查传递的内存地址是否在此范围内...(听起来很简单,但第一步可能有些棘手 :-) )

这当然是可能的(请参见James Kanze的回答),但这是一种可怕的hack方式,所以我不会像这样做。 - bitmask

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接