Valgrind 错误和内存泄漏与 Python/C API



我实际上是在用C++开发一个游戏,并尝试用脚本语言来做AI。为此,我选择了带有Python/C api的Python2。 我的AI实际上在工作,但有一个大问题:当我在我的程序上运行valgrind时,有很多错误和内存泄漏。所以,我会知道这是因为我的代码还是 API 而发生的?

以下是我的班级AI的摘要:

IA::IA()
{
setenv("PYTHONPATH",".",1);
Py_Initialize();
PyRun_SimpleString("import sys");
pName = PyBytes_FromString((char*)"Test");
pModule = PyImport_Import(pName);
pDict = PyModule_GetDict(pModule);
pFunc = PyDict_GetItemString(pDict, "push_f");
}
IA::~IA()
{
Py_DECREF(pValue);
Py_DECREF(pModule);
Py_DECREF(pName);
Py_Finalize();
}
void IA::LaunchIA(float x, float y, float z)
{
PyObject *toSend;
toSend = Py_BuildValue("(OOO)", TlistMob, TlistPlayer, pDPosIA);
pResult = PyObject_CallObject(pFunc, toSend);
PyErr_Print();
printf("return = %fn", (float)PyInt_AsLong(pResult));
}

我的(非常)简单的Python代码:

def push_f(MobList, PlayerList, pos):
return 0

和瓦尔格林德错误 (x1000) :

==11602== Memcheck, a memory error detector
==11602== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==11602== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==11602== Command: ./a.out
==11602== 
==11602== Invalid read of size 4
==11602==    at 0x4FCE173: PyObject_Free (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F02FC2: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4FBDE9A: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F85BAD: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F872FF: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F88559: PyImport_ImportModuleLevel (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EFF697: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F4B1E2: PyObject_Call (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x5021446: PyEval_CallObjectWithKeywords (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF45C5: PyEval_EvalFrameEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x502201B: PyEval_EvalCodeEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF0B88: PyEval_EvalCode (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==  Address 0x693c020 is 2,560 bytes inside a block of size 2,731 free'd
==11602==    at 0x4C2EDEB: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11602==    by 0x4F81D28: PyMarshal_ReadLastObjectFromFile (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F85A22: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F872FF: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F88559: PyImport_ImportModuleLevel (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EFF697: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F4B1E2: PyObject_Call (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x5021446: PyEval_CallObjectWithKeywords (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF45C5: PyEval_EvalFrameEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x502201B: PyEval_EvalCodeEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF0B88: PyEval_EvalCode (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F851B3: PyImport_ExecCodeModuleEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==  Block was alloc'd at
==11602==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11602==    by 0x4F81CDF: PyMarshal_ReadLastObjectFromFile (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F85A22: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F872FF: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F88559: PyImport_ImportModuleLevel (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EFF697: ??? (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F4B1E2: PyObject_Call (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x5021446: PyEval_CallObjectWithKeywords (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF45C5: PyEval_EvalFrameEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x502201B: PyEval_EvalCodeEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4EF0B88: PyEval_EvalCode (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602==    by 0x4F851B3: PyImport_ExecCodeModuleEx (in /usr/lib/x86_64-linux-gnu/libpython2.7.so.1.0)
==11602== 
==11602== 
==11602== HEAP SUMMARY:
==11602==     in use at exit: 491,741 bytes in 204 blocks
==11602==   total heap usage: 3,301 allocs, 3,097 frees, 3,567,424 bytes allocated
==11602== 
==11602== LEAK SUMMARY:
==11602==    definitely lost: 0 bytes in 0 blocks
==11602==    indirectly lost: 0 bytes in 0 blocks
==11602==      possibly lost: 1,072 bytes in 2 blocks
==11602==    still reachable: 490,669 bytes in 202 blocks
==11602==         suppressed: 0 bytes in 0 blocks
==11602== Rerun with --leak-check=full to see details of leaked memory
==11602== 
==11602== For counts of detected and suppressed errors, rerun with: -v
==11602== Use --track-origins=yes to see where uninitialised values come from
==11602== ERROR SUMMARY: 497 errors from 25 contexts (suppressed: 0 from 0)

在我的主方面,你需要知道我只创建了一个 IA 对象。

我做错了什么吗?还是只是 API ?

(这不是重复的,因为我在我的C++可执行文件而不是 Python 上运行 valgrind,我的 c++ 正在运行脚本)

提前致谢!!

我最初建议将其作为重复项。我认为情况不再如此,但它仍然提供了有用的建议,可以从 Python 程序的 Valgrind 输出中删除误报。

除此之外,您的程序还存在许多特定问题:

  • 无错误检查 - Python C API 通常使用 NULL 返回值来指示错误。显然有多种编写错误检查代码的方法,但我很想选择类似

    IA::IA() :
    pModule(NULL), pDict(NULL), pFunc(NULL), pName(NULL) // initially null initialize everything
    {
    setenv("PYTHONPATH",".",1);
    Py_Initialize();
    // I don't think you actually use this line, so maybe remove it
    if (PyRun_SimpleString("import sys") == -1) goto error;
    pName = PyBytes_FromString((char*)"Test");
    if (!pName) goto error;
    pModule = PyImport_Import(pName);
    if (!pModule) goto error;
    pDict = PyModule_GetDict(pModule);
    if (!pDict) goto error;
    pFunc = PyDict_GetItemString(pDict, "push_f");
    if (!pFunc) goto error;
    return; // completed OK
    error:
    Py_XDECREF(pName); // XDECREF is OK with NULL...
    Py_XDECREF(pModule);
    Py_XDECREF(pDict);
    Py_XDECREF(pFunc);
    PyErr_Print();
    throw std::runtime_error(""); // ??? - possibly get and use the error string
    // see https://stackoverflow.com/a/1418703/4657412
    }
    

    我知道人们对goto持怀疑态度,但在这种情况下,这是一种相当干净的跳转到错误处理块的方式。如果您愿意,可以以不同的方式构建它。

  • 析构函数不会pFunc,这看起来像内存泄漏。

  • IA::LaunchIA同样缺乏工作错误检查。

  • IA::LaunchIA从不拒绝toSendpResultTlistMobTlistPlayerpDPosIA。其中一些与您显示的代码不完整有关,但如果它们没有被引用,那么您正在泄漏内存。

  • 在不检查错误的情况下调用PyErr_Print()。文档说:

    仅在设置错误指示器时调用此函数。(否则会导致致命错误!

    我怀疑这可能是你最大的问题。


这些都是我能看到的问题。如果没有一个最小的完整示例,就不可能实际检查您所看到的内容。鉴于您正在使用C++建议您使用/制作一个体面的、面向对象的包装PyObject*器,以避免担心自己对引用计数 - Boost Python 有一个。

最新更新