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

Python 개선 제안 한국어 번역

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 필드를 명시적으로 선언할 수 있습니다.