我正在尝试为Cfree
和malloc
在 C 中创建包装函数,以帮助通知我内存泄漏。有谁知道如何声明这些函数,所以当我调用它malloc()
时free()
它会调用我的自定义函数而不是标准库函数?
10 回答
你有几个选择:
GLIBC 特定的解决方案(主要是 Linux)。如果你的编译环境是
glibc
withgcc
,首选的方法是使用malloc 钩子。它不仅可以让您指定自定义malloc
andfree
,而且还可以通过堆栈上的返回地址来识别调用者。POSIX 特定的解决方案。在可执行文件中定义
malloc
和free
作为原始分配例程的包装器,这将“覆盖”来自 libc 的版本。在包装器内部,您可以调用原始实现,您可以使用句柄malloc
进行查找。定义包装函数的应用程序或库需要与.dlsym
RTLD_NEXT
-ldl
#define _GNU_SOURCE #include <dlfcn.h> #include <stdio.h> void* malloc(size_t sz) { void *(*libc_malloc)(size_t) = dlsym(RTLD_NEXT, "malloc"); printf("malloc\n"); return libc_malloc(sz); } void free(void *p) { void (*libc_free)(void*) = dlsym(RTLD_NEXT, "free"); printf("free\n"); libc_free(p); } int main() { free(malloc(10)); return 0; }
Linux 特定的。
LD_PRELOAD
您可以通过在环境变量中指定动态库中的函数来非侵入性地覆盖它们。LD_PRELOAD=mymalloc.so ./exe
Mac OSX 特定的。
与 Linux 相同,但您将使用
DYLD_INSERT_LIBRARIES
环境变量。
您可以使用 LD_PRELOAD 执行包装和“覆盖”功能 - 类似于前面显示的示例。
LD_PRELOAD=/path.../lib_fake_malloc.so ./app
但我建议这样做“稍微”聪明一点,我的意思是调用 dlsym 一次。
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
void* malloc(size_t size)
{
static void* (*real_malloc)(size_t) = NULL;
if (!real_malloc)
real_malloc = dlsym(RTLD_NEXT, "malloc");
void *p = real_malloc(size);
fprintf(stderr, "malloc(%d) = %p\n", size, p);
return p;
}
我在这里找到的示例:http ://www.jayconrod.com/cgi/view_post.py? 23 Jay Conrod 的帖子。
但是我在这个页面上发现非常酷的是:GNU 链接器提供了一个有用的选项--wrap。当我检查“man ld”时,有以下示例:
void *
__wrap_malloc (size_t c)
{
printf ("malloc called with %zu\n", c);
return __real_malloc (c);
}
我同意他们的观点,这是“微不足道的例子”:)。甚至不需要 dlsym。
让我再引用我的“man ld”页面的一部分:
--wrap=symbol
Use a wrapper function for symbol.
Any undefined reference to symbol will be resolved to "__wrap_symbol".
Any undefined reference to "__real_symbol" will be resolved to symbol.
我希望,描述是完整的,并展示了如何使用这些东西。
就我而言,我需要将 memalign/aligned_malloc 包装在 malloc 下。在尝试了其他解决方案后,我最终实施了下面列出的解决方案。它似乎工作正常。
/*
* Link-time interposition of malloc and free using the static
* linker's (ld) "--wrap symbol" flag.
*
* Compile the executable using "-Wl,--wrap,malloc -Wl,--wrap,free".
* This tells the linker to resolve references to malloc as
* __wrap_malloc, free as __wrap_free, __real_malloc as malloc, and
* __real_free as free.
*/
#include <stdio.h>
void *__real_malloc(size_t size);
void __real_free(void *ptr);
/*
* __wrap_malloc - malloc wrapper function
*/
void *__wrap_malloc(size_t size)
{
void *ptr = __real_malloc(size);
printf("malloc(%d) = %p\n", size, ptr);
return ptr;
}
/*
* __wrap_free - free wrapper function
*/
void __wrap_free(void *ptr)
{
__real_free(ptr);
printf("free(%p)\n", ptr);
}
在 C 中,我使用的方法类似于:
#define malloc(x) _my_malloc(x, __FILE__, __LINE__)
#define free(x) _my_free(x)
这使我能够毫不费力地检测到分配内存的行和文件。它应该是跨平台的,但是如果已经定义了宏就会遇到问题(只有在使用另一个内存泄漏检测器时才会出现这种情况。)
如果您想在 C++ 中实现相同的功能,该过程会稍微复杂一些,但使用相同的技巧。
这是我多年来使用的一组包装函数(当我进入 C 语言时仍然这样做)来检测未释放的内存、多次释放的内存、对已释放内存的引用、缓冲区溢出/下溢以及释放内存没有分配。
ftp://ftp.digitalmars.com/ctools.zip
他们已经存在了 25 年,并且已经证明了自己。
您可以使用宏预处理器重新定义 malloc 并免费使用 mem 包,但我建议不要这样做,因为它不会像 strdup 那样将库调用重定向到 malloc。
如果您为 malloc() 和 free() 定义自己的函数并将其与您的应用程序显式链接,则应优先使用您的函数而不是库中的函数。
但是,您的名为“malloc”的函数不能调用库 malloc 函数,因为在“c”中没有单独命名空间的概念。换句话说,您必须实现 malloc 的内部结构并释放自己。
另一种方法是编写函数 my_malloc() 和 my_free(),它们调用标准库函数。这意味着任何调用 malloc 的代码都必须更改为调用您的 my_xxx 函数。
如果您是自定义的唯一客户malloc
并且free
(即,您不是试图为其他库中的代码修补这些方法),那么您可以使用依赖注入。
#ifndef ALLOCATOR_H
#define ALLOCATOR_H
#include <stddef.h>
struct Allocator;
typedef struct {
void *(*allocate)(struct Allocator *allocator, size_t size);
void (*free)(struct Allocator *allocator, void *object);
} AllocatorVTable;
typedef struct Allocator {
const AllocatorVTable *vptr;
} Allocator;
typedef struct {
Allocator super;
char *buffer;
size_t offset;
size_t capacity;
} BufferedAllocator;
void BufferedAllocator_init(BufferedAllocator *allocator, char *buffer, size_t capacity);
typedef Allocator MallocAllocator;
void MallocAllocator_init(MallocAllocator *allocator);
void *Allocator_allocate(Allocator *allocator, size_t size);
void Allocator_free(Allocator *allocator, void *object);
#endif
#include "allocator.h"
#include "malloc.h"
void *Allocator_allocate(Allocator *allocator, size_t size) {
return allocator->vptr->allocate(allocator, size);
}
void Allocator_free(Allocator *allocator, void *object) {
allocator->vptr->free(allocator, object);
}
void *BufferedAllocator_allocate(Allocator *allocator, size_t size) {
BufferedAllocator *bufferedAllocator = (BufferedAllocator *) allocator;
if (bufferedAllocator->offset + size > bufferedAllocator->capacity) {
fprintf(stderr, "buffer overflow: %ld + %ld > %ld\n",
bufferedAllocator->offset, size, bufferedAllocator->capacity);
return NULL;
}
bufferedAllocator->offset += size;
return bufferedAllocator->buffer + bufferedAllocator->offset - size;
}
void BufferedAllocator_free(Allocator *allocator, void *object) {
}
const AllocatorVTable bufferedAllocatorVTable = {
.allocate = BufferedAllocator_allocate,
.free = BufferedAllocator_free,
};
void BufferedAllocator_init(BufferedAllocator *allocator, char *buffer,
size_t capacity) {
allocator->super.vptr = &bufferedAllocatorVTable;
allocator->buffer = buffer;
allocator->offset = 0;
allocator->capacity = capacity;
}
void *MallocAllocator_allocate(Allocator *allocator, size_t size) {
return malloc(size);
}
void MallocAllocator_free(Allocator *allocator, void *object) {
free(object);
}
const AllocatorVTable mallocAllocatorVTable = {
.allocate = MallocAllocator_allocate,
.free = MallocAllocator_free,
};
void MallocAllocator_init(MallocAllocator *allocator) {
allocator->vptr = &mallocAllocatorVTable;
}
#include <assert.h>
#include "allocator_test.h"
#include "allocator.h"
void testAllocator() {
{
BufferedAllocator bufferedAllocator;
char buffer[4];
size_t capacity = sizeof(buffer);
BufferedAllocator_init(&bufferedAllocator, buffer, capacity);
Allocator *allocator = &bufferedAllocator.super;
void *chill = Allocator_allocate(allocator, capacity);
assert(chill == buffer);
void *oops = Allocator_allocate(allocator, 1);
assert(oops == NULL);
}
{
MallocAllocator allocator;
MallocAllocator_init(&allocator);
void *chill = Allocator_allocate(&allocator, 100);
assert(chill != NULL);
void *alsoChill = Allocator_allocate(&allocator, 100);
assert(alsoChill != NULL);
}
}
因此,您可以将一个传递Allocator *
给您编写的任何想要分配东西的代码(除了char buf[n]
堆栈上的东西)。您可以使用 aMallocAllocator
来使用系统malloc
/ free
,或者您可以BufferedAllocator
在程序的最顶部使用 a 。ABufferedAllocator
只是一个非常简单的 malloc/free 的示例。它在我的用例中运行良好,因为我非常清楚我的程序将提前使用多少内存,并且在整个程序完成之前我不会删除任何对象。使用此接口,您可以编写更复杂的算法,如本讲座中描述的算法之一。有很多不同的策略可以防止碎片和许多权衡,所以滚动你自己的 malloc/free 可能真的很有用。
如果您使用的是 Linux,则可以使用 malloc_hook()(使用 GNU glibc)。此函数允许您在调用实际 malloc 之前让 malloc 调用您的函数。手册页有一个关于如何使用它的示例。
如果您只谈论您可以控制的内存,即您自己 malloc 和 free,您可以查看rmdebug。无论如何,这可能就是您要写的内容,因此您可以节省一些时间。它有一个非常自由的许可证,如果这对你很重要的话。
我个人在一个项目中使用它来查找内存泄漏,好处是它比 valgrind 快得多,但是它不是那么强大,所以你没有得到完整的调用堆栈。