源码位置:cpython\Python\import.c

一.PyImport_AppendInittab
这个函数提供了一个简便方法,用于在Python初始化前向内置模块表中添加单个模块条目。此函数通常被嵌入式应用程序用来在调用Py_Initialize()
之前注册自定义的C扩展模块,使其成为Python解释器内置的一部分,无需通过普通的导入机制即可使用。
/* Shorthand to add a single entry given a name and a function */
int
PyImport_AppendInittab(constchar *name, PyObject* (*initfunc)(void))
{
struct _inittabnewtab[2];
if (INITTAB != NULL) {
Py_FatalError("PyImport_AppendInittab() may not be called after Py_Initialize()");
}
memset(newtab, '/* Shorthand to add a single entry given a name and a function */
int
PyImport_AppendInittab(constchar *name, PyObject* (*initfunc)(void))
{
struct _inittabnewtab[2];
if (INITTAB != NULL) {
Py_FatalError("PyImport_AppendInittab() may not be called after Py_Initialize()");
}
memset(newtab, '\0', sizeof newtab);
newtab[0].name = name;
newtab[0].initfunc = initfunc;
return PyImport_ExtendInittab(newtab);
}
', sizeof newtab);
newtab[0].name = name;
newtab[0].initfunc = initfunc;
return PyImport_ExtendInittab(newtab);
}
1.创建临时表结构:
-
创建一个包含两个元素的
_inittab
结构体数组newtab
-
第一个元素用于存储新模块信息
-
第二个元素作为表的终止标记(全NULL)
2.检查Python初始化状态
-
如果
INITTAB
不为NULL,表示Python已经初始化 -
此时不允许添加新的内置模块,会触发致命错误
-
这是一个安全检查,确保模块在Python启动前注册
3.初始化表条目
-
使用
memset
将整个newtab
数组清零 -
设置第一个条目的模块名称和初始化函数
-
第二个条目保持为NULL,作为表的终止标记
4.添加到全局表
-
调用
PyImport_ExtendInittab(newtab)
将新模块添加到全局表中 -
返回添加操作的结果(0表示成功,-1表示失败)
二.PyImport_ExtendInittab
这个函数允许嵌入式应用程序向 Python 内置模块表添加自定义条目。
/* API for embedding applications that want to add their own entries
to the table of built-in modules. This should normally be called
*before* Py_Initialize(). When the table resize fails, -1 is
returned and the existing table is unchanged.
After a similar function by Just van Rossum. */
int
PyImport_ExtendInittab(struct _inittab *newtab)
{
struct _inittab *p;
size_t i, n;
int res = 0;
if (INITTAB != NULL) {
Py_FatalError("PyImport_ExtendInittab() may not be called after Py_Initialize()");
}
/* Count the number of entries in both tables */
for (n = 0; newtab[n].name != NULL; n++)
;
if (n == 0)
return0; /* Nothing to do */
for (i = 0; PyImport_Inittab[i].name != NULL; i++)
;
/* Force default raw memory allocator to get a known allocator to be able
to release the memory in _PyImport_Fini2() */
/* Allocate new memory for the combined table */
p = NULL;
if (i + n <= SIZE_MAX / sizeof(struct _inittab) - 1) {
size_t size = sizeof(struct _inittab) * (i + n + 1);
p = _PyMem_DefaultRawRealloc(inittab_copy, size);
}
if (p == NULL) {
res = -1;
goto done;
}
/* Copy the tables into the new memory at the first call
to PyImport_ExtendInittab(). */
if (inittab_copy != PyImport_Inittab) {
memcpy(p, PyImport_Inittab, (i+1) * sizeof(struct _inittab));
}
memcpy(p + i, newtab, (n + 1) * sizeof(struct _inittab));
PyImport_Inittab = inittab_copy = p;
done:
return res;
}
该函数目的为让嵌入 Python 的应用程序可以注册自己的内置模块,必须在 Py_Initialize()
调用之前使用。它的工作流程如下:
1.初始检查
-
如果 INITTAB
不为空,说明 Python 已初始化,此时调用会导致程序终止
2.计算表项数量
-
统计新表
newtab
中的条目数量(n
) -
如果
n
为 0,直接返回 0(无需处理) -
统计现有表
PyImport_Inittab
中的条目数量(i
)
3.内存分配
-
检查整数溢出风险
-
计算所需内存:
sizeof(struct _inittab) * (i + n + 1)
-
使用
_PyMem_DefaultRawRealloc
分配内存 -
分配失败时设置返回值 -1
4.合并表内容
-
首次调用时复制
PyImport_Inittab
到新内存 -
将
newtab
内容复制到新内存的后半部分 -
更新全局指针
PyImport_Inittab
和inittab_copy
三._PyMem_DefaultRawRealloc
源码位置:cpython\Objects\obmalloc.c
_PyMem_DefaultRawRealloc
函数是Python内存管理系统中的一个关键组件,用于重新分配内存块。这个函数提供了一个默认的内存重分配实现,它是不受PyMem_SetAllocator()
影响的标准内存分配器。
void *
_PyMem_DefaultRawRealloc(void *ptr, size_t size)
{
#ifdef Py_DEBUG
return _PyMem_DebugRawRealloc(&_PyRuntime.allocators.debug.raw, ptr, size);
#else
return _PyMem_RawRealloc(NULL, ptr, size);
#endif
}
ptr
表示指向要重新分配的现有内存块的指针,size
表示请求的新内存大小(以字节为单位)。函数根据Python构建模式选择不同的内存分配策略:
1.调试模式下(Py_DEBUG
)
-
调用
_PyMem_DebugRawRealloc
函数 -
传递
&_PyRuntime.allocators.debug.raw
作为上下文 -
启用额外的内存边界检查、填充和追踪功能
2.非调试模式下
-
调用简单的
_PyMem_RawRealloc
函数 -
传递
NULL
作为上下文参数 -
本质上是对标准C库
realloc()
的轻量级包装
3.使用场景
这个函数主要在Python内部使用,作为原始内存分配器的默认实现,确保即使自定义分配器被设置,某些核心操作仍能正常工作。
四._PyMem_DebugRawRealloc
源码位置:cpython\Objects\obmalloc.c
这个函数是Python内存调试系统的一部分,用于重新分配内存块,同时保持完整的调试信息。这个函数通过在内存块周围添加额外的标记,帮助开发者发现诸如缓冲区溢出、使用已释放内存等问题。
void *
_PyMem_DebugRawRealloc(void *ctx, void *p, size_t nbytes)
{
if (p == NULL) {
return _PyMem_DebugRawAlloc(0, ctx, nbytes);
}
debug_alloc_api_t *api = (debug_alloc_api_t *)ctx;
uint8_t *head; /* base address of malloc'ed pad block */
uint8_t *data; /* pointer to data bytes */
uint8_t *r;
uint8_t *tail; /* data + nbytes == pointer to tail pad bytes */
size_t total; /* 2 * SST + nbytes + 2 * SST */
size_t original_nbytes;
#define ERASED_SIZE 64
_PyMem_DebugCheckAddress(__func__, api->api_id, p);
data = (uint8_t *)p;
head = data - 2*SST;
original_nbytes = read_size_t(head);
if (nbytes > (size_t)PY_SSIZE_T_MAX - PYMEM_DEBUG_EXTRA_BYTES) {
/* integer overflow: can't represent total as a Py_ssize_t */
returnNULL;
}
total = nbytes + PYMEM_DEBUG_EXTRA_BYTES;
tail = data + original_nbytes;
#ifdef PYMEM_DEBUG_SERIALNO
size_t block_serialno = read_size_t(tail + SST);
#endif
#ifndef Py_GIL_DISABLED
/* Mark the header, the trailer, ERASED_SIZE bytes at the begin and
ERASED_SIZE bytes at the end as dead and save the copy of erased bytes.
*/
uint8_t save[2*ERASED_SIZE]; /* A copy of erased bytes. */
if (original_nbytes <= sizeof(save)) {
memcpy(save, data, original_nbytes);
memset(data - 2 * SST, PYMEM_DEADBYTE,
original_nbytes + PYMEM_DEBUG_EXTRA_BYTES);
}
else {
memcpy(save, data, ERASED_SIZE);
memset(head, PYMEM_DEADBYTE, ERASED_SIZE + 2 * SST);
memcpy(&save[ERASED_SIZE], tail - ERASED_SIZE, ERASED_SIZE);
memset(tail - ERASED_SIZE, PYMEM_DEADBYTE,
ERASED_SIZE + PYMEM_DEBUG_EXTRA_BYTES - 2 * SST);
}
#endif
/* Resize and add decorations. */
r = (uint8_t *)api->alloc.realloc(api->alloc.ctx, head, total);
if (r == NULL) {
/* if realloc() failed: rewrite header and footer which have
just been erased */
nbytes = original_nbytes;
}
else {
head = r;
#ifdef PYMEM_DEBUG_SERIALNO
bumpserialno();
block_serialno = serialno;
#endif
}
data = head + 2*SST;
write_size_t(head, nbytes);
head[SST] = (uint8_t)api->api_id;
memset(head + SST + 1, PYMEM_FORBIDDENBYTE, SST-1);
tail = data + nbytes;
memset(tail, PYMEM_FORBIDDENBYTE, SST);
#ifdef PYMEM_DEBUG_SERIALNO
write_size_t(tail + SST, block_serialno);
#endif
#ifndef Py_GIL_DISABLED
/* Restore saved bytes. */
if (original_nbytes <= sizeof(save)) {
memcpy(data, save, Py_MIN(nbytes, original_nbytes));
}
else {
size_t i = original_nbytes - ERASED_SIZE;
memcpy(data, save, Py_MIN(nbytes, ERASED_SIZE));
if (nbytes > i) {
memcpy(data + i, &save[ERASED_SIZE],
Py_MIN(nbytes - i, ERASED_SIZE));
}
}
#endif
if (r == NULL) {
returnNULL;
}
if (nbytes > original_nbytes) {
/* growing: mark new extra memory clean */
memset(data + original_nbytes, PYMEM_CLEANBYTE,
nbytes - original_nbytes);
}
return data;
}
1.初始检查
-
如果输入指针
p
是 NULL,直接调用_PyMem_DebugRawAlloc
分配新内存并返回 -
否则从上下文
ctx
中获取调试API信息
2.内存布局解析
-
数据指针
data
就是传入的p
-
头部指针
head
位于数据前2*SST
字节处 -
从头部读取原始分配的字节数
original_nbytes
-
检查新的字节数是否会导致整数溢出
3.保存原始数据 (在非GIL禁用模式下)
-
定义局部缓冲区
save[2*ERASED_SIZE]
保存原始数据 -
根据原始大小采取不同的策略:
-
如果原始数据较小,全部保存
-
否则只保存开头和结尾的
ERASED_SIZE
字节 -
同时将头部、尾部和部分数据区域标记为”死亡”状态(
PYMEM_DEADBYTE
)
4.重新分配内存
-
调用底层的
realloc
重新分配内存 -
如果失败(返回NULL),保持使用原始大小
-
成功则更新
head
指针和序列号(如果启用了序列号功能)
5.重建内存块结构
-
重新写入大小信息、API ID和边界标记
-
在数据区域前后添加用于检测缓冲区溢出的”禁止字节”
6.恢复原始数据
-
将之前保存的数据复制回新分配的内存
-
如果是扩大内存,将新增部分标记为”干净”状态(
PYMEM_CLEANBYTE
)
7.返回
-
如果重新分配失败,返回NULL
-
否则返回指向数据区域的指针(不是原始分配的内存块起始位置)
五._PyMem_RawRealloc
源码位置:cpython\Objects\obmalloc.c
这段代码是Python内存管理系统中的_PyMem_RawRealloc
函数实现,它是对C标准库realloc
函数的封装。
void *
_PyMem_RawRealloc(void *Py_UNUSED(ctx), void *ptr, size_t size)
{
if (size == 0)
size = 1;
return realloc(ptr, size);
}
1.函数签名
void *_PyMem_RawRealloc(void *Py_UNUSED(ctx), void *ptr, size_t size)
-
返回类型:
void *
– 指向重新分配后内存区域的指针 -
参数:
-
void *Py_UNUSED(ctx)
– 上下文参数,使用Py_UNUSED
宏标记为未使用 -
void *ptr
– 待重新分配的内存块指针 -
size_t size
– 请求的新内存大小(字节数)
2.函数功能
-
重新分配指定内存块的大小
-
如果
size
为0,将其设置为1(第83-84行) -
调用标准C库的
realloc
函数执行实际的内存重分配
3.特殊处理
-
当
size
为0时设置为1的处理是为了解决不同系统上realloc(ptr, 0)
行为不一致的问题 -
某些系统可能将
realloc(ptr, 0)
视为free(ptr)
并返回NULL -
通过确保至少分配1字节,Python保证了跨平台的一致行为
这是Python内存分配系统中的一个底层函数,它属于”raw”内存分配器系列,直接与系统内存分配交互,不使用Python的对象分配机制。
参考文献
[0] PyImport_AppendInittab函数解析:https://z0yrmerhgi8.feishu.cn/wiki/DlbEwVBnXil6erk8VDVcuFQfnmT
[1] Python初始化配置:https://docs.python.org/zh-cn/3.14/c-api/init_config.html#
知识星球服务内容:Dify源码剖析及答疑,Dify对话系统源码,NLP电子书籍报告下载,公众号所有付费资料。加微信buxingtianxia21进NLP工程化资料群。
(文:NLP工程化)