如何知道指针是指向堆还是堆栈?

时间:2010-07-12 16:51:41

标签: c++ memory-management pointers

示例:

bool isHeapPtr(void* ptr)
{
     //...
}

int iStack = 35;
int *ptrStack = &iStack;
bool isHeapPointer1 = isHeapPtr(ptrStack); // Should be false
bool isHeapPointer2 = isHeapPtr(new int(5)); // Should be true
/* I know... it is a memory leak */

为什么,我想知道这个:

如果我在一个类中有一个成员指针,我不知道指向对象是否是新分配的。然后我应该使用这样的实用程序来知道我是否必须delete指针。

可是:
我的设计还没有制作完成。所以,我会按照我delete的方式编程。我要避免垃圾编程

14 个答案:

答案 0 :(得分:30)

没有办法做到这一点 - 如果你需要这样做,你的设计就会出现问题。讨论了为什么你不能在More Effective C++中执行此操作。

答案 1 :(得分:15)

在一般情况下,你很运气,我很害怕 - 因为指针可以有任何价值,所以没有办法区分它们。如果您了解堆栈的起始地址和大小(例如,从嵌入式操作系统中的TCB),您可能能够做到这一点。类似的东西:

stackBase = myTCB->stackBase;
stackSize = myTCB->stackSize;

if ((ptrStack < stackBase) && (ptrStack > (stackBase - stackSize)))
    isStackPointer1 = TRUE;

答案 2 :(得分:12)

我能想到的唯一“好”解决方案是为该类重载operator new并跟踪它。像这样的东西(脑编译代码):

class T {
public:    
    void *operator new(size_t n) {
        void *p = ::operator new(n);
        heap_track().insert(p);
        return p;
    }

    void operator delete(void* p) {
        heap_track().erase(p);
        ::operator delete(p);
    }

private:

    // a function to avoid static initialization order fiasco
    static std::set<void*>& heap_track() {
        static std::set<void*> s_;
        return s_;
    }

public:
    static bool is_heap(void *p) {
        return heap_track().find(p) != heap_track().end();
    }
};

然后你可以做这样的事情:

T *x = new X;
if(T::is_heap(x)) {
    delete x;
}

但是,我会建议不要让你能够询问是否在堆上分配了某些东西。

答案 3 :(得分:8)

好吧,拿出你的汇编程序书,并将指针的地址与堆栈指针进行比较:

int64_t x = 0;
asm("movq %%rsp, %0;" : "=r" (x) );
if ( myPtr < x ) {
   ...in heap...
}

现在x将包含您需要将指针与之比较的地址。请注意,它将用于在另一个线程中分配的内存,因为它将拥有自己的堆栈。

答案 4 :(得分:5)

在这里,适用于MSVC:

#define isheap(x, res) {   \
void* vesp, *vebp;     \
_asm {mov vesp, esp};   \
_asm {mov vebp, ebp};    \
res = !(x < vebp && x >= vesp); }

int si;

void func()
{
    int i;
    bool b1;
    bool b2;
    isheap(&i, b1); 
    isheap(&si, b2);
    return;
}

它有点难看,但有效。仅适用于局部变量。如果从调用函数传递堆栈指针,则此宏将返回true(表示它是堆)

答案 5 :(得分:3)

在主流操作系统中,堆栈从顶部开始增长,而堆从底部开始增长。因此,可能启发式地检查地址是否超出了大值,对于某些“大”的定义。例如,以下内容适用于我的64位Linux系统:

#include <iostream>

bool isHeapPtr(const void* ptr) {
  return reinterpret_cast<unsigned long long int>(ptr) < 0xffffffffull;
}

int main() {
  int iStack = 35;
  int *ptrStack = &iStack;
  std::cout << isHeapPtr(ptrStack) << std::endl;
  std::cout << isHeapPtr(new int(5)) << std::endl;
}

请注意,这是一个粗略的启发式方法,可能很有趣,但不适合生产代码。

答案 6 :(得分:3)

首先,你为什么要知道这个?你想解决什么真正的问题?

我知道做出这种决定的唯一方法就是超载全球operator newoperator delete。然后你可以询问你的内存管理器指针是否属于它(堆)(堆栈或全局数据)。

答案 7 :(得分:3)

即使您可以确定指针是在一个特定堆上还是在一个特定堆栈上,一个应用程序可以有多个堆栈和多个堆栈。

