零引用计数,仍然没有分段错误
Zero reference count and still no segmentation fault
我有 Python/C API 和内存分配方面的开发技能,并且预计以下 Python-embedded 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,这段代码实际上是否正确?
或者,这段代码是否错误,这里没有发生分段错误只是运气问题(为什么?)?
代码 错误 -- 对 Py_DECREF()
的调用释放了对象,这意味着 pythonList
是一个悬挂指针,所以当PyList_Check()
尝试引用该指针,调用未定义的行为。
至于为什么这没有导致分段错误,正式的答案是未定义的行为不需要导致任何特定的可观察结果(例如分段错误)。未定义的行为可能导致程序执行 literally anything,程序员有责任避免调用它。
但实际上,有一个更令人满意的解释:在大多数流行的系统上,当程序试图访问未映射到任何物理内存年龄或已映射的虚拟内存页面时,会导致分段错误到不允许程序访问的物理页面。因此,如果您将 pythonList
设置为指向某些 random/invalid 内存位置,然后尝试取消引用它,您可能会遇到分段错误。但是,pythonList
不是指向随机内存位置,而是指向有效 Python List 对象所在的内存位置(直到刚才 Py_DECREF()
被释放它)。该内存的 "freeing" 仅意味着该进程的堆数据结构现在在其 "free memory list" 中包含该部分内存作为下次该进程的其他部分想要分配时可以重用的内存记忆。它不涉及告诉 MMU 该内存位置现在不可访问(一般来说,它不能,因为 MMU 检查内存 页 的有效性,而不是单个字节的有效性,并且有一个包含有效对象和已释放内存区域混合在一起的内存页面是很常见的)。所以 MMU/segmentation-fault 健全性检查系统不会捕捉到你对释放内存的读取(valgrind 可能会捕捉到它,但代价是你的程序 运行 比正常情况慢 10-100 倍)。
我有 Python/C API 和内存分配方面的开发技能,并且预计以下 Python-embedded 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,这段代码实际上是否正确?或者,这段代码是否错误,这里没有发生分段错误只是运气问题(为什么?)?
代码 错误 -- 对 Py_DECREF()
的调用释放了对象,这意味着 pythonList
是一个悬挂指针,所以当PyList_Check()
尝试引用该指针,调用未定义的行为。
至于为什么这没有导致分段错误,正式的答案是未定义的行为不需要导致任何特定的可观察结果(例如分段错误)。未定义的行为可能导致程序执行 literally anything,程序员有责任避免调用它。
但实际上,有一个更令人满意的解释:在大多数流行的系统上,当程序试图访问未映射到任何物理内存年龄或已映射的虚拟内存页面时,会导致分段错误到不允许程序访问的物理页面。因此,如果您将 pythonList
设置为指向某些 random/invalid 内存位置,然后尝试取消引用它,您可能会遇到分段错误。但是,pythonList
不是指向随机内存位置,而是指向有效 Python List 对象所在的内存位置(直到刚才 Py_DECREF()
被释放它)。该内存的 "freeing" 仅意味着该进程的堆数据结构现在在其 "free memory list" 中包含该部分内存作为下次该进程的其他部分想要分配时可以重用的内存记忆。它不涉及告诉 MMU 该内存位置现在不可访问(一般来说,它不能,因为 MMU 检查内存 页 的有效性,而不是单个字节的有效性,并且有一个包含有效对象和已释放内存区域混合在一起的内存页面是很常见的)。所以 MMU/segmentation-fault 健全性检查系统不会捕捉到你对释放内存的读取(valgrind 可能会捕捉到它,但代价是你的程序 运行 比正常情况慢 10-100 倍)。