8.5 Избегаем рекурсии

Предыдущая  Содержание  Следующая V*D*V

В разделе 8.3 мы пытались реализовать cast() следующим образом:

 

% cast {

    assert(isOf(_self, class));

    return (void *) _self;

}

 

К сожалению, это приводит к бесконечному циклу. Чтобы понять это, давайте проследим вызовы:

 

void * list = new(List, 1);

void * object = new(Object);

 

    addFirst(list, object) {

      cast(List, list) {

        isOf(list, List) {

          classOf(list) {

            cast(Object, list) {

              ifOf(list, Object) {

                classOf(list) {

 

cast() базируется на методе isOf(), который вызывает classOf() и, возможно, super(). Оба эти метода соблюдают наш стандарт кодирования и импортируют свои параметры с использованием %casts, который в свою очередь вызывает cast(), чтобы проверить, представляют ли аргументы собой Object или Class, соответственно. Наша реализация isOf() в разделе 8.3 вызывает classOf() до соблюдения третьей аксиомы гласящей, что любой объект происходит по крайней мере от Object.

Насколько строгой мы хотим сделать проверку типа? Если мы доверяем нашему коду, cast() это пустая операция и может быть заменена тривиальным макросом. Если мы не доверяем нашему коду, параметры и все другие операции разыменовывания должны быть проверены через оборачивание в cast() во всех функциях. Все должны использовать и затем доверять cast() и понятно, что cast() не может использовать другие функции для выполнения проверки.

Итак, что в действительности гарантирует cast(class, object)? По меньшей мере то же, что и isOf(), а именно, что объект не пустой указатель и что описание его класса может быть прослежено до аргумента класса. Если мы берём код isOf() и думаем о безопасности, получаем следующий алгоритм:

 

(_self = self) является объектом

(myClass = self -> class) является объектом

 

if (class != Object)

    class является объектом

    while (myClass != class)

        assert(myClass != Object);

        myClass является описанием класса

        myClass = myClass -> super;

 

return self;

 

Критические части выделены курсивом: какой ненулевой указатель представляет объект, как мы распознаём описание класса? Один способ отличить произвольные указатели от указателей на объекты состоит в том, чтобы позволить каждому объекту начинаться с магического числа, то есть к описанию класса в Object.d добавить компонент .magic:

 

% Class Object {

    unsigned long magic; // магическое число

    const Class @ class; // описание объекта

%

        ...

 

После того как магическое число установлено new() и в инициализации Class и Object, мы проверяем его значение следующим макросом:

 

#define MAGIC 0x0effaced // магическое число для объекта

// вычёркиваем: создать (себя) скромно или застенчиво не привлекая внимания

 

#define isObject(p) \

        ( assert(p), \

        assert(((struct Object *) p) -> magic == MAGIC), p )

 

Строго говоря мы не должны проверять, что myClass — объект, но два дополнительных оператора подтверждения отсутствия ошибок дёшевы. Если мы не проверим, что class — объект, это мог бы быть нулевой указатель, и затем мы могли бы подсунуть объект с нулевым указателем для описания класса после cast().

Дорогая часть — вопрос, является ли myClass описанием класса. У нас не должно быть очень много описаний классов и мы должны знать их все, поэтому можно было бы справиться в таблице действительных указателей. Однако, cast() является одной из самых внутренних функций в нашем коде, поэтому следует сделать её максимально эффективной. Для начала myClass — второй элемент в цепи от объекта до его описания класса и оба были уже проверены на содержание магического числа. Если мы игнорируем проблему случайных указателей, уничтожающих описания классов, разумно предположить, что цепочка .super между описаниями классов остается целой после того, как Class_ctor() её настраивает. Поэтому удаляем проверку из цикла совсем и приходим к следующей реализации для cast():

 

static void catch (int sig) // обработчик сигнала: плохой указатель

{

    assert(sig == 0);       // плохой указатель, не должно произойти

}

 

% cast {

    void (* sigsegv)(int) = signal(SIGSEGV, catch);

#ifdef SIGBUS

    void (* sigbus)(int) = signal(SIGBUS, catch);

#endif

    const struct Object * self = isObject(_self);

    const struct Class * myClass = isObject(self -> class);

 

    if (class != Object) {

        isObject(class);

        while (myClass != class) {

            assert(myClass != Object); // недопустимое приведение типа

            myClass = myClass -> super;

        }

    }

 

#ifdef SIGBUS

    signal(SIGBUS, sigbus);

#endif

    signal(SIGSEGV, sigsegv);

    return (void *) self;

}

 

Обработка сигнала защищает нас от принятия численного значения за указатель. SIGSEGV определён в ANSI-C и указывает на запрещённый доступ к памяти; SIGBUS (или _SIGBUS) является вторым таким сигналом, определённым многими системами.

 

Предыдущая  Содержание  Следующая