如何确定返回的指针是否在堆栈或堆上

我有一个插件架构,我在动态库中调用函数,他们返回一个char* ,这是答案,它在稍后阶段使用。

这是插件函数的签名:

 char* execute(ALLOCATION_BEHAVIOR* free_returned_value, unsigned int* length); 

其中ALLOCATION_BEHAVIOR必须是: DO_NOT_FREE_MEFREE_MEDELETE_ME ,其中插件(在库中)告诉我插件如何分配它刚刚返回的字符串: DO_NOT_FREE_ME告诉我,这是一个我不应该触摸的变量(例如一个永远不会改变的const static char* FREE_ME告诉我应该使用free()来释放返回的值, DELETE_ME告诉我使用delete[]来消除内存泄漏。

显然,我不相信插件,所以我希望能够检查一下,如果他告诉我free()变量,确实它是真正可以释放的东西……这是否可能使用今天的C Linux / Windows上的/ C ++技术?

区分malloc/freenew/delete通常是不可能的,至少不是以可靠和/或可移植的方式。 更多的是,在许多实现中,无论如何new简单包装malloc

区分堆/堆栈的以下备选方案均未经过测试,但它们都应该可以正常工作。

Linux的:

  1. 由Luca Tettananti提出的解决方案,解析/proc/self/maps以获取堆栈的地址范围。
  2. 作为启动时的第一件事, clone你的进程,这意味着提供一个堆栈。 由于您提供它,您自动知道它在哪里。
  3. 使用增加的level参数调用GCC的__builtin_frame_address函数,直到它返回0.然后知道深度。 现在再次使用最大级别调用__builtin_frame_address ,并使用级别0调用一次。堆栈上的任何内容必须位于这两个地址之间。
  4. sbrk(0)作为启动时的第一件事,并记住价值。 每当你想知道某些东西是否在堆上时, sbrk(0)再次出现 – 堆上的东西必须在这两个值之间。 请注意,对于使用内存映射进行大型分配的分配器,这将无法正常工作。

知道堆栈的位置和大小(备选方案1和2),找出一个地址是否在该范围内是微不足道的。 如果不是,则必然是“堆”(除非有人试图成为超级智能屁并给你一个指向静态全局或函数指针的指针,或者……)。

视窗:

  1. 使用CaptureStackBackTrace ,堆栈上的任何内容都必须位于返回的指针数组的第一个和最后一个元素之间。
  2. 如上所述,使用GCC-MinGW(以及__builtin_frame_address ,它应该正常工作)。
  3. 使用GetProcessHeapsHeapWalk检查每个已分配的块以进行匹配。 如果没有匹配任何堆,它就会被分配到堆栈上(…或内存映射,如果有人试图超级聪明的话)。
  4. HeapReAllocHEAP_REALLOC_IN_PLACE_ONLY一起使用,并且大小完全相同。 如果失败,则不会在堆上分配从给定地址开始的内存块。 如果它“成功”,那就是无操作。
  5. 使用GetCurrentThreadStackLimits (仅限Windows GetCurrentThreadStackLimits
  6. 调用NtCurrentTeb() (或读取fs:[18h] )并使用返回的TEB的StackBaseStackLimit字段。

几年前我在comp.lang.c上做了同样的问题,我喜欢James Kuyper的回答:

是。 分配时跟踪它。

这样做的方法是使用内存所有权的概念。 在分配内存块的生命周期中,您应始终拥有一个且只有一个“拥有”该块的指针。 其他指针可能指向该块,但只有拥有指针应该传递给free()。

如果可能的话,应该为了拥有指针而保留一个拥有指针; 它不应该用于存储指向它不拥有的内存的指针。 我通常会尝试通过调用malloc()来初始化拥有指针; 如果这不可行,应该在第一次使用之前将其设置为NULL。 我还尝试确保拥有指针的生命周期在我释放它拥有的内存之后立即结束。 但是,如果不可能,请在free()内存后立即将其设置为NULL。 有了这些预防措施,你不应该在没有首先将它传递给free()的情况下让非null拥有指针的生命周期结束。

如果您无法跟踪哪些指针是“拥有”指针,请在其声明旁边对该事实进行评论。 如果您遇到很多麻烦,请使用命名约定来跟踪此function。

如果由于某种原因,无法专门为其指向的内存所有权保留拥有指针变量,则应该留出一个单独的标志变量来跟踪该指针当前是否拥有它指向的内存。 。 创建一个包含指针和所有权标志的结构是一种非常自然的方法来处理它 – 它确保它们不会分开。

如果你有一个相当复杂的程序,可能需要将内存的所有权从一个拥有的指针变量转移到另一个。 如果是这样,请确保目标指针拥有的任何内存在传输之前是空闲的()d,并且除非源指针的生存期在传输之后立即结束,否则将源指针设置为NULL。 如果您正在使用所有权标志,请相应地重置它们。

插件/库/任何不应该通过传递的’ALLOCATION_BEHAVIOR *’指针返回枚举。 充其量是混乱的。 ‘deallocation’方案属于数据,应该用它封装。

我更喜欢返回一个基类的对象指针,该指针具有一个虚拟的’release()’函数成员,主应用程序可以在需要/需要时调用它并根据该对象的需要处理’dealloaction’。 release()无能为力,在对象的私有数据memebr中指定的缓存中重新填充对象,只需删除()它,具体取决于插件子类应用的任何覆盖。

如果这是不可能的,因为插件是用不同的语言编写的,或者是用不同的编译器构建的,插件可以返回一个函数以及数据,以便主应用程序可以使用数据指针作为参数调用它。解除分配的目的。 这至少允许您将char *和function *放在C ++端的相同对象/结构中,因此至少保持一些封装的外观并允许插件选择它想要的任何释放方案。

编辑 – 如果插件使用与主应用程序不同的堆,这样的方案也可以安全地工作 – 也许它在具有自己的子分配器的DLL中。

在Linux上,您可以解析/proc/self/maps以提取堆栈和堆的位置,然后检查指针是否属于某个范围。

这不会告诉您是否应该通过free或delete来处理内存。 如果你控制架构,你可以让插件释放分配的内存,添加适当的API(IOW,一个与你的execute对称的plugin_free函数)。 另一种常见模式是跟踪在每次调用时传递给插件的上下文对象(在初始化时创建)中的分配,然后插件在关闭时使用它来进行清理。

他们如何在堆栈上分配一些你可以随意释放的东西,因为他们已经回来了? 那只是可怕的死亡。 即使使用它也会死得很厉害。

如果你想检查他们是否已经返回指向静态数据的指针,那么你可能想要掌握你的堆顶部和底部(我很确定在linux上可以使用sbrk),看看是否返回指针是否在该范围内。

当然,有可能即使是该范围内的有效指针也不应该被释放,因为它们已经存储了另一个副本,它们将在以后使用。 如果你不相信他们,你根本不应该相信他们。

您必须使用一些调试工具来确定指针是在堆栈上还是在堆上。 在Windows上,下载Sysinternals Suite。 这提供了各种调试工具。