PyImport_AppendInittab函数解析

源码位置: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工程化)

发表评论