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

Valgrind error and memory leaks with Python/C API

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

这是我的 class 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 = %f\n", (float)PyInt_AsLong(pResult));

}

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

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

并且 valgrind 错误 (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)

在我的 main 中,您需要知道我只创建了一个 IA 对象。

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

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

提前致谢!!!

我最初建议 this 作为副本。我认为不再是这种情况,但它仍然提供有用的建议,以消除 Python 程序的 Valgrind 输出中的误报。

除此之外,您的程序还有许多具体问题:

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

    的方法
    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 
    }
    

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

  • 析构函数没有递减 pFunc,这看起来像是内存泄漏。

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

  • IA::LaunchIA 从不递减 toSendpResultTlistMobTlistPlayerpDPosIA。其中一些与您显示的代码的不完整性有关,但如果它们没有减少,那么您就会泄漏内存。

  • 您调用 PyErr_Print() 没有检查错误。文档说:

    Call this function only when the error indicator is set. (Otherwise it will cause a fatal error!)

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


这些是我能看到的所有问题。没有一个最小的完整示例,就不可能真正检查您所看到的内容。如果你使用的是 C++,你可能会被很好地建议 use/make 一个体面的、面向对象的包装器 PyObject* 以避免自己担心引用计数 - Boost Python 有一个。