【问题标题】:Can a data structure implementation know if it is/isn't on the heap? [duplicate]数据结构实现可以知道它是否在堆上吗? [复制]
【发布时间】:2014-11-29 16:41:53
【问题描述】:

考虑一个主文件和另一个实现数据结构的文件(例如:链表)。

链表的调用者可以将链表上的对象放在栈上或堆上,我认为这是调用者的责任。

那么在实现链表的时候,它如何知道它是否在堆上呢?考虑一个从列表中删除节点的典型“方法”。链表如何知道它是否应该释放该内存?据我了解,释放堆栈上的某些内容会导致未定义的行为。

因为这是类项目的一部分,我无法传递一些东西 (isOnHeap) 来指示调用者是否已将内存放在堆上(说明:无法在我们的实现中不允许这样做),所以我假设这个问题可能有一个共同的解决方案,特别是考虑到它的常见程度。请注意,链表实现必须处理释放自己的内存(假设这是给定的,因为它的实现对调用者隐藏)。

【问题讨论】:

  • 如果这对数据结构代码很重要(但为什么?),那么应该从头开始将其设计到 API 中......
  • 标签表示这是一个与 C 相关的问题(在标题中添加了 C)。我确实看到了那个问题。但是,请考虑问题的背景。

标签: c malloc free heap-memory


【解决方案1】:

C 中的数据结构实现不应该释放任何东西,除非它被明确告知这样做。典型的链表实现可能具有“创建节点”、“在此处将节点插入列表”、“从列表中删除节点”和“销毁节点”等功能。这些都不需要检查堆栈与堆。通常,唯一可以在堆栈上的是整个列表的头尾指针;即使您也可以通过您设计的 API 函数分配和释放(并使用堆)。

如果您真的希望能够构建一个可以将节点保留在堆栈上的侵入式链表,我们也可以讨论这个问题,但鉴于您发布的背景,我怀疑情况是否如此。

无论如何,没有可移植的解决方案来知道堆栈或堆上是否有东西,但是您可以在此处看到一些特定于平台的技巧:How to know if a pointer points to the heap or the stack? - 请注意,对于班级分配,您不太可能应该需要这些。

【讨论】:

  • 那我有点糊涂了,因为我们学习的一个典型的释放内存的例子是确保链表结构迭代并释放所有分配的内存。如果列表实现对调用者隐藏,那么调用者如何知道如何释放已在堆上分配的节点上的任何数据?顺便说一句,我不是在挑战你的观点,这是一个真正的问题。调用者应该承担责任对我来说确实更有意义。但是,我们在列表实现中给定的“删除”和“销毁”函数会指示释放动态内存。
【解决方案2】:

...我无法传递一些东西(isOnHeap)来指示调用者是否已将内存放在堆上

这偶尔会出现,但在这种情况下并非如此。由于运行时库的版本不同,该问题通常会出现。问题是库分配了调用者必须释放的块。

可以在 Windows Net API 函数中看到一个问题示例。例如,假设您调用NetGroupEnum function 来枚举组。该库将分配GROUP_INFO_* 结构。调用者完成结构后,调用者有责任调用库的NetApiBufferFree function

在这种情况下,项目需要提供分配器和删除器功能。然后你的例程简单地分配给别人提供的例程;然后在不再需要对象时使用例程删除。

为了解决最初的问题,通常可以梳理出分配的位置。但是堆栈与堆通常无关紧要。

【讨论】:

    【解决方案3】:

    通常链表节点是动态创建的,并且会在堆中分配。但是,如果您选择将已经在堆栈中的节点放入列表中,您将违反此假设。我假设你问的是这种情况。

    然后,取决于库的实现,free 可能什么都不做,只是将内存注册为可用(如果它在堆中)。如果内存恰好在堆栈中,它可以选择做任何事情。或者它可能会出错并退出。但它是未定义的。

    【讨论】:

    • 通常 - 是的。但有时使用堆栈上的数据更有效。在某些情况下,同一个列表可能有混合项目——在堆上和在栈上。通常项目知道如何销毁自己(它引用了销毁函数或类似的一些)......
    【解决方案4】:

    这里是简单的 is_on_stack(addr) 函数:

    int *stack_start = nullptr;
    
    bool is_on_stack(void* ptr) {
      int stack_probe;  
      if( stack_start < &stack_probe  )
        return stack_start < ptr && ptr < &stack_probe; // stack grows upwards;
      else 
        return &stack_probe < ptr && ptr < stack_start; // stack grows downwards;
    }
    
    int main() {
      int stack_probe = 0;
      stack_start = &stack_probe;
    
      int dummy;
    
      bool r1 = is_on_stack(&dummy); // shall be true
    
      int* heap_thing = (int*)malloc(sizeof(int));
    
      bool r2 = is_on_stack(heap_thing); // shall be false         
    }
    

    这里的重点是 main() 函数开头的这些行:

      int stack_probe = 0;
      stack_start = &stack_probe;
    

    【讨论】:

    • 谢谢,但我找到了一个可能的替代解决方案——所以我要重新问一些更具体的问题。由于这在技术上确实回答了这个问题,因此我将选择它作为其他任何可能觉得它有用的人的答案。
    • @c-smile - 我认为你的代码有错误。在is_on_stack 中,您将stack_probe 分配为int,然后将其(未定义)内容与stack_start 进行比较,stack_start 是一个int 指针。这显然是错误的。我想你想在这个函数的所有测试中使用&amp;stack_probe 而不是stack_probe
    • 此外,如果我错了,请纠正我,我相信这是 C 规范中未定义的行为。您正在比较指向不同对象的指针。虽然我同意这应该适用于大多数 C 实现,但我觉得我需要指出这一点。
    • @DoxyLover,同意你的看法。该解决方案在某些实现中可能不起作用,例如某些处理器、Big Endian、堆栈的增长方向、多线程......,
    • @DoxyLover 谢谢,已修复
    猜你喜欢
    • 2017-10-31
    • 2021-09-14
    • 2016-08-13
    • 2013-01-23
    • 2011-06-05
    • 2021-04-01
    • 2015-11-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多