C ++类可以确定它是在堆栈还是堆上?

时间:2010-01-13 05:47:24

标签: c++ stack heap

我有

class Foo {
....
}

有没有办法让Foo能够分开:

function blah() {
  Foo foo; // on the stack
}

function blah() {
  Foo foo* = new Foo(); // on the heap
}

我希望Foo能够做出不同的事情,具体取决于它是在堆栈还是堆上分配。

编辑:

有很多人问我“为什么这样做?”

答案:

我现在正在使用重新计算的GC。但是,我希望能够运行mark&扫一扫。为此,我需要标记一组“根”指针 - 这些是堆栈上的指针。因此,对于每个类,我想知道它们是在堆栈中还是在堆中。

15 个答案:

答案 0 :(得分:14)

一种愚蠢的方式:

struct Detect {
   Detect() {
      int i;
      check(&i);
   }

private:
   void check(int *i) {
      int j;
      if ((i < &j) == ((void*)this < (void*)&j))
         std::cout << "Stack" << std::endl;
      else
         std::cout << "Heap" << std::endl;
   }
};

如果对象是在堆栈上创建的,那么它必须位于外部函数堆栈变量的方向上。堆通常从另一侧生长,因此堆栈和堆将在中间某处相遇。

(确实存在不起作用的系统)

答案 1 :(得分:10)

你需要真正问我们真正的问题:-)你很明白为什么你认为这是必要的,但几乎可以肯定不是。事实上,这几乎总是一个坏主意。

为什么你认为你需要这样做?

我通常会发现这是因为开发人员希望根据分配的位置删除或删除对象,但这通常应留给代码的客户端而不是代码本身。


更新

道歉,你可能已经找到了你要问的几个领域之一。理想情况下,您将覆盖所有内存分配和取消分配运算符,以跟踪从堆中创建和删除的内容。

但是,我不确定拦截该类的新/删除是一件简单的事情,因为可能存在未调用delete的情况,并且由于标记/扫描依赖于引用计数,需要能够截取指针赋值才能使其正常工作。

你有没有想过如何处理这个问题?

经典例子:

myobject *x = new xclass();
x = 0;

不会导致删除电话。

另外,您如何检测到其中一个实例的指针在堆栈中?截取new和delete可以让你存储对象本身是堆栈还是基于堆,但是我不知道如何分配指针的位置,特别是代码如下:

myobject *x1 = new xclass();  // yes, calls new.
myobject *x2 = x;             // no, it doesn't.

答案 2 :(得分:8)

答案是否定的,没有标准/便携方式来做到这一点。涉及重载新操作员的黑客往往会有漏洞。依赖于检查指针地址的黑客是特定于操作系统和特定于堆的实现,并且可能随着操作系统的未来版本而改变。您可能对此感到满意,但我不会围绕此行为构建任何类型的系统。

我会开始寻找不同的方法来实现你的目标 - 也许你可以有一个完全不同的类型作为你的方案中的“根”,或者要求用户(正确)注释堆栈分配的类型,如此一个特殊的构造函数。

答案 3 :(得分:4)

更直接,更少侵入性的方法是在内存区域映射(例如/proc/<pid>/maps)中查找指针。每个线程都有一个分配给其堆栈的区域。静态和全局变量将存在于.bss section中,rodata或const段中的常量等等。

答案 4 :(得分:4)

可以将'this'的值与堆栈指针的当前值进行比较。如果这个&lt; sp然后你被分配到堆栈中。

试试这个(在x86-64中使用gcc):

#include <iostream>

class A
{
public:
    A()
    {
        int x;

        asm("movq %1, %%rax;"
            "cmpq %%rsp, %%rax;"
            "jbe Heap;"
            "movl $1,%0;"
            "jmp Done;"
            "Heap:"
            "movl $0,%0;"
            "Done:"
            : "=r" (x)
            : "r" (this)
            );

        std::cout << ( x ? " Stack " : " Heap " )  << std::endl; 
    }
};

class B
{
private:
    A a;
};

int main()
{
    A a;
    A *b = new A;
    A c;
    B x;
    B *y = new B;
    return 0;
}

应输出:

Stack 
Heap 
Stack 
Stack 
Heap

答案 5 :(得分:3)

我不是肯定你在问什么,但是覆盖new运算符可能就是你想要做的。由于在C ++中在堆上创建对象的唯一安全方法是使用new运算符,因此可以区分堆上存在的对象与其他形式的内存。谷歌“在c ++中重载新内容”以获取更多信息。

但是,您应该考虑是否真的需要从类内部区分这两种类型的内存。根据存储的位置,对象的行为会有所不同,如果你不小心的话,听起来像是一个灾难的秘诀!

答案 6 :(得分:3)

如上所述,您需要通过重载的new运算符来控制对象的分配方式。但要注意两件事,首先是'placement new'操作符,它在用户预先分配的内存缓冲区中初始化你的对象;第二,没有什么能阻止用户简单地将任意内存缓冲区转换为你的对象类型:

char buf[0xff]; (Foo*)buf;

另一种方式是大多数运行时使用比执行堆分配时要求的内存更多的内存。他们通常在那里放置一些服务结构,以通过指针识别正确的解除分配。您可以检查这些模式的运行时实现,但它会使您的代码真的不可移植,危险且不可支持的过度杀伤。

