Skip to content

内存管理与C互操作

本章讲解Cython中的内存管理和C互操作。Cython支持直接使用C级内存分配,灵活但需谨慎——内存泄漏和野指针是常见问题。

学习路径:内存分配 → 指针操作 → C数组 → C++容器 → 智能指针

核心原则:

  • C级内存需要手动管理,malloc配free,new配delete
  • 使用RAII模式避免内存泄漏
  • typed memoryview是更安全的替代方案

功能说明:使用malloc/calloc/free进行C级内存分配。

# C级内存分配(需手动释放)
cdef int* p1 = <int*>malloc(sizeof(int))
if p1 == NULL:
raise MemoryError("Allocation failed")
# 使用p1...
free(p1) # 必须释放
# 分配数组
cdef int* p2 = <int*>malloc(10 * sizeof(int))
if not p2:
raise MemoryError()
# 使用...
free(p2)

输出示例:

>>> p1 = <int*>malloc(sizeof(int))
>>> p1[0] = 42
>>> p1[0]
42
>>> free(p1)

功能说明:三种内存分配方式,calloc自动清零。

# malloc - 分配原始内存(不初始化)
cdef int* arr = <int*>malloc(100 * sizeof(int))
if arr == NULL:
raise MemoryError()
# calloc - 分配并清零(初始化为0)
cdef int* arr2 = <int*>calloc(100, sizeof(int))
# 所有元素自动为0
# realloc - 调整大小
cdef int* arr3 = <int*>realloc(arr, 200 * sizeof(int))
if arr3 == NULL:
raise MemoryError()
# free - 释放内存
free(arr)
free(arr2)
free(arr3)

输出示例:

>>> arr2[0]
0
>>> arr2[99]
0

常见坑:malloc/realloc失败返回NULL,必须检查;realloc移动数据后旧指针可能失效。

功能说明:Cython提供的自动内存管理方式,减少手动管理负担。

# Python list由GC管理
cdef object lst = [] # Python list
# C级数组但Cython管理
cdef list cy_list = [1, 2, 3, 4, 5]
# Cython自动处理引用计数
# 使用view.array(推荐)
from cython.view import array
cdef int[:] view = array(
shape=(100,),
itemsize=sizeof(int),
format="i"
)
# 自动内存管理

最佳实践:简单场景用Python对象,需要C级性能时用cython.view.array。

功能说明:使用valgrind检测内存泄漏。

Terminal window
# 使用valgrind检测
valgrind --leak-check=full python test.pyx
# 使用cpython API追踪
cimport gc
def check_memory():
gc.collect()
# 查看内存状态
pass

功能说明:指针存储内存地址,解引用访问数据。

# 取地址
cdef int x = 42
cdef int* p = &x
# 解引用
p[0] = 100 # x变为100
print(p[0]) # 100
print(x) # 100
# NULL检查
if p != NULL:
print(p[0])

输出示例:

>>> x
42
>>> p = &x
>>> p[0] = 100
>>> x
100

功能说明:指针算术在数组遍历中常用。

cdef int arr[10] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
cdef int* ap = arr
# 指针算术
print(ap[0]) # arr[0] = 0
print(ap[1]) # arr[1] = 1
print((ap + 5)[0]) # arr[5] = 5
print((ap + 9)[0]) # arr[9] = 9
# 指针差值
cdef ptrdiff_t diff = (ap + 9) - ap # 9

输出示例:

>>> ap[0]
0
>>> (ap + 5)[0]
5
>>> diff
9

功能说明:栈上数组(固定大小)和堆上数组(动态大小)。

# 栈上数组(编译时已知大小)
cdef int stack_arr[100]
cdef int i
for i in range(100):
stack_arr[i] = i
# 堆上数组(运行时决定大小)
cdef int* heap_arr = <int*>malloc(100 * sizeof(int))
for i in range(100):
heap_arr[i] = i * i
# 使用完毕后必须释放
free(heap_arr)
# 指针数组
cdef int* pointers[10]

常见坑:栈空间有限(通常8MB),大数组用堆分配。

功能说明:边界检查防止越界访问。

# 安全访问函数
cdef int safe_get(int* p, int index, int size) except -1:
if index < 0 or index >= size:
raise IndexError("Index out of bounds")
return p[index]
# assert快速检查(生产环境用)
cdef int fast_get(int* p, int index, int size) nogil:
# 假设调用者保证index有效
# 性能更高,但出错时是未定义行为
return p[index]

最佳实践:开发环境用安全检查,生产环境用nogil+assert。


功能说明:编译时已知大小的数组,分配在栈上。

