PEP 3123 – 표준 C에 맞도록 PyObject_HEAD 만들기
- Author:
- Martin von Löwis <martin at v.loewis.de>
- Status:
- Final
- Type:
- Standards Track
- Created:
- 27-Apr-2007
- Python-Version:
- 3.0
- Post-History:
Table of Contents
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
Python은 현재 PyObject_HEAD를 사용하면서 정의되지 않은 C 동작에 의존합니다. 이 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;
}
여기서 문제는 저장 공간에 구조체 PyObject인 것처럼 접근하는 동시에 구조체 FooObject인 것처럼 접근한다는 점입니다.
역사적으로 컴파일러는 이 코드에서 아무런 문제도 일으키지 않았습니다. 그러나 최신 컴파일러는 이 조항을 최적화 기회로 활용하여 f->ob_refcnt와 o->ob_refcnt가 동일한 메모리를 가리킬 수 없고, 따라서 반환문에서 ob_refcnt 값을 전혀 가져오지 않아도 함수가 0을 반환해야 한다고 판단합니다. 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 필드를 명시적으로 선언할 수 있습니다.
Copyright
This document has been placed in the public domain.