Инициализация структуры C выполняется " рекурсивно"


Я недавно столкнулся с примером инициализации C-структуры, который был объяснен этим вопросом.

Чего я не понимаю, так это того, что кажется рекурсивным определением; это из MicroPython/objtype.c

typedef struct _mp_obj_type_t mp_obj_type_t;

const mp_obj_type_t mp_type_type = { // <-- SAME SYMBOL DEFINED HERE . . .
    { &mp_type_type }, // <-- IS USED HERE
    .name = MP_QSTR_type,
    .print = type_print,
    .make_new = type_make_new,
    .call = type_call,
    .unary_op = mp_generic_unary_op,
    .attr = type_attr,
};

Поля, указанные в .<some_field>, я понимаю (см. ссылку в первом предложении).

А как же" рекурсивная " инициализация?

Есть и другие экземпляры в Микропитонном коде , которые используют это синтаксис:

const mp_obj_type_t pyb_led_type = {
    { &mp_type_type }, <-- SAME SYMBOL AS ABOVE
    .name = MP_QSTR_LED,
    .print = led_obj_print,
    .make_new = led_obj_make_new,
    .locals_dict = (mp_obj_t)&led_locals_dict,
};

Это имеет больше смысла: структура pyb_led_type инициализируется со значениями по умолчанию, установленными в структуре mp_type_type, и некоторые поля изменяются по умолчанию.

А как же const mp_obj_type_t mp_type_type?

структура mp_type_type по умолчанию имеет значения . . . struct mp_type_type . . . ???

Предварительно обработанные выходные данные идентичны .c.

Что здесь происходит?

Вот несколько полей структуры

struct _mp_obj_type_t {
    // A type is an object so must start with this entry, which points to mp_type_type.
    mp_obj_base_t base;

    // The name of this type.
    qstr name;

    // Corresponds to __repr__ and __str__ special methods.
    mp_print_fun_t print;

    ...
};
struct _mp_obj_base_t {
    const mp_obj_type_t *type MICROPY_OBJ_BASE_ALIGNMENT;
};
typedef struct _mp_obj_base_t mp_obj_base_t;
3 5
gcc

3 ответа:

Самореферентные структуры в C

Код Микропифона, который вы цитируете, просто создает самореферентный экземпляр структуры, что совершенно нормально в C. рассмотрим этот пример, который в значительной степени лишен некоторых ненужных частей:

#include "stdio.h"

// const base
struct A {
    const struct A* base;
};

// non-const base
struct B {
    const struct B* base;
};

const struct A a = { &a };

const struct B b = { &b };

int main() {
    printf("%p %p\n", (void*) &a, (void*)a.base);
    printf("%p %p\n", (void*) &b, (void*)b.base);
    return 0;
}

Специфическое использование в создании экземпляров mp_obj_type_t структур в коде Микропифона

Проект MicroPython использует указатель base для реализации (множественного) наследования в Python. Ссылка base является указатель на другой тип, который является базовым типом ("Родительский" в иерархии типов), глядя на определение этой структуры :
struct _mp_obj_type_t {
    // A type is an object so must start with this entry, which points to mp_type_type.
    mp_obj_base_t base;
   // .. many more fields
}

Случай, который вы упоминаете, - это mp_type_type переменная const, кажется, базовый тип всех типов, таким образом, само-ссылка, но это имеет гораздо больше смысла, когда вы смотрите на типы, которые "наследуют" от mp_type_type, например pyb_led_type:

const mp_obj_type_t pyb_led_type = {
    { &mp_type_type },
    .name = MP_QSTR_LED,
    .print = led_obj_print,
    .make_new = led_obj_make_new,
    .locals_dict = (mp_obj_t)&led_locals_dict, };

Объекты в этой системе (Микропитон) начинаются с указателя на их тип. Типы микропитонов представлены в виде объектов типа mp_type_type. Mp_type_type - это сам тип, поэтому его поле типа указывает на него самого. (Концепция, пожалуй, лучше всего иллюстрируется в литературе по Smalltalk. cf: http://pharo.gforge.inria.fr/PBE1/PBE1ch14.html возможно.)

const mp_obj_type_t mp_type_type = { // <-- SAME SYMBOL DEFINED HERE . . .
    { &mp_type_type }, // <-- IS USED HERE
    .name = MP_QSTR_type,

...

};

Поля, указанные пользователем . Я понимаю (см. ссылку в первом разделе предложение).

А как же "рекурсивная" инициализация?
Там нет ничего, что можно было бы обоснованно охарактеризовать как рекурсивную инициализацию. Это было бы инициализацией объекта или одного из его членов с значением этого объекта, что C действительно запрещает, но такая вещь не является очевидной.

В вашем примере показан член объект инициализируется адресом этого объекта (именно это вычисляет оператор &). Адрес объекта никоим образом не зависит от его значения, и его можно безопасно вычислить до инициализации объекта. Такая практика даже не является чем-то необычным в общем смысле, хотя это необычно специально инициализировать часть объекта указателем на этот объект.