【问题标题】:Zero reference count and still no segmentation fault零引用计数,仍然没有分段错误
【发布时间】:2018-05-29 14:07:19
【问题描述】:

我在 Python/C API 和内存分配方面具有开发技能,并且预计以下 Python 嵌入式 C++ 代码会出现问题并导致类似于分段错误的情况:

#include <Python.h> 
#include <iostream>

int main(){
  Py_Initialize();

  PyObject* pythonList = Py_BuildValue("[i i]",1,2);

  Py_DECREF(pythonList); // I checked with Py_REFCNT(pythonList) that reference count is now 0

  PyList_Check(pythonList); // hence, I was expecting here something like a segmentation fault, but this does not happen...

  std::cout << "Ok, goodbye" << std::endl;
  return 0;
}

但是,运行时并没有发生任何不好的事情(并且会显示“Ok, goodbye”)。

  • 尽管访问(在PyList_Check(pythonList); 中)一个已减为零的 PyObject,这段代码实际上是否正常?

  • 或者,这个代码是不是错误,只是运气好,这里没有发生分段错误(为什么?)?

【问题讨论】:

  • 从释放的内存中读取会调用未定义的行为,而未定义行为的问题在于它的行为是未定义的。这意味着它可能导致崩溃,可能导致程序继续运行但给出不正确的结果,它可能导致程序继续运行并给出正确的结果,除非在特定类型的 CPU 等上。没有办法表征预期的行为,因为它是未定义的。唯一的解决方案是一开始就不要调用未定义的行为,这样您就不必担心它会做什么。
  • 感谢提供和通过 cmets !

标签: c++ python-c-api reference-counting


【解决方案1】:

代码错误 -- 对Py_DECREF() 的调用释放了对象,这意味着pythonList 是一个悬空指针,所以当PyList_Check() 尝试尊重该指针时,未定义的行为被调用。

至于为什么没有导致分段错误,正式的答案是未定义的行为不需要导致任何特定的可观察结果(例如分段错误)。未定义的行为可能导致程序执行literally anything,程序员有责任避免调用它。

实际上,有一个更令人满意的解释:在大多数流行的系统上,当程序尝试访问未映射到任何物理内存年龄或映射到不允许程序访问的物理页面。因此,如果您将pythonList 设置为指向某个随机/无效的内存位置,然后尝试取消引用它,您可能会遇到分段错误。但是,pythonList 不是指向随机内存位置,而是指向有效 Python List 对象所在的内存位置(直到刚才Py_DECREF() 释放它时)。该内存的“释放”仅仅意味着进程的堆数据结构现在将这部分内存包含在其“空闲内存列表”中,作为可以在下一次进程的其他部分想要分配内存时重用的内存。它不涉及告诉 MMU 该内存位置现在不可访问(一般来说,它不能,因为 MMU 检查内存 pages 的有效性,而不是单个字节的有效性,而且相当常见的内存页面包含有效对象和已释放内存区域混合在一起)。因此,MMU/segmentation-fault 完整性检查系统不会捕获您对释放内存的读取(valgrind 可能会捕获它,但代价是您的程序运行速度比正常情况慢 10-100 倍)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-04-23
    • 1970-01-01
    • 2021-11-03
    • 2020-11-01
    • 2015-08-14
    • 1970-01-01
    • 1970-01-01
    • 2016-05-27
    相关资源
    最近更新 更多