45

我有

class Foo {
....
}

有没有办法让 Foo 能够分离出来:

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

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

我希望 Foo 能够根据它是在堆栈上还是在堆上分配来做不同的事情。

编辑:

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

答案:

我现在正在使用引用计数的 GC。但是,我也希望有能力进行标记和扫描。为此,我需要标记一组“根”指针——这些是堆栈上的指针。因此,对于每个类,我想知道它们是在堆栈中还是在堆中。

4

15 回答 15

22

一种骇人听闻的方法:

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;
   }
};

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

(肯定有一些系统不起作用)

于 2010-01-13T05:58:14.057 回答
9

您实际上需要问我们真正的问题(a) :-) 您可能很清楚为什么您认为这是必要的,但几乎可以肯定不是。事实上,这几乎总是一个坏主意。换句话说,你为什么认为你需要这样做?

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


更新:

既然您已经在问题中阐明了您的原因,我很抱歉,您可能已经找到了您所要求的少数几个领域之一(运行您自己的垃圾收集过程)。理想情况下,您将覆盖所有内存分配和取消分配运算符,以跟踪从堆中创建和删除的内容。

但是,我不确定拦截类的 new/delete 是否简单,因为可能存在delete未调用的情况,并且由于 mark/sweep 依赖于引用计数,因此您需要能够拦截指针分配使其正常工作。

你有没有想过你将如何处理它?

经典例子:

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

不会导致删除调用。

此外,您将如何检测指向您的实例之一的指针在堆栈上的事实?拦截 new 和 delete 可以让您存储对象本身是基于堆栈还是基于堆的,但我不知道如何判断指针将被分配到哪里,尤其是使用如下代码:

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

也许您可能想研究 C++ 的智能指针,它在很大程度上使手动内存管理过时。共享指针本身仍然会遇到循环依赖等问题,但明智地使用弱指针可以轻松解决这个问题。

在您的方案中可能不再需要手动垃圾收集。


(a)这被称为X/Y problem. 很多时候,人们会提出一个预先假定有一类解决方案的问题,而更好的方法只是描述问题,而不预先考虑最佳解决方案是什么。

于 2010-01-13T05:55:49.910 回答
8

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

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

于 2010-01-13T07:33:09.870 回答
8

如果您将“this”的值与堆栈指针的当前值进行比较,这是可能的。如果 this < 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
于 2010-01-13T15:15:02.410 回答
5

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

于 2010-01-13T06:48:24.353 回答
4

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

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

另一种方式是,大多数运行时使用的内存比进行堆分配时要求的多一点。他们通常在那里放置一些服务结构,以通过指针识别正确的释放。你可以检查你的运行时实现是否有这些模式,尽管它会使你的代码真的不可移植、危险和不可支持的矫枉过正。

同样,如上所述,当您应该询问您设计此解决方案的初始问题(“为什么”)时,您实际上是在询问解决方案的详细信息(“如何”)。

于 2010-01-13T06:14:27.903 回答
4

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

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

于 2010-01-13T05:55:17.003 回答
3

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

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

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

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

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

静态/全局对象呢?除了堆栈/堆之外,您如何区分它们?

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

所以真的,最好的答案是“标记和扫描 GC 不与 C++ 一起使用是有原因的”。如果您想要一个合适的垃圾收集器,请使用另一种支持它的语言。

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

于 2010-01-13T12:16:39.477 回答
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;
}
于 2012-04-22T14:57:45.303 回答
2

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

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

新建,删除,新建不抛出,删除不抛出,新数组,删除数组,新数组不抛出,删除数组不抛出,放置新,放置删除,放置新数组,放置删除数组。

您可以做的一件事是将其放在基类中并从中派生。

这有点痛苦,所以你想要什么不同的行为?

于 2010-01-13T06:13:25.080 回答
1

为了回答您的问题,一种可靠的方法(假设您的应用程序没有使用多个线程),假设您的智能指针不包含的所有内容不在堆上:

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

于 2013-03-26T21:02:37.930 回答
1

为您的班级重载 new()。通过这种方式,您将能够区分堆和堆栈分配,但不能区分堆栈和静态/全局。

于 2010-01-13T05:50:08.847 回答
1

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

重载 new 和 delete 会导致比你想象的更多的漏洞。

于 2011-02-16T11:25:12.747 回答
1

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

编辑:
迈耶斯的建议在Ron van der Wal 写的一篇文章中进行了总结,迈耶斯本人在他的博客中(在这篇文章中)链接到了这篇文章:

跟踪基于堆的对象

作为全局变量方法的替代方案,Meyers 提出了一个 HeapTracked 类,它使用一个列表来跟踪在堆外分配的类实例的地址,然后使用此信息来确定特定对象是否驻留在堆上。实现是这样的:

class HeapTracked {
  // Class-global list of allocated addresses
  typedef const void *RawAddress;
  static list<RawAddress> addresses;
public:
  // Nested exception class
  class MissingAddress {};

  // Virtual destructor to allow dynamic_cast<>; pure to make
  // class HeapTracked abstract.
  virtual ~HeapTracked()=0;

  // Overloaded operator new and delete
  static void *operator new(size_t sz)
  {
    void *ptr=::operator new(sz);
    addresses.push_front(ptr);
    return ptr;
  }

  static void operator delete(void *ptr)
  {
    // Remove ‘ptr’ from ‘addresses’
    list<RawAddress>::iterator it=find(addresses.begin(),

    addresses.end(), ptr);
    if (it !=addresses.end()) {
      addresses.erase(it);
      ::operator delete(ptr);
    } else
      throw MissingAddress();
  }

  // Heap check for specific object
  bool isOnHeap() const
  {
    // Use dynamic cast to get start of object block
    RawAddress ptr=dynamic_cast<RawAddress>(this);
    // See if it’s in ‘addresses’
    return find(addresses.begin(), addresses.end(), ptr) !=
      addresses.end();
  }
};

// Meyers omitted first HeapTracked:: qualifier...
list<HeapTracked::RawAddress> HeapTracked::addresses; 

原始文章还有更多内容需要阅读:Ron van der Wal 评论了这个建议,然后演示了其他替代堆跟踪方法。

于 2016-06-15T06:29:05.753 回答
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
于 2013-03-27T06:57:51.907 回答