C è un linguaggio di basso livello, quasi un assemblatore portatile, quindi le sue strutture di dati e costrutti di linguaggio sono vicini al metallo (le strutture di dati non hanno costi nascosti - eccetto i limiti di imbottitura, allineamento e dimensione imposti dall'hardware e dall'ABI ). Quindi C in effetti non ha una digitazione dinamica nativa. Ma se ne hai bisogno, potresti adottare una convenzione secondo cui tutti i tuoi valori sono aggregati a partire da alcune informazioni sul tipo (ad es. Alcuni enum
...); use union
-s e (per cose simili ad array) membro flessibile dell'array nel struct
contenere anche le dimensioni dell'array.
(durante la programmazione in C, è responsabilità dell'utente definire, documentare e seguire utili convenzioni - in particolare pre e post-condizioni e invarianti; anche l'allocazione dinamica della memoria C richiede esplicazioni su chi dovrebbe avere free
una malloc
zona di memoria ammassata)
Quindi, per rappresentare valori che sono numeri interi inscatolati, o stringhe, o qualche tipo di simbolo simile allo schema , o vettori di valori, userete concettualmente un'unione taggata (implementata come unione di puntatori) che inizia sempre dal tipo di tipo -, per esempio:
enum value_kind_en {V_NONE, V_INT, V_STRING, V_SYMBOL, V_VECTOR};
union value_en { // this union takes a word in memory
const void* vptr; // generic pointer, e.g. to free it
enum value_kind_en* vkind; // the value of *vkind decides which member to use
struct intvalue_st* vint;
struct strvalue_st* vstr;
struct symbvalue_st* vsymb;
struct vectvalue_st* vvect;
};
typedef union value_en value_t;
#define NULL_VALUE ((value_t){NULL})
struct intvalue_st {
enum value_kind_en kind; // always V_INT for intvalue_st
int num;
};
struct strvalue_st {
enum value_kind_en kind; // always V_STRING for strvalue_st
const char*str;
};
struct symbvalue_st {
enum value_kind_en kind; // V_SYMBOL
struct strvalue_st* symbname;
value_t symbvalue;
};
struct vectvalue_st {
enum value_kind_en kind; // V_VECTOR;
unsigned veclength;
value_t veccomp[]; // flexible array of veclength components.
};
Per ottenere il tipo dinamico di un valore
enum value_kind_en value_type(value_t v) {
if (v.vptr != NULL) return *(v.vkind);
else return V_NONE;
}
Ecco un "cast dinamico" per i vettori:
struct vectvalue_st* dyncast_vector (value_t v) {
if (value_type(v) == V_VECTOR) return v->vvect;
else return NULL;
}
e un "accessore sicuro" all'interno dei vettori:
value_t vector_nth(value_t v, unsigned rk) {
struct vectvalue_st* vecp = dyncast_vector(v);
if (vecp && rk < vecp->veclength) return vecp->veccomp[rk];
else return NULL_VALUE;
}
In genere definirai la maggior parte delle funzioni brevi sopra come static inline
in alcuni file di intestazione.
A proposito, se puoi usare il garbage collector di Boehm, allora sei in grado di programmare abbastanza facilmente in uno stile di livello superiore (ma non sicuro), e diversi interpreti Scheme sono fatti in quel modo. Potrebbe essere un costruttore di vettore variadico
value_t make_vector(unsigned size, ... /*value_t arguments*/) {
struct vectvalue_st* vec = GC_MALLOC(sizeof(*vec)+size*sizeof(value));
vec->kind = V_VECTOR;
va_args args;
va_start (args, size);
for (unsigned ix=0; ix<size; ix++)
vec->veccomp[ix] = va_arg(args,value_t);
va_end (args);
return (value_t){vec};
}
e se hai tre variabili
value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;
potresti costruire un vettore da loro usando make_vector(3,v1,v2,v3)
Se non vuoi usare il garbage collector di Boehm (o progettarne uno tuo), dovresti stare molto attento a definire i distruttori e documentare chi, come e quando la memoria dovrebbe essere free
-d; vedi questo esempio. Quindi potresti usare malloc
(ma poi provare contro il suo fallimento) invece di GC_MALLOC
sopra ma devi definire con attenzione e usare alcune funzioni di distruttorevoid destroy_value(value_t)
Il punto di forza di C è di essere abbastanza basso livello da rendere possibile il codice come sopra e definire le proprie convenzioni (particolari per il proprio software).