Определение типа void* (и других переменных) в C

avatar
oxibts
8 августа 2021 в 18:51
102
2
1

Я пишу простой интерпретатор Лиспа, и у меня определены следующие struct и enum:

typedef enum {
    STRING,
    INTEGER,
    FLOAT,
    FUNCTION,
    VARIABLE,
    SYMBOL,
    NIL
} atom_e;

typedef union {
    char* string;
    int integer;
    float decimal;
} data_t;

typedef struct {
    data_t data;
    atom_e type;
    void* next;
} atom_t;

typedef struct {
    void* head;
} list_t;

atom_e относится к поддерживаемым типам атомов для моего Лиспа. data_t используется для хранения каждого атома. Он используется только в atom_t. list_t используется для сбора atom_ts. Он имеет head, который указывает либо на atom_t или list_t (в случае вложенных списков) atom_t — это структура атома. Он состоит из атома (хранится в data) , описание его типа (type) и void*. Этот пустой указатель может указывать на другой atom_t или list_t.

.

Я разработал его таким образом, чтобы при написании Лиспа было более понятно, когда list является вложенным и каков его родительский/дочерний список. Разбор s-exp всегда даст вы используете list_t*, потому что весь допустимый код Lisp начинается с открывающей скобки, знака начала списка.

Сейчас я нахожусь на стадии "eval" Лиспа, и функция eval работает следующим образом:

Если nextatom_t или list_t) указывает на atom_t, eval этот атом и следующие за ним (т.е. (set x 10)). Если next указывает на list_t, оцените весь этот список, атом за атомом. (т.е. "(set x (* 5 2))")

Я разработал его, как описано выше, предполагая, что C предоставляет встроенную функцию type() или isinstance(), которую я мог бы использовать в функции eval — насколько мне известно, это не так. Как бы я имитировал функцию Python isinstance() в C, чтобы я мог сравнивать типы указателей void? Я хотел бы придерживаться ANSI C, где это возможно, последней версии C99, чтобы обеспечить максимальную переносимость.

Источник

Ответы (2)

avatar
Kaz
8 августа 2021 в 20:40
1

Вы просто должны сделать это:

typedef enum {
    STRING,
    INTEGER,
    FLOAT,
    FUNCTION,
    VARIABLE,
    SYMBOL,
    LIST,  // Add this!
    NIL
} atom_e;

Затем в data_t добавьте следующее:

typedef union {
    char* string;
    int integer;
    float decimal;
    void *head; // add this, specific to LIST type
} data_t;

Таким образом, список представляет собой просто еще один atom_t. Конечно, тогда этот тип должен называться не atom_t, а value_t или как-то так: он представляет любое значение: как атомы, так и список.

atom_e должен быть просто type_e: он перечисляет все типы, а не только атомы.

Тогда ваша функция eval должна сделать

switch (value->type) {
case STRING:
case INTEGER:
case FLOAT:
case FUNCTION:
case NIL:
  return value; // these are self-evaluating
case VARIABLE:
  // ?
case SYMBOL:
  // ?
case LIST:
  // implement compound form evaluation here
}

Итак, вот ваш instanceof; вы просто посмотрите на код типа, который у вас уже есть.

Обратите внимание, что в реальном Лиспе нет "переменных" и "символов" как отдельных типов. Переменная — это связь между символом и значением, а не вид объекта. Когда оценивается символ, он понимается как переменная: оценщик ищет в текущей среде привязку, названную этим символом.

(Среда Лиспа может быть набором объектов привязки, которые имеют внутренний тип VARIABLE, для косвенного обращения к переменным во время выполнения, но это расширение базовой семантики Лиспа.)

avatar
Daniel Kleinstein
8 августа 2021 в 18:55
1

В C нет отражения — это означает, что вы не можете рассуждать о типах переменных языка во время выполнения так же, как в Python.

То, что вы можете сделать, это сохранить тип элемента внутри его структуры - например,

typedef enum {
    DATA,
    ATOM
} element_type;

и

typedef union {
    element_type type;
    ...
} data_t;

typedef struct {
    element_type type;
    ...
} atom_t;

и затем проверьте это следующим образом:

void* element;
element_type type = *((element_type*)element);
if (type == DATA) {
    data_t* data = (data_t*)element;
    ....
} else if (type == ATOM) {
    atom_t* atom = (atom_t*)element;
    ...
}

В качестве альтернативы вы можете иметь одну структуру, которая инкапсулирует как тип, так и void*:

struct {
    element_type type;
    void* element; // points to data_t* or atom_t* depending on the type
}

Дополнительный забавный факт — это распространенный шаблон в кодовых базах C, один из самых известных примеров которого можно найти в самом Python. Все объекты в CPython являются C PyObjects за кулисами:

typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    PyTypeObject *ob_type;
} PyObject;

и CPython просматривает поле ob_type, чтобы узнать, к какому типу относится общий PyObject.