【问题标题】:CPython's PyLong_FromLong(0L) never fails?CPython 的 PyLong_FromLong(0L) 永远不会失败?
【发布时间】:2020-06-03 01:36:16
【问题描述】:

这段代码来自Python C-API reference

item = PyLong_FromLong(0L);
if (item == NULL)
    goto error;

假设解释器是 CPython,那么 Python 0 对象的内存是 already allocated,那么那里会出现什么问题呢?阅读PyLong_FromLong 的源代码,对于小整数值,它将是immediately return get_small_int((sdigit)0L)get_small_int 函数真的是very simple

static PyObject *
get_small_int(sdigit ival)
{
    assert(IS_SMALL_INT(ival));
    PyThreadState *tstate = _PyThreadState_GET();
    PyObject *v = (PyObject*)tstate->interp->small_ints[ival + NSMALLNEGINTS];
    Py_INCREF(v);
    return v;
}

第一行的断言不会失败,因为PyLong_FromLong 已经验证了它。 _PyThreadState_GET() 是一个宏,根据其定义旁边的 commentis unsafe: it does not check for error and it can return NULL. 这可能看起来像是故障源,但请注意tstate->interp 可以正常访问,这会出现段错误如果宏返回了NULL,则为解释器。之后,对 Python 0 对象的想要引用是 Py_INCREF'd 并返回给 PyLong_FromLong(0L) 的原始调用者。

我是否正确理解 CPython 的 PyLong_FromLong 对于小整数参数不会失败,还是我错过了什么?另外,为了完整起见,是否可以从其他解释器中使用用 C 编写的扩展模块,或者在编写它们时我可以假设它们将处理 CPython?

【问题讨论】:

  • 哦,make this assumption fail with ctypes 可能是微不足道的。问题提到了 32 位平台,但是在运行在 64 位平台上的 Python 3 解释器上,您可以尝试类似ctypes.c_long.from_address(id(1) + 24).value = 2 来操纵1 的基础值,然后执行1 == 2 并且解释器可能会出现段错误.
  • 很好,虽然我肯定会认为这不会发生,呵呵。
  • PyPy 尝试定义 C API 兼容层(我认为 Jython 也是如此),因此您总是在处理 CPython 的假设可能不正确。但无论如何可能仍然是一个合理的假设。我想我只是对基于被认为是“小整数”的代码编写代码有点谨慎

标签: python error-handling python-c-api


【解决方案1】:

是的,你完全正确,这里永远不会有任何错误,小整数是预先分配的,所以 PyLong_FromLong(0L); 永远不会失败。

但为什么还要测试 item 是否为 NULL?如我所见,这只是一个部分:

  • 新建一个 PyLongObject;
  • 然后测试是否成功。

PyLong_FromLong 通过缓存小的 int 做了一些优化,但它仍然主要和逻辑地返回一个新对象,尽管 0L 没有。作为一个函数调用者,我们不应该依赖内部缓存策略。所以留下一些代码来检查会更有意义和更安全。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 2014-01-24
    • 2011-02-24
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    相关资源
    最近更新 更多