【问题标题】:Where my memory is alloced, Stack or Heap, Can I find it at Run-time?我的内存分配在哪里,堆栈或堆,我可以在运行时找到它吗?
【发布时间】:2011-02-28 14:41:05
【问题描述】:

我知道使用 new 分配的内存,在堆中获取它的空间,所以我们需要在程序结束之前删除它,以避免内存泄漏。

让我们看看这个程序...

Case 1:

char *MyData = new char[20];
_tcscpy(MyData,"Value");
.
.
.
delete[] MyData; MyData = NULL;


Case 2:
char *MyData = new char[20];
MyData = "Value";
.
.
.
delete[] MyData; MyData = NULL;

在情况 2 中,它不是为堆内存分配值,而是指向一个字符串字面量。

现在,当我们执行删除操作时,它会崩溃,正如预期的那样,因为它没有尝试删除堆内存。 有没有办法知道指针指向堆或堆栈的位置?

程序员就是这样

  • 不会尝试删除任何堆栈内存
  • 他可以调查为什么这个最初指向堆内存的指针会引用本地文字?中间的堆内存怎么了?它是否被另一个指针指向并在其他地方删除等等?

【问题讨论】:

  • 你很幸运在第一种情况下没有崩溃——你使用的是delete而不是delete[](或者更好的是std::string)。
  • @avakar:我宁愿说他不走运——使用delete 而不是delete[] 是未定义的行为,具有无限的后果。除非对实现进行非常仔细的检查,否则没有人确切知道会发生什么。即使在那之后,代码也无法移植。
  • @avakar:很抱歉。我直接输入了。现在我已经改变了。感谢您指出。
  • 我必须保持我的咆哮:你为什么不使用std::string
  • @avakar:这只是一个旨在说明问题的示例。

标签: c++ heap-memory stack-memory


【解决方案1】:

有没有办法知道指针指向堆或堆栈的位置?

只有在分配时记住它才能知道这一点。在这种情况下,您所做的是将指针存储在智能指针类中,并将其存储在类代码中。

如果你以boost::shared_ptr 为例,你可以这样做:

template<typename T> void no_delete(T* ptr) { /* do nothing here */ }

class YourDataType; // defined elsewhere
boost::shared_ptr<YourDataType> heap_ptr(new YourDataType()); // delete at scope end

YourDataType  stackData;
boost::shared_ptr<YourDataType> stack_ptr(&stackData, &no_delete); // never deleted

