Python PyLongObject内存泄漏

Python PyLongObject内存泄漏,python,c,python-3.x,python-c-api,python-extensions,Python,C,Python 3.x,Python C Api,Python Extensions,我的编码方法中存在内存泄漏 静态常量大小\u t\u位每\u位=大小数字*字符位; /** *返回存储Elias gamma编码输入列表的位流的Python int。 */ 静态PyObject* encodePyObject*self,PyObject*obj { 如果!PyList_Checkobj{ PyErr_SetStringPyExc_TypeError,要编码的输入必须是列表; 返回NULL; } const Py_ssize_t list_length=PyList_GET_SI

我的编码方法中存在内存泄漏

静态常量大小\u t\u位每\u位=大小数字*字符位; /** *返回存储Elias gamma编码输入列表的位流的Python int。 */ 静态PyObject* encodePyObject*self,PyObject*obj { 如果!PyList_Checkobj{ PyErr_SetStringPyExc_TypeError,要编码的输入必须是列表; 返回NULL; } const Py_ssize_t list_length=PyList_GET_SIZEobj; size\u t size=\u cell\u divsize\u PyList\u GET\u SIZEobj,\u位/u位; 大小\u t当前\u位=0; 大小\u t当前\u位=0; PyLongObject*bstream=\u PyLong\u Newsize; 如果bstream==NULL{ 返回NULL;/\u PyLong\u新集合错误 } 对于Py\u ssize\u t i=0;i0{ if-Pyrr_检查信号{ 返回NULL; } 如果当前_位==0{ b流->对象数字[当前数字]=数字0; 如果0左>每位{ 左零-=\u位/u位; ++当前_位; }否则{ 当前位+=左零; 如果当前位=每位{ 当前_位=0; ++当前_位; } 零左=0; } }否则{ 数字掩码=~digit-1>>当前\u位;//启用当前\u位的最高有效位 bstream->ob_digit[当前_digit]&=mask;//将此数字的剩余部分设置为零 数字零写入=\u位每\u位\u-当前\u位; 如果左置零>写入零{ 左零-=写入的零; 当前_位=0; ++当前_位; }否则{ 当前位+=左零; 如果当前位=每位{ 当前_位=0; ++当前_位; } 零左=0; } } } //写数字的二进制表示法 大小\u t位\u左=N+1; 而位_左>0{ if-Pyrr_检查信号{ 返回NULL; } 无符号长掩码=1UL ob_位[当前_位]|=~位-1>>1>>当前_位; }否则{ b流->ob_位[当前_位]&=~~位-1>>1>>当前_位; } ++当前_位; 如果当前位=每位{ 当前_位=0; ++当前_位; } -左位; 掩码>>=1; } } //删除未使用的数字 如果大小-当前数字>1{ 大小=当前数字+1; PyLongObject*new=PyLongObject*PyObject\u Reallocbstream,offsetofPyLongObject,ob\u位+大小*\u位/u位; ifnew==NULL{ PyObject_Freebstream; 返回PyErr_nomery; } b流=新的; PyObject_InitVarPyVarObject*b流和PyLong_类型、大小; } //用零填充剩余部分,覆盖malloc随机性 数字掩码=~数字-1>>当前\u位; b流->对象数字[当前数字]&=掩码; 返回PyObject*b流; } 用Python

tracemalloc.start()
encoded = encode(numbers)
sleep(3) # give garbage collection time to run
size, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print("size, peak", size, peak)
print(sys.getsizeof(encoded))
印刷品

size, peak 17053496 17053496
2131708
而不是预期的

size, peak 2131708 ...
2131708
使用我自己的类型而不是PyLongObject,它看起来和我的眼睛一样,完全不会导致内存泄漏和输出

size, peak 2131309 2131309
2131309
这是具有该输出的类型

类型定义结构{ PyObject_VAR_头 uint8_t数据[1]; }比特流; 静态PyTypeObject BitStreamType={ PyVarObject\u HEAD\u INITNULL,0 .tp_name=elias_gamma_code.BitStream, .tp_dealoc=0, .tp_free=PyObject_Del, .tp_basicsize=偏移流、数据、, .tp_itemsize=sizeofuint8_t, .tp_flags=Py_TPFLAGS_默认值, }; 因此,使用PyLongObject有一些特别之处,特别是它会导致大量内存使用

我猜这是因为PyObject_Realloc,所以我在这里问:

但显然

如果返回非空指针,则obj已被释放

事实上,使用PyObject_Freeprevious_obj导致被释放的指针未分配malloc错误

Elias gamma编码将非零正二进制整数表示为N个前导零,N是数字-1的位长度,与floorlog2num相同,后跟数字本身

+----+--------+ |数字|γ编码| +----+--------+ | 1 | 1 | | 2 | 0 10 | | 3 | 0 11 | | 4 | 00 100 | | 5 | 00 101 | | ... | | | 15 | 000 1111 | | 16 | 0000 1 0000 | | ... | | | 31 | 0000 1 1111 | | 32 | 00000 10 0000 | 列表中的所有数字都可以这样编码和连接。生成的比特流可以毫不含糊地解码到原始列表。

在错误路径中,给定

PyLongObject *bstream = _PyLong_New(size);
你必须减少对它的引用。但您没有这样做,例如:

if (PyErr_Occurred()) { // number overflowed or was negative
    PyErr_Format(PyExc_ValueError, "Number at list index %zd was negative or too large", i);
    return NULL;
} else if (number == 0) { // unencodable
    PyErr_Format(PyExc_ValueError, "Zero at list index %zd", i);
    return NULL;
}
正确的C习惯用法是转到错误处理标签:

    PyLongObject *bstream = NULL;

    if (!(bstream = _PyLong_New(size))) {
        goto error;
    }

    if (! some other check) {
        goto error;
    }
    
    ...
    return bstream;

error:
    // xdecref requires that the pointer is initialized in all
    // code paths, either to null or pointing to a valid live PyObject 

    Py_XDECREF(bstream);
    return NULL;
}
另一个问题是,在Python中是否允许realloc一个长对象。。。将来它可能会崩溃

最后这个看起来有点奇怪:

size * _bits_per_digit
PyObject_Realloc将新的大小作为字节,那么为什么要将每个数字的位数乘以-而必须乘以数字类型的宽度大小。

在错误路径中,给定

PyLongObject *bstream = _PyLong_New(size);
你必须减少对它的引用。但您没有这样做,例如:

if (PyErr_Occurred()) { // number overflowed or was negative
    PyErr_Format(PyExc_ValueError, "Number at list index %zd was negative or too large", i);
    return NULL;
} else if (number == 0) { // unencodable
    PyErr_Format(PyExc_ValueError, "Zero at list index %zd", i);
    return NULL;
}
正确的C习惯用法是转到错误处理标签:

    PyLongObject *bstream = NULL;

    if (!(bstream = _PyLong_New(size))) {
        goto error;
    }

    if (! some other check) {
        goto error;
    }
    
    ...
    return bstream;

error:
    // xdecref requires that the pointer is initialized in all
    // code paths, either to null or pointing to a valid live PyObject 

    Py_XDECREF(bstream);
    return NULL;
}
另一个问题是,在Python中是否允许realloc一个长对象。。。将来它可能会崩溃

最后这个看起来有点奇怪:

size * _bits_per_digit

PyObject_Realloc将新的大小作为字节,那么为什么您要乘以每个数字的位数-而必须乘以数字类型的宽度大小。

这完全是由于您发现的每个数字的大小*_位数!谢谢非常感谢您指出错误的惯用风格,我应该把标签放在哪里?在方法末尾返回之后?通常的方法是:1在函数开始时将所有PyObject*设置为NULL。在函数末尾有错误:xdecref除了结果之外的所有PyObject;返回结果;因此,成功路径和失败路径之间共享相同的清理代码,但失败时结果为空。这完全是由于您发现的每位数大小*\u位\u造成的!谢谢非常感谢您指出错误的惯用风格,我应该把标签放在哪里?在方法末尾返回之后?通常的方法是:1在函数开始时将所有PyObject*设置为NULL。在函数末尾有错误:xdecref除了结果之外的所有PyObject;返回结果;因此,在成功路径和失败路径之间共享相同的清理代码,但失败时结果为NULL。