Escribo un lenguaje de tipado dinámico. Actualmente, mis objetos están representados de esta manera:Representación de tipado dinámico en 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; };
El objetivo es que debería ser capaz de pasar todo a su alrededor como un struct Object*
y luego descubrir el tipo del objeto comparando el atributo class
. Por ejemplo, para emitir un número entero para su uso yo simplemente hacer lo siguiente (a suponer que integer
es de tipo struct Class*
):
struct Object* foo = bar();
// increment foo
if(foo->class == integer)
((struct Integer*)foo)->value++;
else
handleTypeError();
El problema es que, por lo que yo sé, el estándar de C no hace promesas sobre cómo se almacenan las estructuras En mi plataforma, esto funciona. Pero en otra plataforma struct String
podría almacenar value
antes de class
y cuando accedí al foo->class
en el cuadro anterior, estaría accediendo a foo->value
, lo cual es obviamente malo. La portabilidad es un gran objetivo aquí.
Hay alternativas a este enfoque:
struct Object
{
struct Class* class;
union Value
{
struct Class c;
int i;
struct String s;
} value;
};
El problema aquí es que la unión utiliza tanto espacio como el tamaño de la cosa más grande que se puede almacenar en la unión. Dado que algunos de mis tipos son muchas veces más grandes que mis otros tipos, esto significaría que mis tipos pequeños (int
) ocuparían tanto espacio como mis tipos grandes (map
), lo cual es una compensación inaceptable.
struct Object
{
struct Class* class;
void* value;
};
Esto crea un nivel de redirección que ralentizará las cosas. La velocidad es un objetivo aquí.
La última alternativa es pasar alrededor de void*
sy gestionar las partes internas de la estructura yo mismo. Por ejemplo, para poner en práctica el ensayo del tipo mencionado anteriormente:
void* foo = bar();
// increment foo
if(*((struct Class*) foo) == integer)
(*((int*)(foo + sizeof(struct Class*))))++;
else
handleTypeError();
Esto me da todo lo que quiero (portabilidad, diferentes tamaños para diferentes tipos, etc.) pero tiene al menos dos inconvenientes:
- horrible , propenso a errores C. El código anterior solo calcula una compensación de un solo miembro; empeorará con tipos más complejos que enteros. Tal vez pueda aliviar esto un poco usando macros, pero esto será doloroso sin importar qué.
- Dado que no hay
struct
que represente el objeto, no tengo la opción de asignaciones de pila (al menos sin implementar mi propia pila en el montón).
Básicamente, mi pregunta es, ¿cómo puedo obtener lo que quiero sin tener que pagar por ello? ¿Hay alguna manera de ser portátil, tener una variación en el tamaño para diferentes tipos, no utilizar la redirección, y mantener mi código bonito?
EDITAR: Esta es la mejor respuesta que he recibido para una pregunta ASÍ. Elegir una respuesta fue difícil. SO solo me permite elegir una respuesta, así que elegí la que me condujo a mi solución, pero todos ustedes recibieron votos por contestar.
Gracias por ese enlace; Aprendí mucho de eso. – Imagist
De acuerdo con su enlace, parece que esto se puede hacer con menos indirección que su código; específicamente: "[I] f a 'struct' comienza con 'int', la 'struct *' también puede ser convertida a 'int *', lo que permite escribir valores int en el primer campo. " Esto significa que en este caso la 'struct Integer *' se puede convertir en una 'struct Class ** ', lo que significa que no tengo que cambiar mis declaraciones; Solo necesito asegurarme de hacer referencia a la clase a través de punteros (así es como los estoy pasando de todos modos). – Imagist