问题描述
我正在编写一个用于缓存的小型库。 Python dict 不适合我,已经尝试过 std::map,得到 SIGSEGV 的错误非常相似。无论如何,整点在下面的日志中进行了描述。我究竟做错了什么?有没有其他方法可以在 C 中存储对象?
问题:
(gdb) run
The program being debugged has been started already.
Start it from the beginning? (y or n) y
Starting program: /usr/bin/python3
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Python 3.9.2 (default,Feb 20 2021,18:40:11)
[GCC 10.2.0] on linux
Type "help","copyright","credits" or "license" for more information.
>>> from syncached import cache
>>> cache.push(1,object())
>>> cache.get(1) == object()
True
>>> cache.get(1) == object()
Program received signal SIGSEGV,Segmentation fault.
0x00007ffff7d049d0 in PyMem_Calloc () from /usr/lib/libpython3.9.so.1.0
(gdb) bt
#0 0x00007ffff7d049d0 in PyMem_Calloc () from /usr/lib/libpython3.9.so.1.0
#1 0x00007ffff7cfb27d in PyList_New () from /usr/lib/libpython3.9.so.1.0
#2 0x00007ffff7d6f4e3 in ?? () from /usr/lib/libpython3.9.so.1.0
#3 0x00007ffff7de2e37 in PyAST_CompileObject () from /usr/lib/libpython3.9.so.1.0
#4 0x00007ffff7de2c3b in ?? () from /usr/lib/libpython3.9.so.1.0
#5 0x00007ffff7cf68ab in ?? () from /usr/lib/libpython3.9.so.1.0
#6 0x00007ffff7cf6a63 in PyRun_InteractiveLoopFlags () from /usr/lib/libpython3.9.so.1.0
#7 0x00007ffff7c84f6b in PyRun_AnyFileExFlags () from /usr/lib/libpython3.9.so.1.0
#8 0x00007ffff7c7965c in ?? () from /usr/lib/libpython3.9.so.1.0
#9 0x00007ffff7dc9fa9 in Py_BytesMain () from /usr/lib/libpython3.9.so.1.0
#10 0x00007ffff7a46b25 in __libc_start_main () from /usr/lib/libc.so.6
#11 0x000055555555504e in _start ()
pyhashmap.c:
#include "Python.h"
#include <stdlib.h>
typedef struct {
Py_hash_t key;
PyObject *val;
} hashmap_member;
typedef struct {
size_t cache_size;
size_t currsize;
hashmap_member *list;
} pyhashmap;
pyhashmap *new_map(size_t size){
pyhashmap *map = PyMem_Malloc(sizeof(pyhashmap));
map->cache_size = size;
map->currsize = 0;
map->list = PyMem_Malloc(size*sizeof(hashmap_member));
return map;
}
void map_insert(pyhashmap *map,Py_hash_t key,PyObject *val){
if (map->currsize == map->cache_size){
return;
}
for (size_t i = 0; i < map->currsize; i++){
if (map->list[i].key == key){
return;
}
}
map->list[map->currsize] = (hashmap_member) {.key = key,.val = val};
map->currsize++;
}
PyObject *map_get(pyhashmap *map,Py_hash_t key){
for (size_t i = 0; i < map->currsize; i++){
if (map->list[i].key == key){
return map->list[i].val;
}
}
return Py_None;
}
ipyhashmap.pxd:
cdef extern from "pyhashmap.c":
ctypedef struct pyhashmap
pyhashmap *new_map(size_t)
void map_insert(pyhashmap *,int,object)
object map_get(pyhashmap *,int)
cache.pyx:
from syncached.ipyhashmap cimport pyhashmap,new_map,map_insert,map_get
cdef pyhashmap *map = new_map(5)
cpdef push(int key,object val):
map_insert(map,key,val)
cpdef get(key):
return map_get(map,key)
另外,第二个问题:
>>> cache.push(3,{"a": "B"})
>>> cache.get(3)
{3: 3,((<NULL>,'get'),('cache','get')): ((((((...),()),None),(3,None)),'get')),((((((...),((((...),((...),()): ((((((...),(((...),None)): ((((...),'Py_Repr': [{...},[...]]}
>>> cache.get(3)
KeyError: 'unkNown symbol table entry'
>>> cache.get(3)
[1] 21720 segmentation fault (core dumped) python3
解决方法
选项 1:让另一个 python 对象引用存储的 python 对象。
我推荐的最好方法是通过将这些对象存储在 Python 列表、字典或集合中,防止存储的 Python 对象在自定义地图中时被垃圾收集。这将确保在存储对象时引用计数不会降为零。
选项 2:手动管理存储的 Python 对象引用计数。
在处理指向 Python 对象 (PyObject*) 的指针时,您可以尝试手动管理引用计数。如果您增加引用计数但不减少相同的次数,则不再使用该对象时将永远不会从内存中删除该对象。占用的不能被应用程序回收,即内存会泄漏。但是,如果您不增加引用计数,则可能会在您仍在 C 代码中引用该对象时删除该对象。
您可以尝试使用 Python's Reference Counting API 中的 Py_INCREF 和 Py_DECREF 直接在 C 中管理内存(请参阅类似问题 here 的答案)。如果允许您使用 C++ 而不是 C,那么 RAII 可以使引用计数管理更简单。