根据询问的原因,每个容器对于它是否“拥有”它所持有的指针都有严格的政策是非常重要的。毕竟,即使这些指针指向堆分配的内存,其他一些代码也可能具有相同指针的副本。虽然可以转让所有权,但每个指针一次应该有一个“所有者”。所有者负责破坏。

在极少数情况下,容器可以跟踪拥有和非拥有指针(使用标志或单独存储)。但是,大多数情况下,为任何可以保存指针的对象设置一个明确的策略更简单。例如,大多数智能指针始终拥有其容器实际指针。

当然智能指针在这里很重要 - 如果你想要一个所有权跟踪指针,我相信你可以找到或写一个智能指针类型来抽象那些麻烦。

答案 8 :(得分:2)

尽管声称相反,但显然有可能以平台相关的方式做你想做的事。然而,仅仅因为某些事情是可能的,这并不会自动成为一个好主意。一个简单的堆栈规则==没有删除,否则==删除不太可能正常工作。

一种更常见的方式是说如果我分配了一个缓冲区,那么我必须将其删除,如果程序传递给我一个缓冲区,则删除它不是我的责任。

e.g。

class CSomething
{
public:
    CSomething()
    : m_pBuffer(new char[128])
    , m_bDeleteBuffer(true)
    {
    }

    CSomething(const char *pBuffer)
    : m_pBuffer(pBuffer)
    , m_bDeleteBuffer(false)
    {
    }

    ~CSomething()
    {
        if (m_bDeleteBuffer)
            delete [] m_pBuffer;
    }

private:
    const char *m_pBuffer;
    bool        m_bDeleteBuffer;
};

答案 9 :(得分:2)

你正在努力做到这一点。澄清您的设计,以便明确谁“拥有”数据并让该代码处理其生命周期。

答案 10 :(得分:1)

这是使用TIP在Windows中执行此操作的通用方法:

bool isStack(void* x)
{
    void* btn, *top;
    _asm {
        mov eax, FS:[0x08] 
        mov btn, eax
        mov eax, FS:[0x04] 
        mov top, eax
    }
    return x < top && x > btn;
}

void func()
{

    int i;

    bool b1;
    bool b2;

    b1 = isStack(&i);
    b2 = isStack(&si);

    return;
}

答案 11 :(得分:0)

我知道半可靠地执行此操作的唯一方法是,如果您可以为需要执行此操作的类型重载operator new。不幸的是,那里有一些重大的陷阱,我不记得它们是什么。

我确实知道一个缺陷就是没有直接分配就可以在堆上。例如:

class A {
   int data;
};

class B {
 public:
   A *giveMeAnA() { return &anA; }
   int data;
   A anA;
};

void foo()
{
   B *b = new B;
   A *a = b->giveMeAnA();
}

在上面的代码中,a中的foo结尾有一个指向堆上没有用new分配的对象的指针。如果您的问题确实是“如何知道我是否可以在此指针上调用delete”。重载operator new做一些棘手的事可能会帮助你回答这个问题。我仍然认为,如果你不得不提出这个问题,那你就做错了。

答案 12 :(得分:0)

你怎么能不知道是否有堆分配的东西?您应该将软件设计为具有单点分配。

除非你在嵌入式设备中做一些真正奇特的东西或深入定制内核,否则我觉得不需要它。

查看此代码(为了示例,没有错误检查):

class A
{
int *mysweetptr;

A()
{
   mysweetptr = 0; //always 0 when unalloc'd
}

void doit()
{
   if( ! mysweetptr)
   {
       mysweetptr = new int; //now has non-null value
   }
}

void undoit()
{
   if(mysweetptr)
   {
      delete mysweetptr;
      mysweetptr = 0;  //notice that we reset it to 0.
   }
}

bool doihaveit()
{
   if(mysweetptr)
     return true;
   else 
     return false; 
}
~A()
{
   undoit();
}
};

特别注意我使用null值来确定指针是否已被分配,或者我是否需要删除它。

答案 13 :(得分:0)

你的设计不应该依赖于确定这些信息(正如其他人所指出的那样,这是不可能的)。相反,您的类应该明确定义它在构造函数或方法中所使用的指针的所有权。如果你的类拥有这些指针的所有权,那么传递指向堆栈或全局的指针是不正确的行为,你应该在知道不正确的客户端代码可能崩溃的情况下删除它。如果您的班级没有取得所有权,则不应该删除指针。