PEP 3123 – PyObject_HEAD를 표준 C에 맞게 만들기
- 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가 같은 메모리를 가리킬 수 없으므로 함수가 return 문에서 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.