本文目录导读:
- 核心数据结构:
PyLongObject - 运算中的 "溢出检查" 本质:进位传播 + 内存分配
- 乘法中的特殊处理:Karatsuba 和更大规模检查
- 整数转字符串时的溢出(这是最常见报错)
- 特例:空循环和优化
- 完整路径图(加法为例)
- 最终回答用一句话总结
这是一个非常底层的技术问题,CPython 的整数实现非常独特:Python 的整数是任意精度的(大整数),因此在底层操作中,CPython 不依赖于 CPU 硬件的整数溢出标志位,而是通过显式的内存检查和进位传播来防止溢出。
本质上,CPython 的整数运算“没有传统意义上的溢出”,因为它会自动扩展内存来容纳更大的数字,但为了防止分配过大的内存导致崩溃(2 ** (2 ** 100)),它会在分配内存时做显式的大小检查并抛出 OverflowError。
下面从源码角度详细拆解,以 Python 3.11 为基准。
核心数据结构:PyLongObject
先看整数对象在底层的表示,在 Include/longobject.h 和 Objects/longobject.c 中:
// digit 通常是一个 30-bit 或 15-bit 的 "小单元"(取决于平台)
typedef uint32_t digit;
struct _longobject {
PyObject_HEAD
Py_ssize_t ob_size; // 符号和长度:正数为正,负数为负,绝对值是 digit 数组长度
digit ob_digit[1]; // 柔性数组,实际长度由 ob_size 决定
};
关键点:
ob_size的绝对值代表有多少个 30-bit 的 "digit"。ob_size为 0,表示整数 0。- 运算发生在这些 digit 数组上,类似于手算多位十进制数。
运算中的 "溢出检查" 本质:进位传播 + 内存分配
当两个大整数相加时,CPython 会预先计算最大可能需要的 digit 个数,然后分配对应的内存,如果这个 digit 个数超过了某个内部限制(2^63 量级),就会直接抛出 OverflowError。
让我们看一个最基础的函数 x_add(在 Objects/longobject.c 中):
static PyLongObject *
x_add(PyLongObject *a, PyLongObject *b)
{
Py_ssize_t size_a = Py_ABS(Py_SIZE(a));
Py_ssize_t size_b = Py_ABS(Py_SIZE(b));
PyLongObject *z;
Py_ssize_t i;
digit carry = 0;
// 1. 预先计算结果的 digit 个数:最大 size 加 1
Py_ssize_t size_z = MAX(size_a, size_b) + 1;
// 2. 分配内存 —— 这是“溢出检查”的关键
z = _PyLong_New(size_z); // size_z 过大,会在内部检查并抛出 OverflowError
if (z == NULL)
return NULL;
// 3. 逐 digit 相加,处理进位
for (i = 0; i < size_z; i++) {
carry += i < size_a ? a->ob_digit[i] : 0;
carry += i < size_b ? b->ob_digit[i] : 0;
z->ob_digit[i] = carry & PyLong_MASK;
carry >>= PyLong_SHIFT;
}
// 4. 去除最高位可能多余的 0(如果最高位没有进位)
return long_normalize(z);
}
真正的溢出检查发生在 _PyLong_New 中:
PyLongObject *
_PyLong_New(Py_ssize_t size)
{
PyLongObject *result;
// 关键检查:size 超过 PyLong_MAX_BYTES 或 Py_ssize_t 最大范围
// 这里 size 是 digit 个数,每个 digit 通常占4字节
// 因此检查 size * sizeof(digit) 不会超过内存限制
if (size > (PY_SSIZE_T_MAX - PyObject_HEAD_SIZE) / sizeof(digit)) {
// 如果超出,会设置 OverflowError
PyErr_SetString(PyExc_OverflowError, "Python int too large ...");
return NULL;
}
// 否则分配内存
result = PyObject_NewVar(PyLongObject, &PyLong_Type, size);
...
}
当 digit 个数大到逼近 PY_SSIZE_T_MAX 时,_PyLong_New 会报 OverflowError,这是 Python 整数运算中唯一真正的“溢出”。
乘法中的特殊处理:Karatsuba 和更大规模检查
乘法更复杂,CPython 对小数使用教科书乘法,对大数使用 Karatsuba 乘法甚至 FFT 乘法。
在 long_mul 函数中,第一步也是预估结果大小:
static PyObject *
long_mul(PyLongObject *v, PyLongObject *w)
{
// 直观的方法:结果 digit 数 ≈ a_size + b_size
// 先分配
z = _PyLong_New(size_a + size_b);
...
// 如果乘法的中间结果导致 digit 个数预估错误,会在分配时捕获
}
整数转字符串时的溢出(这是最常见报错)
你可能见过 OverflowError: int too large to convert to float 或 OverflowError: cannot fit 'int' into an index-sized integer。
这些通常在类型转换时发生,而不是在算术运算时。
>>> 2 ** 20000 # 成功,生成一个长整数 >>> float(2 ** 20000) # 报错:OverflowError: int too large to convert to float
这是因为 float 只有 53 bit 精度,CPython 在 PyLong_AsDouble 中显式检查:
double
PyLong_AsDouble(PyObject *v)
{
...
// exp > 1024,直接报错
if (exp > 1024) {
set_error("int too large to convert to float");
return -1.0;
}
...
}
特例:空循环和优化
有人会问:while True: x += 1 为什么不会溢出?
因为 Python 整数对象在每次增加时,如果当前 digit 数组能容纳(没有进位导致需要新 digit),就直接修改 ob_digit[0],不需要重新分配,只有当需要新的 digit 时(比如从 2^30-1 到 2^30),才触发新内存分配和相应的 OverflowError 检查。
完整路径图(加法为例)
用户代码:a + b
→ 解释器调用 PyLong_Type 的 nb_add 槽位
→ long_add()
→ x_add() 或 z_add() (取决于符号)
→ 计算 size_z = max(size_a, size_b) + 1
→ _PyLong_New(size_z)
→ if (size > limit) 抛出 OverflowError
→ 分配变量对象
→ 逐 digit 相加,传播进位
→ long_normalize(z) 去除高位零
最终回答用一句话总结
CPython 不依赖 CPU 硬件溢出标志,而是通过预估结果需要的“digit”个数,在内存分配阶段检查是否超出 PY_SSIZE_T_MAX 来防止真正的内存溢出;任何超出该范围的尝试都会抛出 OverflowError。
标签: CPython 整数溢出检查 大整数转换