# 编译时已知大小
cdef int fixed[10]
cdef double matrix[3][4]
# 初始化
cdef int arr[5] = [1, 2, 3, 4, 5]
cdef char* msg = "hello" # C字符串
# 栈上分配注意栈溢出
cdef void process_stack():
# 40KB,可能栈溢出
cdef int large[10000]

输出示例:

>>> arr[0]
1
>>> arr[4]
5

常见坑:栈空间有限,大数组用堆分配(malloc)或静态分配。

功能说明:运行时决定大小的数组,分配在堆上。

# 动态C数组
cdef int* dynamic = <int*>malloc(1000 * sizeof(int))
if dynamic == NULL:
raise MemoryError()
# 使用
cdef int i
for i in range(1000):
dynamic[i] = i * i
# 释放
free(dynamic)
# Cython view(更安全)
from cython.view import array
cdef int[::1] view = array(
shape=(100,),
itemsize=sizeof(int),
format="i"
)

功能说明:两种数据结构之间的转换。

# C数组转Python列表
cdef int c_arr[5] = [1, 2, 3, 4, 5]
cdef list py_list = [c_arr[i] for i in range(5)]
# Python列表转C数组
cdef list source = [10, 20, 30, 40, 50]
cdef int target[5]
cdef int j
for j in range(5):
target[j] = source[j]

输出示例:

>>> py_list
[1, 2, 3, 4, 5]

功能说明:通过pxd声明导入C++ vector。

c++vector.pxd
cdef extern from "<vector>" namespace "std":
cdef cppclass vector[T]:
vector() except +
void push_back(T)
T at(int)
int size()
void clear()
# 使用
cdef vector[int] *vec = new vector[int]()
vec.push_back(1)
vec.push_back(2)
vec.push_back(3)
print(vec.size()) # 3
print(vec.at(0)) # 1
del vec # 必须手动删除

输出示例:

>>> vec.size()
3
>>> vec.at(0)
1

功能说明:C++ unordered_map的声明和使用。

cdef extern from "<unordered_map>" namespace "std":
cdef cppclass unordered_map[K, V]:
unordered_map() except +
void insert(pair[K, V])
V& at(K)
int size()
bint find(K)
cdef unordered_map[int, int] *m = new unordered_map[int, int]()
m.insert(pair[int, int](1, 100))
m.insert(pair[int, int](2, 200))
print(m.size()) # 2
print(m.at(1)) # 100
del m

功能说明:std::string与Python str的转换。

cdef extern from "<string>" namespace "std":
cdef cppclass string:
string() except +
string(char*) except +
char* c_str()
int size()
cdef string *s = new string("hello")
cdef char* c_str = s.c_str()
print(c_str) # hello
del s

功能说明:共享所有权的智能指针,引用计数自动管理。

cdef extern from "<memory>" namespace "std":
cdef cppclass shared_ptr[T]:
shared_ptr() except +
shared_ptr(T*) except +
T* get()
int use_count()
cdef shared_ptr[int] *p = new shared_ptr[int](new int(42))
print(p.get().get()) # 42
print(p.use_count()) # 1
del p # 引用计数为0时自动释放

输出示例:

>>> p.get().get()
42
>>> p.use_count()
1

功能说明:独占所有权的智能指针,超出作用域自动释放。

cdef extern from "<memory>" namespace "std":
cdef cppclass unique_ptr[T]:
unique_ptr() except +
unique_ptr(T*) except +
T* release()
T* get()
cdef unique_ptr[int] *p = new unique_ptr[int](new int(100))
cdef int* raw = p.get()
print(raw[0]) # 100
# unique_ptr超出作用域时自动delete

功能说明:RAII模式确保资源正确释放。

# RAII包装类
cdef class SmartPointer:
cdef int* _ptr
cdef bint _owner
def __cinit__(self, int size):
self._ptr = <int*>malloc(size * sizeof(int))
self._owner = True
def __dealloc__(self):
if self._owner and self._ptr != NULL:
free(self._ptr)
cdef int* get_ptr(self):
return self._ptr

方式特点适用场景
malloc/freeC级,手动管理原始C代码互操作
new/deleteC++对象C++ STL容器
shared_ptr自动引用计数共享所有权
unique_ptr自动析构独占所有权
cython.view.array自动GC简化内存管理
  1. C级内存必配free/delete
  2. 使用RAII模式管理资源
  3. 优先用typed memoryview替代手动指针
  4. NULL检查防止解引用空指针
  5. 大数组用堆分配而非栈

  1. 实现RAII包装类管理C数组,创建和销毁时自动malloc/free
  2. 使用malloc/calloc/free分配和操作二维数组
  3. 包装C++ vector并测试push_back/at/size操作
  4. 实现智能指针类,支持引用计数
  5. 比较C数组和Python list的内存使用(用tracemalloc)
  6. 使用valgrind检测内存泄漏