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

Mar*_*aux 27 c++ pointers memory-management

例:

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 */
Run Code Online (Sandbox Code Playgroud)

为什么,我想知道这个:

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

但是:
我的设计尚未完成.所以,我会按照我一直以来的方式编程delete.我要避免垃圾编程

小智 30

没有办法做到这一点 - 如果你需要这样做,你的设计就会出现问题.讨论了为什么你不能在更有效的C++中做到这一点.

  • @Heath:没有什么可以试验的.这是不可能的.这就像让OP说"我想画一个没有边缘的正方形".我们说"你做不到." 而且你觉得我们应该让他试验一下.(顺便说一下,他仍然能做到这一点.)他到底要做什么,你更喜欢什么样的答案?这是一个很好的答案,甚至指向其他资源,以找出他不能的原因,从领先的C++程序员不能少. (18认同)
  • StackOverflow告诉提问者他们为85.5k的代表"做错了".Sweeeeet.我很好奇Martijn为什么认为他也想这样做,但为什么要告诉他?让他试验吧. (17认同)
  • -1为了讲道律的绝对主义.请参阅Carl Norum关于如何部分满足OP的答案. (15认同)
  • @Heath如果你认为我的85K超级力量延伸到阻止Martijn进行实验,你会大大高估它们. (9认同)
  • @Heath:但他的第一句话是绝对正确的:"没有办法做到这一点".绝对没有办法做OP所要求的,期间,故事的结尾.它完全取决于系统.不仅取决于操作系统,还取决于*system*; 运行相同操作系统的不同CPU架构仍然会有不同的需求以便跟踪信息.大量的组合使得这样的功能几乎不可能构建 - 甚至可能无法在运行时收集必要的信息.*正确的答案是回到绘图板. (8认同)
  • @Neil:噢,废话.我希望当我有85k时,我可以完全取消所有实验.男人,打破我的目标的方法. (4认同)
  • +1因为你是绝对正确的.如果您遇到问题,您的设计就会被破坏. (3认同)
  • @Heath:那是完全不公平的.尼尔是完全正确的; 如果你需要知道指针是否在堆栈上,你必须回到绘图板. (3认同)
  • @Heath:不,你错了.如果有人真的需要这个,他们应该知道他们在做什么(如果他们在问,他们会解释他们为什么这样做).对于其他人来说,尼尔只是说实话.现在成为一个好公民并取消你的投票.(不是那些-2会伤害尼尔.) (3认同)
  • @JAB:没有边的正方形是不可能对象的常见例子.根据定义,正方形有四条边,因此无法在没有边的情况下绘制.如果这样做,则没有绘制正方形,因为它没有四条边.@Heath:我不会混淆任何事情.在C++中没有办法做到这一点.如果OP希望提供他的特定平台(关于它的一切)和编译器(关于它的一切),我们可能有机会(浪费时间和)找到解决方案(这将是100%非标准C++).最好是将枪从孩子身边带走,而不是试着教他如何使用它. (3认同)
  • +1和Heath,有良好的编码实践和糟糕的编码实践.这就是Error'd存在的原因. (2认同)
  • @GMan:在大多数环境或操作系统上,很容易确定地址是否来自堆栈。请参阅诺鲁姆的回答。所以,OP至少可以诚实地回答一半。我同意,确定“堆”地址是一种错误的练习。但有人可以解释原因。最重要的是区分什么是真正的(诚实的,不是说谎或不诚实的……)“不可能”和“不时尚”。 (2认同)
  • @Heath:你认为这个答案很讽刺? (2认同)
  • @Heath如果你认为这个答案是"狡猾的"(我从来没有完全理解这个词,但我认为是"讽刺"的话),你显然不是我在这里的帖子的追随者. (2认同)
  • 作为旁注,确定某些东西是"在堆栈上"还是"在堆上"对于确定`delete`的有效性是没有意义的,只是因为绝对不能保证特定C++实现中的自动存储持续时间对应到OS堆栈.一致的编译器可以自由地将本地填充到OS堆上,并在返回时清理它. (2认同)
  • 调用未定义的行为@David并不会自动违反标准.它仍然是标准的C++.标准只是没有说明会发生什么.A*general*解决方案不存在,但这并不意味着*no*解决方案根本不存在.尼尔的答案没有这样的区别,但我认为应该这样. (2认同)
  • @Rob:我不明白为什么任何包含不合格程序的"标准C++"定义都很有用.我也不明白为什么一些高度特定于实现的解决方案的可能存在意味着解决方案通常是可能的,特别是因为OP没有提到实现细节.我真的没有看到Neil的价值,如果我们知道确切的实现细节,就可能以一种非常依赖系统的方式获得一个大多数正确的解决方案.可能有系统可以保证如何评估`i ++ + i ++`; 我们要提一下吗? (2认同)
  • @Rob我真的没有看到你对此感到兴奋 - 如果你不认为我的答案是正确的,那就投降吧.如果您还没有这样做,请阅读我提到的更有效的C++中的内容. (2认同)
  • @Rob那么或许你回答一下,使用基于签名的技术比这些评论更好? (2认同)

Car*_*rum 15

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

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

if ((ptrStack < stackBase) && (ptrStack > (stackBase - stackSize)))
    isStackPointer1 = TRUE;
Run Code Online (Sandbox Code Playgroud)

  • 你不能声称非堆栈==堆. (7认同)
  • @Heath,绝对正确.但是,如果对OS结构或链接器定义的变量进行适当的访问,则可以消除其他非堆区域.这就是我说"类似"的原因.'isHeapPointer`只是因为OP的命名法.立即编辑. (3认同)
  • 喜欢编辑.人们可以确定地址是来自"堆栈"还是"堆栈".如果每个进程有多个线程,则进程应检查每个线程的堆栈. (2认同)
  • 在现代操作系统中,像“堆栈”可能不一定被实现为“堆栈数据结构”。我记得读过一篇文章,其中他们试图通过将堆栈段随机放入内存(即作为堆的一部分)来防止堆栈溢出漏洞。如果您的操作系统使用这种技术,那么您就不走运了。 (2认同)

Eva*_*ran 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();
    }
};
Run Code Online (Sandbox Code Playgroud)

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

T *x = new X;
if(T::is_heap(x)) {
    delete x;
}
Run Code Online (Sandbox Code Playgroud)

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


Gia*_*nni 8

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

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

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

  • 我认为最好的解决方案是沿着这些方向,但你必须知道堆栈的方向. (3认同)
  • @Alexandre是的,这确实是一个反复试验的事情.它永远不会给你一个令人满意的答案,但会激发你的好奇心并教你一些关于记忆布局的知识. (2认同)

And*_*rey 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;
}
Run Code Online (Sandbox Code Playgroud)

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


小智 5

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

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

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

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