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

Posted

技术标签:

【中文标题】如何知道指针指向堆还是栈?【英文标题】:How to know if a pointer points to the heap or the stack? 【发布时间】:2011-03-14 21:52:30 【问题描述】:

例子:

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 的方式对其进行编程。我要避免垃圾编程

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

在一般情况下,恐怕你不走运 - 因为指针可以有任何值,所以没有办法区分它们。如果您知道堆栈起始地址和大小(例如,来自嵌入式操作系统中的 TCB),您也许可以做到。比如:

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

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

【讨论】:

【参考方案3】:

我能想到的唯一“好”解决方案是为该类重载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;

但是,我建议不要采用要求您能够询问是否在堆上分配了某些东西的设计。

【讨论】:

【参考方案4】:

好吧,拿出你的汇编手册,将你的指针地址与堆栈指针进行比较:

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

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

【讨论】:

【参考方案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(表示它是堆)

【讨论】:

【参考方案6】:

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

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

【讨论】:

【参考方案7】:

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

基于询问的原因,对于每个容器来说,对于它是否“拥有”它所持有的指针有一个严格的政策是极其重要的。毕竟,即使这些指针指向堆分配的内存,其他一些代码也可能具有相同指针的副本。每个指针一次应该有一个“所有者”,尽管所有权可以转移。所有者负责销毁。

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

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

【讨论】:

【参考方案8】:

在主流操作系统中,堆栈从顶部增长,而堆从底部增长。因此,对于“大”的某些定义,您可能试探性地检查地址是否超出大值。例如,以下在我的 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;

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

【讨论】:

【参考方案9】:

尽管有相反的说法,但显然可以以依赖于平台的方式做你想做的事。然而,仅仅因为某些事情是可能的,这并不能自动使它成为一个好主意。 stack==no delete 的简单规则,否则==delete 不太可能正常工作。

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

例如

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

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

这是使用 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;

【讨论】:

【参考方案12】:

我知道半可靠地执行此操作的唯一方法是,如果您可以为您需要执行此操作的类型重载 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();

在上述代码中,foo 中的a 以指向堆上未分配new 的对象的指针结束。如果您的问题真的是“我怎么知道我是否可以在此指针上调用 delete。”重载operator new 来做一些棘手的事情可能会帮助你回答这个问题。我仍然认为,如果你不得不问这个问题,你就做错了。

【讨论】:

【参考方案13】:

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

除非您在嵌入式设备中做一些真正奇特的东西或在自定义内核中深入工作,否则我认为没有必要。

看看这段代码(没有错误检查,举例来说):

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();

;

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

【讨论】:

【参考方案14】:

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

【讨论】:

以上是关于如何知道指针指向堆还是栈?的主要内容,如果未能解决你的问题,请参考以下文章

指针free后还存在吗?

如何知道指针是指向动态还是静态分配的内存

变量是放在了堆内存还是栈内存

对象列表应该存储在堆还是栈上?

java中创建数组时怎么分配内存?

JVM笔记5-对象的访问定位。