如何获取使用malloc()分配的内存块的大小?

11

可能的重复问题:
如何从C中的指针获取数组的大小?
有没有办法以编程方式确定C ++数组的大小?如果没有,为什么?

我从一个C风格的函数中得到了一个分配的内存块的指针。现在,出于调试目的,知道这个指针所指向的分配内存块的大小将非常有趣。

是否有比盲目地运行超出其边界而引发异常更优雅的方法?

提前感谢, Andreas

编辑:

我在Windows上使用VC++2005和Linux上的GCC 4.3

编辑2:

我在VC++2005下使用_msize,但在调试模式下会导致异常...

编辑3:

好吧。我已经尝试了上面描述的那种方式,它可以工作。 至少在我调试并确保在调用库后立即运行超出缓冲区边界时可以工作。像魅力一样工作。

它只是不够优雅,而且在生产代码中无法使用。


3
请参见以下两个链接: https://dev59.com/xHVC5IYBdhLWcg3wvT1a https://dev59.com/THVC5IYBdhLWcg3woCrN这些链接讨论了在C语言中如何以编程方式确定数组的大小和从指针中获取数组大小的方法。 - Shog9
那里没有有用的答案。 - AndreasT
@AndreasT:你能详细说明一下吗?那些问题中的一个或另一个没有回答你想要的是什么(https://dev59.com/THVC5IYBdhLWcg3woCrN似乎特别接近你在这里所问的)。现在,这似乎是一个重复的问题 - 如果你能澄清你想要做什么,也许我们可以找到更好的答案... - Shog9
在你提到的网站上,搜索没有给我建议,但第三个答案提供了这个信息:msize()或类似的函数(_msize()等)。我不是在寻找数组的大小,而是想知道在给定位置分配了多少字节的内存。当然,这基本上是相同的问题,特别是在C语言中,你以相同的方式分配数组和大内存块。但我不需要知道可能附有一些大小信息的数组。我需要已分配内存块的大小。 - AndreasT
在C语言中的相似之处对我来说并不明显。 - AndreasT
@AndreasT - 正如 thirtythreeforty 在这里提到的,可以参考以下答案(类似问题):https://dev59.com/pnM_5IYBdhLWcg3wq1CF#1281720 - Guy Avraham
9个回答

23

这不是标准的方法,但如果你的库有一个 msize() 函数,那么它可以给你返回大小。

一种常见的解决方案是使用自己的函数包装 malloc,记录每个请求及其大小和结果内存范围,在发布版本中,你可以切换回“真正的” malloc


1
+1 我几年前就做过这个。它很容易实现,而且非常有用。 - Brian Agnew
我无法访问源代码。 - AndreasT
但是_msize提示可能有效。谢谢! - AndreasT
检查是否有 _debug_malloc 包。在调试版本中,可能会有一个运行时库来获取 malloc 信息。但总的来说,你不应该设计你的代码,假设这是可能的。 - Martin Beckett
5
你还可以使用malloc_usable_size()函数,该函数在GNU平台上提供。 - George Hilliard
哪个库有 msize() 函数? - R1S8K

16

如果您不介意为了调试而进行肮脏的暴力操作,您可以使用#define宏来挂钩malloc和free的调用,并在前4个字节中填充大小。

按照这个旋律

void *malloc_hook(size_t size) {
    size += sizeof (size_t);
    void *ptr = malloc(size);
    *(size_t *) ptr = size;
    return ((size_t *) ptr) + 1;
}

void free_hook (void *ptr) {
    ptr = (void *) (((size_t *) ptr) - 1);
    free(ptr);
}

size_t report_size(ptr) {
    return * (((size_t *) ptr) - 1);
}

然后

#define malloc(x) malloc_hook(x)

等等


除了对齐问题外,这实际上是以便携方式完成它的最佳方法。如果您使用像dlmalloc这样更冒险的东西,您可以利用弱链接,但对于PC应用程序来说,这可能过度杀伤。 - Dan Olson
这听起来也不错,但我没有代码访问权限。 - AndreasT
由于我无意中回答了一个重复的问题,并提供了几乎相同的解决方案,因此它应该值得一次点赞! - Clifford
1
这段代码按照原来的写法并没有起作用,似乎存在一些问题,但是我最终让它运行了。不错的hack! - ApproachingDarknessFish
@ApproachingDarknessFish,你能发一下代码吗? - R1S8K

10

C运行时库没有提供这样一个函数。另外,故意引发异常也不能告诉您块的大小。

通常在C语言中解决这个问题的方法是维护一个单独的变量来跟踪分配块的大小。当然,这有时会不方便,但通常没有其他方法可以知道。

您的C运行时库可能提供一些堆调试函数,可以查询已分配的块(毕竟,free()需要知道块的大小),但此类任何内容都是不可移植的。


6

使用gccGNU链接器,您可以轻松地封装malloc

#include <stdlib.h>
#include <stdio.h>


void* __real_malloc(size_t sz);
void* __wrap_malloc(size_t sz)
{
    void *ptr;

    ptr = __real_malloc(sz);
    fprintf(stderr, "malloc of size %d yields pointer %p\n", sz, ptr);

    /* if you wish to save the pointer and the size to a data structure, 
       then remember to add wrap code for calloc, realloc and free */

    return ptr;
}

int main()
{
    char *x;
    x = malloc(103);

    return 0;
}

并编译

gcc a.c -o a -Wall -Werror -Wl,--wrap=malloc

当然,这同样适用于使用g++编译的c++代码,并且如果您愿意,也可以使用new操作符(通过其名称混淆)。事实上,静态/动态加载库也将使用您的 __wrap_malloc 函数。

真的吗?那怎么可能?没有调试信息的 .a 或 .so 文件中的代码会被覆盖吗?如果是的话:酷!B) - AndreasT
弱引用。链接器将覆盖对malloc的符号引用,并将它们指向您指定的位置。 - Christopher
没错,不需要调试信息,任何库都包含提供的符号表和需要从其他地方获取的符号表。因此,您可以告诉链接器将哪个函数与可执行文件的malloc符号绑定。 - Adrian Panasiuk

