Я пишу динамически типизированный язык. В настоящее время мои объекты представлены следующим образом:Представление динамической типизации в C
struct Class { struct Class* class; struct Object* (*get)(struct Object*,struct Object*); };
struct Integer { struct Class* class; int value; };
struct Object { struct Class* class; };
struct String { struct Class* class; size_t length; char* characters; };
Цель состоит в том, что я должен быть в состоянии передать все вокруг как struct Object*
, а затем обнаружить тип объекта, сравнивая атрибут class
. Например, насыпать целое число для использования я бы просто сделать следующее (предположим, что integer
имеет тип struct Class*
):
struct Object* foo = bar();
// increment foo
if(foo->class == integer)
((struct Integer*)foo)->value++;
else
handleTypeError();
Проблема заключается в том, что, насколько я знаю, стандарт C не дает никаких обещаний по поводу как хранятся структуры. На моей платформе это работает. Но на другой платформе struct String
может хранить value
до class
, и когда я обратился к foo->class
в вышеуказанное, я бы действительно получил доступ к foo->value
, что явно плохо. Переносимость - большая цель.
Есть альтернативы такому подходу:
struct Object
{
struct Class* class;
union Value
{
struct Class c;
int i;
struct String s;
} value;
};
Проблема здесь состоит в том, что объединение использует столько же места, как и размер самой большой вещи, которые могут храниться в союзе. Учитывая, что некоторые из моих типов во много раз больше моих других типов, это будет означать, что мои маленькие типы (int
) занимают столько места, сколько мои большие типы (map
), что является неприемлемым компромиссом.
struct Object
{
struct Class* class;
void* value;
};
Это создает уровень перенаправления, который замедляет работу. Скорость здесь - цель.
Конечная альтернатива заключается в том, чтобы пройти около void*
s и самостоятельно управлять внутренними элементами конструкции. Например, для выполнения испытания указанного выше типа:
void* foo = bar();
// increment foo
if(*((struct Class*) foo) == integer)
(*((int*)(foo + sizeof(struct Class*))))++;
else
handleTypeError();
Это дает мне все, что я хочу (портативность, различные размеры для различных типов и т.д.), но имеет по крайней мере две отрицательные стороны:
- Hideous , подверженный ошибкам C. В приведенном выше коде только вычисляется смещение с одним членом; он будет намного хуже с типами, более сложными, чем целые. Я мог бы немного облегчить это с помощью макросов, но это будет болезненно, несмотря ни на что.
- Поскольку нет
struct
, который представляет объект, у меня нет возможности распределения стека (по крайней мере, без реализации моего собственного стека в куче).
В принципе, мой вопрос в том, как я могу получить то, что хочу, не заплатив за это? Есть ли способ быть переносимым, иметь разницу в размере для разных типов, не использовать перенаправление и сохранять мой код довольно?
EDIT: Это лучший ответ, который я когда-либо получал по запросу. Выбор ответа был трудным. SO только позволяет мне выбрать один ответ, поэтому я выбрал тот, который приведет меня к моему решению, но вы все получили upvotes.
Спасибо за эту ссылку; Я многому научился у него. – Imagist
Согласно вашей ссылке, похоже, что это можно сделать с меньшей косвенностью, чем ваш код; в частности: «[I] f a' struct' начинается с 'int',' struct * 'также может быть передан в' int * ', что позволяет записывать значения int в первое поле." Это означает, что в этом случае «struct Integer *» может быть передано в 'struct Class **', что означает, что мне не нужно менять свои объявления; Мне нужно только быть уверенным, чтобы ссылаться на класс через указатели (так я все равно передаю их). – Imagist