Python 中的 C 扩展 - 返回 Py_BuildValue() 内存泄漏问题

发布于 2024-10-29 19:01:40 字数 1161 浏览 3 评论 0原文

我有一个巨大的内存泄漏问题,涉及我正在开发的 C 扩展。在 C 中,我有一个名为 A 的双精度数组和一个名为 AnotherIntVariable 的 int 变量,我想将它们传递给 Python。好吧,在我的 C 扩展模块中,我执行以下操作:

int i;  
PyObject *lst = PyList_New(len_A);  
PyObject *num;  
if(!lst)  
   return NULL;  
for(i=0;i<len_A;i++){  
   num=PyFloat_FromDouble(A[i]);  
   if(!num){  
      Py_DECREF(lst);  
      return NuLL;  
   }  
   PyList_SET_ITEM(lst,i,num);  
}  
free(A);  
return Py_BuildValue("Oi",lst,AnotherIntVariable)

因此,在 Python 中,我收到此列表和 int,如下所示:

Pyt_A,Pyt_int=MyCModule.MyCFunction(...)

其中 Pyt_A 和 Pyt_int 是我从 C 扩展“MyCModule”获取的列表和整数code>”,来自我之前描述的函数“MyCFunction”。

问题是,在 Python 中,我使用这个 Pyt_A 数组(这就是为什么我使用 Py_BuildValue 而不是简单的 return 语句来执行INCREF 以便从垃圾收集器中暂时保存该变量),但随后我需要以某种方式取消引用它,以便释放分配的内存。问题是我多次使用 MyCFunction 函数,这会产生内存泄漏,因为我不知道如何取消引用在 python 中获得的数组以摆脱它。

我尝试通过在代码的 C 部分执行 return lst 而不是 Py_BuildValue("Oi",lst,AnotherIntVariable) 来返回数组,但这只会产生结果当我尝试在 python 中使用它时出现分段错误(可能是因为垃圾收集器完成了他的工作)...

...我在这里缺少什么?有人可以帮助我吗?

I have a huge memory leak problem involving a C-extension I'm developing. In C, I have an array of doubles called A and an int variable called AnotherIntVariablethat I want to pass to Python. Well, in my C-extension module I do the following:

int i;  
PyObject *lst = PyList_New(len_A);  
PyObject *num;  
if(!lst)  
   return NULL;  
for(i=0;i<len_A;i++){  
   num=PyFloat_FromDouble(A[i]);  
   if(!num){  
      Py_DECREF(lst);  
      return NuLL;  
   }  
   PyList_SET_ITEM(lst,i,num);  
}  
free(A);  
return Py_BuildValue("Oi",lst,AnotherIntVariable)

So in Python i recieve this list and the int like this:

Pyt_A,Pyt_int=MyCModule.MyCFunction(...)

Where Pyt_A and Pyt_int are the list and the integer I get from my C-extension "MyCModule", from the function "MyCFunction" that I described earlier.

The problem is that, in Python, I use this Pyt_A array (so that's why I use Py_BuildValue instead of a simple return statement, to do an INCREF in order to save this variable for a moment from the garbage collector) but then I need to dereference it somehow in order to free that allocated memory. The problem is that I use the MyCFunction function several times, and this produces a memory leakage because I don't know how to dereference the array that I get in python in order to get rid of it.

I tried just returning the array by doing a return lst in the C part of the code instead of the Py_BuildValue("Oi",lst,AnotherIntVariable), but that only results in a Segmentation Fault when I try to use it in python (probably because the garbage collector did his work)...

...what am I missing here? Can anybody help me?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

盛夏尉蓝 2024-11-05 19:01:40

如果您查看 Py_BuildValue 的文档 (http://docs.python.org/3/c-api/arg.html#c.Py_BuildValue)您可以看到,在 O 类型代码下,它表示传入对象的引用计数增加一(注意:该页面的前面部分描述了 PyArg_ParseTupleO 类型代码,但没有 em> 增加引用计数,但这里也不相关)。

因此,在调用 Py_BuildValue 后,列表的引用计数为 2,但您只希望它为 1

不要直接返回 Py_BuildValue 的结果,而是将其保存到 PyObject 指针,递减 lst 引用计数,然后返回结果。

无论如何,您应该检查 Py_BuildValue 调用的结果,因为您还需要在 Py_BuildValue 失败时释放 num (即返回 <代码>NULL)。

If you look at the documentation for Py_BuildValue (http://docs.python.org/3/c-api/arg.html#c.Py_BuildValue) you can see that under the O typecode, it says that the reference count of the passed in object is incremented by one (Note: an earlier section in that page describes the O typecode for PyArg_ParseTuple, which doesn't increment the reference count, but also isn't relevant here).

So, after the call to Py_BuildValue, the refcount for your list is 2, but you only want it to be 1.

Instead of returning the result of Py_BuildValue directly, save it to a PyObject pointer, decrement the lst reference count, then return your result.

You should be checking the result of the Py_BuildValue call anyway, since you also need to free num in the event that Py_BuildValue fails (i.e. returns NULL).

明天过后 2024-11-05 19:01:40

感谢伊格纳西奥的澄清,现在它变得非常有意义了!最后,解决方案是,不要直接返回 Py_BuildValue,而是这样做:

free(A);  
PyObject *MyResult = Py_BuildValue("Oi",lst,AnotherIntVariable);  
Py_DECREF(lst);  
return MyResult

它就像一个魅力!

Thanks for clearing it up Ignacio, now it makes so much sense! Finally, the solution was to, instead of returning directly the Py_BuildValue, do:

free(A);  
PyObject *MyResult = Py_BuildValue("Oi",lst,AnotherIntVariable);  
Py_DECREF(lst);  
return MyResult

It worked like a charm!

入怼 2024-11-05 19:01:40

有一个比其他答案所建议的更简单的解决方案。使用 O 格式代码(对传递的对象采用新的强引用)后,不要将代码重新排列为 decref,只需使用 N 格式代码,该代码 < em>窃取您的引用,通过转移所有权完全避免不必要的引用计数操作。为此所需的唯一更改是更改

return Py_BuildValue("Oi",lst,AnotherIntVariable);

为:

return Py_BuildValue("Ni",lst,AnotherIntVariable);
//                    ^ only change

Py_BuildValue 也非常强大;如果由于任何原因失败,它会继续解析参数以确保引用计数被窃取并适当释放。如果 N 参数 (lst) 作为 NULL 传递(通常是因为在参数列表中调用了返回新强引用的函数并且失败,设置异常)它保留该异常并返回 NULL 本身,以便异常按预期传播。

There's a simpler solution that what the other answers have been suggesting. Don't rearrange your code to decref after using the O format code (which takes a new strong reference to the passed object), just use the N format code, which steals your reference, avoiding unnecessary reference count manipulation entirely by transferring ownership. The only change needed for this is to change:

return Py_BuildValue("Oi",lst,AnotherIntVariable);

to:

return Py_BuildValue("Ni",lst,AnotherIntVariable);
//                    ^ only change

Py_BuildValue is pretty robust too; if it fails for any reason it continues parsing arguments to ensure reference counts are stolen and released appropriately. If the N argument (lst) is passed as NULL (usually because a function returning a new strong reference was called in the argument list and failed, setting an exception) it preserves that exception and returns NULL itself so the exception propagates as intended.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文