PEP 3123 – 使 PyObject_HEAD 符合标准 C
- 作者:
- Martin von Löwis <martin at v.loewis.de>
- 状态:
- 最终
- 类型:
- 标准跟踪
- 创建:
- 2007年4月27日
- Python 版本:
- 3.0
- 历史记录:
目录
摘要
Python 目前依赖于未定义的 C 行为,它使用了 PyObject_HEAD
。此 PEP 提出将其更改为标准 C。
基本原理
标准 C 定义,对象只能通过其类型的指针访问,所有其他访问都是未定义的行为,但有少数例外。特别是,以下代码具有未定义的行为
struct FooObject{
PyObject_HEAD
int data;
};
PyObject *foo(struct FooObject*f){
return (PyObject*)f;
}
int bar(){
struct FooObject *f = malloc(sizeof(struct FooObject));
struct PyObject *o = foo(f);
f->ob_refcnt = 0;
o->ob_refcnt = 1;
return f->ob_refcnt;
}
这里的问题是存储既被视为 struct PyObject
,也被视为 struct FooObject
。
历史上,编译器在处理此代码时没有任何问题。但是,现代编译器将该条款用作优化机会,发现 f->ob_refcnt
和 o->ob_refcnt
不可能引用相同的内存,因此函数应该返回 0,而根本无需在 return 语句中获取 ob_refcnt 的值。对于 GCC,Python 现在使用 -fno-strict-aliasing
来解决此问题;对于其他编译器,它可能只是看到未定义的行为。即使使用 GCC,使用 -fno-strict-aliasing
也可能不必要地使生成的代码性能下降。
规范
标准 C 对其别名规则有一个特定的例外,专门设计用于支持 Python 的情况:结构体类型的变量也可以通过指向其第一个字段的指针进行访问。例如,如果一个结构体以一个 int
开头,则 struct *
也可以转换为 int *
,允许将 int 值写入第一个字段。
对于 Python,PyObject_HEAD
和 PyObject_VAR_HEAD
将被更改,不再列出所有字段,而是列出一个类型为 PyObject
/PyVarObject
的单个字段。
typedef struct _object {
_PyObject_HEAD_EXTRA
Py_ssize_t ob_refcnt;
struct _typeobject *ob_type;
} PyObject;
typedef struct {
PyObject ob_base;
Py_ssize_t ob_size;
} PyVarObject;
#define PyObject_HEAD PyObject ob_base;
#define PyObject_VAR_HEAD PyVarObject ob_base;
定义为固定大小结构的类型将包含 PyObject 作为其第一个字段,对于可变大小的对象则为 PyVarObject。例如:
typedef struct {
PyObject ob_base;
PyObject *start, *stop, *step;
} PySliceObject;
typedef struct {
PyVarObject ob_base;
PyObject **ob_item;
Py_ssize_t allocated;
} PyListObject;
以上 PyObject_HEAD
的定义是规范性的,因此扩展作者可以选择使用宏,或者将其 ob_base
字段显式地放入其结构体中。
按照惯例,基字段应该称为 ob_base。但是,所有对 ob_refcnt 和 ob_type 的访问必须将对象指针强制转换为 PyObject*(除非指针已知具有该类型),并且应该使用相应的访问器宏。为了简化对 ob_type、ob_refcnt 和 ob_size 的访问,宏
#define Py_TYPE(o) (((PyObject*)(o))->ob_type)
#define Py_REFCNT(o) (((PyObject*)(o))->ob_refcnt)
#define Py_SIZE(o) (((PyVarObject*)(o))->ob_size)
被添加。例如,代码块
#define PyList_CheckExact(op) ((op)->ob_type == &PyList_Type)
return func->ob_type->tp_name;
需要更改为
#define PyList_CheckExact(op) (Py_TYPE(op) == &PyList_Type)
return Py_TYPE(func)->tp_name;
对于类型对象的初始化,当前序列
PyObject_HEAD_INIT(NULL)
0, /* ob_size */
变得不正确,必须替换为
PyVarObject_HEAD_INIT(NULL, 0)
与 Python 2.6 的兼容性
为了支持同时使用 Python 2.6 和 Python 3.0 编译的模块,Py_*
宏被添加到 Python 2.6 中。宏 Py_INCREF
和 Py_DECREF
将被更改为将其参数强制转换为 PyObject *
,以便模块作者也可以在为 Python 2.6 设计的模块中显式声明 ob_base
字段。
版权
本文档已置于公共领域。
来源:https://github.com/python/peps/blob/main/peps/pep-3123.rst