【讨论】:

    【解决方案2】:

    一旦你需要这些知识,你就已经失去了。为什么?因为即使你省略了错误的 delete[],你仍然有内存泄漏。

    创造记忆的人应该永远是删除它的人。如果在某些情况下某个指针可能会丢失(或被覆盖),那么您必须保留一份它的副本以便正确删除。

    【讨论】:

    • 是的,正如你所说的,我已经指出程序中存在内存泄漏。我的问题是,我们有什么方法可以调查指向一个堆内存的指针是否在某些地方发生了变化,以及旧的指针发生了什么变化。对于这样的示例程序,很容易发现。但对于较大的维护项目,它是乏味的。如果你碰巧从事维护项目,你会同意的。
    • 这就是为什么我们使用std::strings、std::vectors,在不需要的时候避免使用指针,如果真的需要我们使用智能指针
    • AKN:Nikko 在这里提出了一个很好的观点。也许作为维护工作的一部分,您将不得不重构代码以使用智能指针或 C++ 字符串。至少当我必须将旧的 C 代码合并到我们的框架中时,我就是这样做的。
    【解决方案3】:

    标准 C++ 中无法确定指针是否指向动态分配的内存。请注意,字符串文字不会在堆栈上分配。

    【讨论】:

    • @Neil:“字符串文字未在堆栈上分配”。如果没有堆叠,你能告诉它在哪里吗?
    • @AKN The Strandard 没有说。它确实说文字的生命周期是程序的生命周期,因此它们不能放在堆栈上。
    • @AKN 字符串文字是可执行文件的一部分。它们与代码一起在文本段中(当然取决于二进制格式)。
    • 感谢 Neil 和 Jeremy 的澄清。
    • "stack" 是一个有点模糊的术语,但重要的是以下代码完全可以:const char* foo() { return "Foo"; }foo() 返回时字符串"Foo" 不会消失。跨度>
    【解决方案4】:

    正如大多数用户所说,没有标准的方法来发现您正在处理的内存。

    此外,正如许多用户指出的那样,这是一种变态的情况,您将一个指针传递给一个函数,如果它是在堆上分配的,该函数应该自动删除它。

    但如果你坚持,还是有一些方法可以发现哪个内存属于哪个类型。

    你实际上处理了 3 种类型的内存

    • 堆栈
    • 全球

    例如:

    char* p = new char[10]; // p is a pointer, points to heap-allocated memory
    
    char* p = "Hello, world!"; // p is a pointer, points to the global memory
    
    char p[] = "Hello, world!"; // p is a buffer allocated on the stack and initialized with the string
    

    现在让我们区分它们。我将用 Windows API 和 x86 汇编器来描述这一点(因为这是我所知道的 :))

    让我们从堆栈内存开始。

    bool IsStackPtr(PVOID pPtr)
    {
        // Get the stack pointer
        PBYTE pEsp;
        _asm {
            mov pEsp, esp
        };
    
        // Query the accessible stack region
        MEMORY_BASIC_INFORMATION mbi;
        VERIFY(VirtualQuery(pEsp, &mbi, sizeof(mbi)));
    
        // the accessible stack memory starts at mbi.BaseAddress and lasts for mbi.RegionSize
        return (pPtr >= mbi.BaseAddress) && (pPtr < PBYTE(mbi.BaseAddress) + mbi.RegionSize);
    }
    

    如果指针分配在另一个线程的堆栈上,您应该通过GetThreadContext 获取其堆栈指针,而不是仅获取EIP 寄存器值。

    全局内存

    bool IsGlobalPtr(PVOID pPtr)
    {
        MEMORY_BASIC_INFORMATION mbi;
        VERIFY(VirtualQuery(pPtr, &mbi, sizeof(mbi)));
    
        // Global memory allocated (mapped) at once for the whole executable
        return mbi.AllocationBase == GetModuleHandle(NULL);
    }
    

    如果您正在编写一个 DLL,您应该放置它的模块句柄(实际上是它的基本映射指针)而不是 GetModuleHandle(NULL)

    理论上你可以假设如果内存既不是全局的也不是堆栈的——它是在堆上分配的。

    但实际上这里有一个很大的歧义。

    您应该知道堆有不同的实现(例如HeapAlloc/HeapFree 访问的原始 Windows 堆,或 CRT 包装的 malloc/freenew/delete )。

    只有当您确定它是堆栈/全局指针或通过new 分配时,您才可以通过delete 运算符删除这样的块。

    总结:

    1. 这是一种变态的把戏。一般不宜使用。最好用指针提供一些额外的信息,告诉如何释放它。
    2. 只有在确定内存分配在哪个堆上(如果是堆内存),您才能使用它。

    【讨论】:

    • 信不信由你,不是每个人都在使用 Windows。
    • 好吧,我不坚持使用Windows。在 Linux 上可能也有办法做到这一点。
    • 它会丢失内存映射文件,包括匿名(页面文件)映射,以及您从其他人那里获得的内存(例如 COM 字符串)。和线程局部变量。甚至更多。
    • 当然。更不用说“堆”不一定是标准的 C/C++ 堆。我提到过。
    【解决方案5】:

    我认为没有(简单的)方法可以判断内存的分配位置(也许您可以使用调试器来确定它,但这显然不是您想要的)。底线是:永远不要做你在案例 2 中所做的事情。

    【讨论】:

      【解决方案6】:

      在情况 2 中,MyData = "Value" 导致内存泄漏,因为不再有对从 new 返回的内存的引用。

      【讨论】:

        【解决方案7】:

        没有简单的方法或标准的方法来做到这一点。您可以拦截堆分配函数并将每个内存分配区域放在一个列表中。您的“IsHeap”函数应检查传递给该函数的区域是否是列表中的区域。这只是一个提示——以跨平台的方式几乎不可能做到这一点。

        但话又说回来 - 你为什么需要那个?

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-10-04
          • 2013-07-14
          • 1970-01-01
          • 2015-07-19
          • 1970-01-01
          • 2013-03-16
          • 2013-10-18
          • 1970-01-01
          相关资源
          最近更新 更多