C è un linguaggio di basso livello, quasi un assemblatore portatile, quindi le sue strutture dati e i suoi costrutti del linguaggio sono vicini al metallo (le strutture dati non hanno costi nascosti - eccetto vincoli di padding, allineamento e dimensione imposti dall'hardware e ABI ). C infatti non ha una digitazione dinamica nativamente. Ma se ne hai bisogno, puoi adottare una convenzione che tutti i tuoi valori siano aggregati iniziando con alcune informazioni sul tipo (ad esempio alcuni enum
...); usa union
-s e (per cose simili ad array) membro flessibile dell'array in struct
contenente anche le dimensioni di l'array.
(quando si programma in C, è responsabilità dell'utente definire, documentare e seguire le convenzioni utili, in particolare pre- e post-condizioni e invarianti, anche L'allocazione di memoria dinamica C richiede convenzioni esplicative su chi dovrebbe free
di heap- malloc
ated memory zone)
Quindi, per rappresentare valori che sono interi in scatola, o stringhe, o qualche tipo di Schema - come simbolo , o vettori di valori, utilizzerai concettualmente un tag tagged (implementato come unione di puntatori), sempre a partire dal tipo, ad 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 certo 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 "accesso 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 un file di intestazione.
BTW, se puoi usare il garbage collector di Boehm allora sei in grado di codificare abbastanza facilmente in qualche livello superiore (ma stile non sicuro) e diversi interpreti Scheme sono fatti in questo modo. Un costruttore di vettori variadico potrebbe essere
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 creare un vettore da loro usando make_vector(3,v1,v2,v3)
Se non vuoi usare il garbage collector di Boehm (o creane uno tuo) devi 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 testare il suo fallimento) invece di GC_MALLOC
sopra, ma devi definire con cura e usare una certa funzione distruttore void destroy_value(value_t)
Il punto di forza di C deve essere di livello abbastanza basso da rendere possibile il codice come sopra possibile e definire le tue convenzioni (in particolare per il tuo software).