在使用外部C DLL时Python的内存泄漏

3 投票
1 回答
1331 浏览
提问于 2025-04-20 19:35

我有一个Python模块,它调用一个用C语言写的DLL来编码XML字符串。当这个函数返回编码后的字符串时,它没有释放在这个过程中分配的内存。具体来说:

encodeMyString = ctypes.create_string_buffer(4096)

CallEncodingFuncInDLL(encodeMyString, InputXML)

我查看了这个这个这个,还尝试过调用gc.collect,但可能因为这个对象是在外部DLL中分配的,Python的垃圾回收机制并没有记录到它,所以无法释放它。由于代码不断调用编码函数,它就不断分配内存,最终导致Python进程崩溃。有没有办法来监控这个内存使用情况?

1 个回答

5

因为你没有提供关于DLL的任何信息,所以我只能说得比较模糊,但……

Python无法追踪那些它不知道的外部分配的内存。怎么可能呢?那块内存可能是DLL的常量部分,或者是用mmapVirtualAlloc分配的,或者是更大对象的一部分,或者DLL可能只是希望它在使用时是活着的。

任何一个有函数分配并返回新对象的DLL,都必须有一个函数来释放那个对象。例如,如果CallEncodingFuncInDLL返回一个你需要负责的新对象,那么就会有一个像DestroyEncodedThingInDLL这样的函数,它会接收这个对象并释放它。

那么,你什么时候调用这个函数呢?


让我们退一步,把这个说得更具体一点。假设这个函数是普通的strdup,那么你用来释放内存的函数就是free。你有两个选择,决定什么时候调用free。我不知道你为什么会想从Python调用strdup,但这是最简单的例子,所以我们就假装它不是没用的。


第一个选择是调用strdup,立即将返回的值转换为原生的Python对象,然后释放它,之后就不用再担心了:

newbuf = libc.strdup(mybuf)
s = newbuf.value
libc.free(newbuf)
# now use s, which is just a Python bytes object, so it's GC-able

或者,更好的是,使用自定义的restype可调用对象来自动处理:

def convert_and_free_char_p(char_p):
    try:
        return char_p.value
    finally:
        libc.free(char_p)
libc.strdup.restype = convert_and_free_char_p

s = libc.strdup(mybuf)
# now use s

但是,有些对象不能那么容易地转换为原生的Python对象——或者可以,但这样做并没有太大意义,因为你需要不断地将它们传回DLL。在这种情况下,你不能在用完之前就清理它。

最好的方法是把那个不透明的值封装在一个类里,在close__exit____del__等适当的时候释放它。一个不错的做法是使用@contextmanager

@contextlib.contextmanager
def freeing(value):
    try:
        yield value
    finally:
        libc.free(value)

所以:

newbuf = libc.strdup(mybuf)
with freeing(newbuf):
    do_stuff(newbuf)
    do_more_stuff(newbuf)
# automatically freed before you get here
# (or even if you don't, because of an exception/return/etc.)

或者:

@contextlib.contextmanager
def strduping(buf):
    value = libc.strdup(buf)
    try:
        yield value
    finally:
        libc.free(value)

现在:

with strduping(mybuf) as newbuf:
    do_stuff(newbuf)
    do_more_stuff(newbuf)
# again, automatically freed here

撰写回答