代码之家  ›  专栏  ›  技术社区  ›  Ed Mazur

swig生成的扩展发生内存泄漏

  •  2
  • Ed Mazur  · 技术社区  · 14 年前

    我有一个内存泄漏问题,用PHIG在PHP中包装C++库。当包含复杂类型的C++的回调被发送到PHP,而启用了导演时,似乎会发生这种情况。下面是一个独立的例子来重现泄漏:

    Client.hpp:

    #ifndef CLIENT_HPP_
    #define CLIENT_HPP_
    
    #include <vector>
    #include "ProcedureCallback.hpp"
    
    class Client {
    public:
        void invoke(ProcedureCallback *callback) {
            callback->callback(std::vector<int>(0));
        }
    };
    
    #endif /* CLIENT_HPP_ */
    

    程序确认.hpp:

    #ifndef PROCEDURECALLBACK_HPP_
    #define PROCEDURECALLBACK_HPP_
    
    #include <vector>
    
    class ProcedureCallback {
    public:
        virtual void callback(std::vector<int>) = 0;
    };
    
    #endif /* PROCEDURECALLBACK_HPP_ */
    

    所以要使用它,您可以创建一个 Client ,传递子类 ProcedureCallback 对客户的 invoke 方法,然后客户端调用 callback 方法,并传递一个空的int向量。

    这是swig接口文件:

    %module(directors="1") debugasync
    %feature("director");
    
    %{
    #include "Client.hpp"
    #include "ProcedureCallback.hpp"
    %}
    
    %include "Client.hpp"
    %include "ProcedureCallback.hpp"
    

    它的输出非常大,所以我把它放在Pastebin上: debugasync_wrap.cpp . 对此文件感兴趣的可能是swigdirector_procedureCallback::callback(第1319行):

    void SwigDirector_ProcedureCallback::callback(std::vector< int > arg0) {
      zval *args[1];
      zval *result, funcname;
      MAKE_STD_ZVAL(result);
      ZVAL_STRING(&funcname, (char *)"callback", 0);
      if (!swig_self) {
        SWIG_PHP_Error(E_ERROR, "this pointer is NULL");
      }
    
      zval obj0;
      args[0] = &obj0;
      {
        SWIG_SetPointerZval(&obj0, SWIG_as_voidptr(&arg0), SWIGTYPE_p_std__vectorT_int_t, 2);
      }
      call_user_function(EG(function_table), (zval**)&swig_self, &funcname,
        result, 1, args TSRMLS_CC);
      FREE_ZVAL(result);
      return;
    fail:
      zend_error(SWIG_ErrorCode(),"%s",SWIG_ErrorMsg());
    }
    

    这可能也很有趣(第827行):

    static void
    SWIG_ZTS_SetPointerZval(zval *z, void *ptr, swig_type_info *type, int newobject TSRMLS_DC) {
      swig_object_wrapper *value=NULL;
      /*
       * First test for Null pointers.  Return those as PHP native NULL
       */
      if (!ptr ) {
        ZVAL_NULL(z);
        return;
      }
      if (type->clientdata) {
        if (! (*(int *)(type->clientdata)))
          zend_error(E_ERROR, "Type: %s failed to register with zend",type->name);
        value=(swig_object_wrapper *)emalloc(sizeof(swig_object_wrapper));
        value->ptr=ptr;
        value->newobject=newobject;
        if (newobject <= 1) {
          /* Just register the pointer as a resource. */
          ZEND_REGISTER_RESOURCE(z, value, *(int *)(type->clientdata));
        } else {
          /*
           * Wrap the resource in an object, the resource will be accessible
           * via the "_cPtr" member. This is currently only used by
           * directorin typemaps.
           */
          value->newobject = 0;
          zval *resource;
          MAKE_STD_ZVAL(resource);
          ZEND_REGISTER_RESOURCE(resource, value, *(int *)(type->clientdata));
          zend_class_entry **ce = NULL;
          zval *classname;
          MAKE_STD_ZVAL(classname);
          /* _p_Foo -> Foo */
          ZVAL_STRING(classname, (char*)type->name+3, 1);
          /* class names are stored in lowercase */
          php_strtolower(Z_STRVAL_PP(&classname), Z_STRLEN_PP(&classname));
          if (zend_lookup_class(Z_STRVAL_P(classname), Z_STRLEN_P(classname), &ce TSRMLS_CC) != SUCCESS) {
            /* class does not exist */
            object_init(z);
          } else {
            object_init_ex(z, *ce);
          }
          Z_SET_REFCOUNT_P(z, 1);
          Z_SET_ISREF_P(z);
          zend_hash_update(HASH_OF(z), (char*)"_cPtr", sizeof("_cPtr"), (void*)&resource, sizeof(zval), NULL);
          FREE_ZVAL(classname);
        }
        return;
      }
      zend_error(E_ERROR, "Type: %s not registered with zend",type->name);
    }
    

    并演示PHP中的内存泄漏( debugasync.php 是由swig生成的一组代理类,我也将其上载到了pastebin):

    <?php
    
    require('debugasync.php');
    
    class MyCallback extends ProcedureCallback {
        public function callback($intVector) {}
    }
    
    $client = new Client();
    $callback = new MyCallback();
    
    while (true) {
        print(number_format(memory_get_usage()) . "\n");
        for ($j = 0; $j < 1000; $j++) {
            $client->invoke($callback);
        }
    }
    

    这将打印内存使用情况,执行1K调用并重复。运行它会显示快速增长的内存空间:

    $ php test.php 
    692,664
    1,605,488
    2,583,232
    3,634,776
    4,538,784
    5,737,760
    6,641,768
    7,545,816
    ^C
    

    还需要注意的是,如果C++回调传递一个基元(即 int )而不是复杂类型(即 std::vector<int> )没有内存泄漏。

    内存泄漏的原因是什么?

    更一般地说,我可以使用什么工具来解决这个问题?Valgrind的Massif甚至在用调试符号构建了PHP之后,也没有真正缩小范围。

    1 回复  |  直到 14 年前
        1
  •  2
  •   Artefacto    14 年前

    我对Swig一无所知,但是如果内存使用率由 memory_get_usage ,然后使用Zend引擎内存管理器分配占用的内存。

    当脚本完成时 干净地 (没有CTRL+C或 die ,内存管理器将告诉您发现的内存泄漏,只要:

    • PHP以调试模式编译( --enable-debug )
    • 你有 report_memleaks = true 在php.ini文件中

    这将告诉您在哪里分配了未释放的内存。

    也就是说,您的代码片段没有什么特别有趣的地方;只有非堆栈分配的变量得到了正确的处理。