同样,如上所述,当你应该询问你为这个解决方案设计的初始问题(“为什么”)时,你真的要求解决方案细节(“如何”)。

答案 7 :(得分:2)

不,不能可靠或明智地完成。

您可以通过重载new来检测何时使用new分配对象。

但是如果对象被构造为类成员,并且在堆上分配了拥有的类呢?

这是第三个代码示例,添加到您已经拥有的两个:

class blah {
  Foo foo; // on the stack? Heap? Depends on where the 'blah' is allocated.
};

静态/全局对象怎么样?除了堆栈/堆栈之外,你怎么能告诉他们呢?

您可以查看对象的地址,并使用它来确定它是否在定义堆栈的范围内。但是堆栈可能会在运行时调整大小。

所以,最好的答案是“有一个原因为什么标记&amp;扫描GC不用于C ++”。 如果你想要一个合适的垃圾收集器,请使用另一种支持它的语言。

另一方面,大多数经验丰富的C ++程序员发现,当你学习了必要的资源管理技术(RAII)时,垃圾收集器的需要几乎消失了。

答案 8 :(得分:2)

MFC课程的一种方式:

<强>·H

class CTestNEW : public CObject
{
public:
    bool m_bHasToBeDeleted;
    __declspec(thread) static void* m_lastAllocated;
public:
#ifdef _DEBUG
    static void* operator new(size_t size, LPCSTR file, int line) { return internalNew(size, file, line); }
    static void operator delete(void* pData, LPCSTR file, int line) { internalDelete(pData, file, line); }
#else
    static void* operator new(size_t size) { return internalNew(size); }
    static void operator delete(void* pData) { internalDelete(pData); }
#endif
public:
    CTestNEW();
public:
#ifdef _DEBUG
    static void* internalNew(size_t size, LPCSTR file, int line)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size, file, line);
        m_lastAllocated = ret;
        return ret;
    }

    static void internalDelete(void* pData, LPCSTR file, int line)
    {
        ::operator delete(pData, file, line);
    }
#else
    static void* internalNew(size_t size)
    {
        CTestNEW* ret = (CTestNEW*)::operator new(size);
        return ret;
    }

    static void internalDelete(void* pData)
    {
        ::operator delete(pData);
    }
#endif
};

<强> .CPP

#include "stdafx.h"
.
.
.
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

void* CTestNEW::m_lastAllocated = NULL;
CTestNEW::CTestNEW()
{
    m_bHasToBeDeleted = (this == m_lastAllocated);
    m_lastAllocated = NULL;
}

答案 9 :(得分:1)

为您的班级重载新的()。这样,您就可以在堆和堆栈分配之间进行分析,但不能在堆栈和静态/全局之间进行分析。

答案 10 :(得分:1)

pax提出的元问题被问到“你为什么要这样做”,你可能会得到更多信息。

现在假设你这样做是为了“一个很好的理由”(也许只是好奇)可以通过覆盖运算符new和delete来获得这种行为,但不要忘记覆盖所有 12变种,包括:

new,delete,new no throw,delete no throw,new array,delete array,new array no throw,delete array no throw,placement new,placement delete,placement new array,placement delete array。

你可以做的一件事就是将它放在基类中并从中派生出来。

这是一种痛苦,所以你想要的是什么样的行为?

答案 11 :(得分:1)

我建议使用智能指针。按照设计,类应该有关于类的数据和信息。簿记任务应该在课外授权。

重载新的和删除可能导致比你想象的更多漏洞。

答案 12 :(得分:1)

回答你的问题,一个可靠的方法(假设你的应用程序没有使用更多的一个线程),假设你的智能指针不包含任何东西不在堆上:

- &GT;重载new,以便存储分配的所有bloc的列表,每个块的大小。 - &GT;当你的智能指针的构造函数时,搜索你的这个指针属于你。如果它不在任何块中,你可以说它是“在堆栈上”(实际上,这意味着它不是由你管理的)。否则,你知道你的指针分配的位置和时间(如果你不想寻找孤立的指针,并且可以随意释放内存,或类似的东西..) 它不依赖于建筑。

答案 13 :(得分:0)

在这里查看该计划:http://alumni.cs.ucr.edu/~saha/stuff/memaddr.html。有几个演员,它输出:

        Address of main: 0x401090
        Address of afunc: 0x401204
Stack Locations:
        Stack level 1: address of stack_var: 0x28ac34
        Stack level 2: address of stack_var: 0x28ac14
        Start of alloca()'ed array: 0x28ac20
        End of alloca()'ed array: 0x28ac3f
Data Locations:
        Address of data_var: 0x402000
BSS Locations:
        Address of bss_var: 0x403000
Heap Locations:
        Initial end of heap: 0x20050000
        New end of heap: 0x20050020
        Final end of heap: 0x20050010

答案 14 :(得分:0)

有一个解决方案,但它会强制继承。参见Meyers,“更有效的C ++”,第27项。

(你可以在这个链接中看到它:
http://bin-login.name/ftp/pub/docs/programming_languages/cpp/cffective_cpp/MEC/MI27_FR.HTM