15

我有一个插件架构,我在其中调用动态库中的函数,它们返回给我一个char*答案,它在以后的某个阶段使用。

这是插件函数的签名:

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

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

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

4

7 回答 7

11

区分malloc/freenew/delete通常是不可能的,至少不是以可靠和/或便携的方式。更new何况malloc在许多实现中只是简单地包装。

以下区分堆/堆栈的替代方法均未经过测试,但它们都应该有效。

Linux:

  1. Luca Tettananti 提出的解决方案,解析/proc/self/maps得到栈的地址范围。
  2. 作为启动时的第一件事,clone您的进程,这意味着提供堆栈。既然你提供了它,你就会自动知道它在哪里。
  3. __builtin_frame_address使用递增的level参数调用 GCC 的函数,直到它返回 0。然后你就知道了深度。现在__builtin_frame_address以最高级别再次调用,一次以 0 级别调用。堆栈中的任何内容都必须位于这两个地址之间。
  4. sbrk(0)作为启动时的第一件事,并记住该值。每当您想知道堆上是否有东西时,sbrk(0)再次 - 堆上的东西必须在两个值之间。请注意,这不适用于使用内存映射进行大分配的分配器。

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

视窗:

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

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

是的。分配时跟踪它。

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

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

如果您无法跟踪哪些指针是“拥有”指针,请在其声明旁边添加有关该事实的注释。如果您遇到很多麻烦,请使用命名约定来跟踪此功能。

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

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

于 2013-05-23T09:27:12.817 回答
3

插件/库/任何东西都不应该通过传递的 'ALLOCATION_BEHAVIOR*' 指针返回枚举。充其量是混乱的。'deallocation' 方案属于数据,应与数据一起封装。

我更愿意返回某个基类的对象指针,该对象指针具有一个虚拟的“release()”函数成员,主应用程序可以在它想要/需要时调用它,并根据该对象的需要处理“dealloaction”。release() 什么都不做,将对象重新池化在对象的私有数据 memebr 中指定的缓存中,只是 delete() 它,这取决于插件子类应用的任何覆盖。

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

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

于 2013-05-23T10:53:39.667 回答
2

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

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

于 2013-05-23T09:29:51.887 回答
1

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

使用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;
}

欢迎对此代码提供反馈!

于 2019-01-07T16:45:53.343 回答
0

当它们返回时,它们如何在堆栈上分配一些你可以释放的东西?那只会死得很惨。即使使用它也会死得很惨。

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

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

于 2013-05-23T10:22:25.730 回答
-1

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

于 2013-05-23T09:36:34.570 回答