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

时间:2013-05-23 09:15:07

标签: c++ c memory

我有一个插件架构,我在动态库中调用函数,它们返回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()这个变量,确实它确实是可以释放的......这是可以在Linux / Windows上使用今天的C / C ++技术吗?

7 个答案:

答案 0 :(得分:11)

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

以下所有区分堆/堆栈的替代方法均未经过测试,但 的所有工作都已完成。

<强> Linux的:

  1. Luca Tettananti提出的解决方案,解析/proc/self/maps以获取堆栈的地址范围。
  2. 作为启动时的第一件事,clone您的流程,这意味着提供堆栈。由于您提供它,您将自动知道它在哪里。
  3. 使用增加级别参数调用GCC的__builtin_frame_address函数,直到它返回0.然后知道深度。现在再次使用最高级别调用__builtin_frame_address,然后使用级别0调用一次。堆栈上的任何内容必须位于这两个地址之间。
  4. sbrk(0)作为启动时的第一件事,并记住价值。每当你想知道某些东西是否在堆上时,再次sbrk(0) - 堆上的东西必须在这两个值之间。请注意,对于使用内存映射进行大型分配的分配器,这将无法正常工作。
  5. 知道堆栈的位置和大小(备选方案1和2),找出地址是否在该范围内是微不足道的。如果不是,则必然是“堆”(除非有人试图成为超级聪明的屁股,并为您提供指向静态全局或函数指针等的指针......)。

    <强>窗:

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

答案 1 :(得分:8)

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

  

是。分配时跟踪它。

     

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

     

如果可能的话,应该保留一个拥有指针   拥有指针的目的;它不应该用于存储指针   记忆它不拥有。我一般都试图安排一个拥有   通过调用malloc()初始化指针;如果那不是   可行的,应该在第一次使用之前的某个时候设置为NULL。我也   尝试确保拥有指针的生命周期结束   在我释放它所拥有的记忆之后立刻。但是,当那个   不可能,在free()内存后立即将其设置为NULL。   有了这些预防措施,你就不应该放弃一生   非null拥有指针结束而不首先将其传递给free()。​​

     

如果您无法跟踪哪些指针“拥有”   指针,在他们的声明旁边对这个事实发表评论。如果   你有很多麻烦,使用命名约定来跟踪   这个功能。

     

如果出于任何原因,无法保留拥有指针   变量专门用于它所指向的记忆的所有权,你   应该留出一个单独的标志变量来跟踪是否或   不是指针当前拥有它指向的内存。创建一个   包含指针和所有权标志的struct非常   处理这种情况的自然方式 - 确保它们不会分开。

     

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

答案 2 :(得分:3)

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

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

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

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

答案 3 :(得分:2)

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

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

答案 4 :(得分:0)

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

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

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

答案 5 :(得分:0)

我正在使用以下代码检查学生的作业。返回堆栈内存是一个常见的陷阱,所以我想自动检查它。

使用sbrk

此方法应在所有Unix变体和所有CPU架构上都适用。

#include <unistd.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

bool points_to_heap(void* init_brk, void* pointer){
    void* cur_brk = sbrk(0);
    return ((init_brk <= pointer) && (pointer <= cur_brk));
}

int main(void){
    void* init_brk = sbrk(0);
    int* heapvar = malloc(10);
    int i = 0;
    int* stackvar = &i;
    assert(points_to_heap(init_brk, heapvar));
    assert(!points_to_heap(init_brk, stackvar));
    return 0;
}

使用/proc/self/maps

此方法有两个问题:

  • 此代码特定于在64位x86 CPU上运行的Linux。
  • 该方法似乎不适用于使用libcheck框架编写的单元测试中。在那里,所有堆栈变量也被视为堆变量。
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

void get_heap_bounds(uint64_t* heap_start, uint64_t* heap_end){
    FILE *stream;
    char *line = NULL;
    size_t len = 0;
    ssize_t nread;

    stream = fopen("/proc/self/maps", "r");

    while ((nread = getline(&line, &len, stream)) != -1) {
        if (strstr(line, "[heap]")){
            sscanf(line, "%" SCNx64 "-%" SCNx64 "", heap_start, heap_end);
            break;
        }
    }

    free(line);
    fclose(stream);
}

bool is_heap_var(void* pointer){
    uint64_t heap_start = 0;
    uint64_t heap_end = 0;
    get_heap_bounds(&heap_start, &heap_end);

    if (pointer >= (void*)heap_start && pointer <= (void*)heap_end){
        return true;
    }
    return false;
}

欢迎反馈此代码!

答案 6 :(得分:-1)

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