Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

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_refcnto->ob_refcnt가 동일한 메모리를 가리킬 수 없고, 따라서 반환문에서 ob_refcnt 값을 전혀 가져오지 않아도 함수가 0을 반환해야 한다고 판단합니다. GCC의 경우 Python은 이제 이 문제를 우회하기 위해 -fno-strict-aliasing을 사용합니다. 다른 컴파일러에서는 단순히 정의되지 않은 동작으로 처리할 수 있습니다. GCC에서도 -fno-strict-aliasing을 사용하면 생성된 코드가 불필요하게 성능 저하를 일으킬 수 있습니다.

사양

표준 C에는 Python의 경우를 지원하도록 정확히 설계된 별칭 규칙의 특정 예외가 하나 있습니다. 구조체 타입의 값은 첫 번째 필드를 가리키는 포인터를 통해서도 접근할 수 있습니다. 예를 들어 구조체가 int로 시작하는 경우 struct *int *로 캐스팅하여 첫 번째 필드에 int 값을 쓸 수 있습니다.

Python에서는 PyObject_HEADPyObject_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_INCREFPy_DECREF매크로는 인자를 PyObject *로 캐스팅하도록 변경되므로, 모듈 작성자는 Python 2.6용으로 설계된 모듈에서도 ob_base 필드를 명시적으로 선언할 수 있습니다.