4
不可以,如果超出边界,除非在你的实现文档中有说明,否则不能依赖异常。这是编写程序时真正不需要了解的内容。如果你真的想知道,可以查阅编译器的文档或源代码。

为什么这个被踩了,有什么特别的原因吗? - David Thornley
不是我点踩的,但可能是因为malloc和访问malloc分配的内存不会抛出异常,因为它们是C语言的一部分。 - anon
它确实可以。我可以请求那个内存块,然后去触发访问冲突,它就能正常工作。我在C++中进行这个操作,所以会抛出异常。 - AndreasT
在Windows系统上,可能会出现结构化异常(访问冲突),因为这是操作系统用来通知您已经越界的方式。在Linux上,我认为等效的是分段错误。无论您的运行时是否将它们转换为C++异常取决于实现。 - Shog9
这也有点棘手。就像你所说,在Windows上可能会出现访问冲突,而在Linux上可能会出现分段错误。如果没有确切的了解,我会怀疑这些是由虚拟内存管理器引起的,因此可能表明离开了内存页而不是已分配的内存区域。 - David Thornley
@David:没错 - 你有可能会进入到已分配内存的其他部分,悄悄地破坏其他数据结构(包括内存管理器使用的那些...)或读取虚假数据。这是危险的领域... - Shog9

4

没有标准的C函数可以做到这一点。根据您的平台,可能存在不可移植的方法 - 您使用的操作系统和C库是什么?

请注意,触发异常是不可靠的 - 可能会在您拥有的块之后立即出现其他分配,因此您可能直到超出当前块的限制后很长时间才会收到异常。


4

内存检查器,例如Valgrind的memcheckGoogle的TCMalloc(堆检查器部分),可以跟踪这种情况。

您可以使用TCMalloc来转储堆配置文件,以显示分配位置,或者您可以仅使用SameHeap()来检查程序执行两个点时堆是否相同。


我一定会去查看的。 - AndreasT

2
部分解决方案:在Windows上,您可以使用PageHeap来捕获超出分配块的内存访问。
PageHeap是Windows内核中的备用内存管理器(在NT版本中),它会获取进程中的每个分配并返回一个内存块,该内存块的末尾与内存页的末尾对齐,然后使下一页无法访问(无读取、写入访问权限)。如果程序尝试读取或写入超出块结尾的内容,则会发生访问冲突,您可以使用您喜欢的调试器捕获它。
如何获取:从Microsoft下载和安装Windows调试工具包:http://www.microsoft.com/whdc/devtools/debugging/default.mspx,然后启动GFlags实用程序,转到第三个选项卡,输入您的可执行文件名称,然后按键。勾选PageHeap复选框,单击确定即可。
最后一件事:完成调试后,不要忘记再次启动GFlags,并为应用程序禁用PageHeap。GFlags将此设置输入注册表(位于HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\下),因此它是持久的,即使重新启动也是如此。
另外,请注意,使用PageHeap可能会极大地增加应用程序的内存需求。

1
要做你想要的事情的方式是成为分配器。如果您过滤所有请求,然后记录它们以进行调试,那么当内存被释放时,您就可以找到所需的内容。
此外,您可以在程序结束时检查所有已分配的块是否都已释放,如果没有,则列出它们。这种类型的雄心勃勃的库甚至可以通过宏获取函数和行参数,让您准确地知道您正在泄漏内存的位置。
最后,Microsoft的MSVCRT提供了可调试的堆,其中包含许多有用的工具,您可以在调试版本中使用这些工具来查找内存问题:http://msdn.microsoft.com/en-us/library/bebs9zyz.aspx 在Linux上,您可以使用valgrind查找许多错误。http://valgrind.org/

不是我分配内存,而是一个我无法访问源代码的库。 - AndreasT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接