C est un langage de bas niveau, presque un assembleur portable, donc ses structures de données et ses constructions de langage sont proches du métal (les structures de données n'ont pas de coûts cachés - à l'exception des contraintes de remplissage, d'alignement et de taille imposées par le matériel et ABI ). Donc C n'a en effet pas de typage dynamique en natif. Mais si vous en avez besoin, vous pouvez adopter une convention selon laquelle toutes vos valeurs sont des agrégats commençant par des informations de type (par exemple certaines enum
...); utilisez union
-s et (pour les choses de type tableau) un membre de tableau flexible en struct
contenant également la taille du tableau.
(lors de la programmation en C, il est de votre responsabilité de définir, documenter et suivre les conventions utiles - notamment les conditions préalables et postérieures et les invariants; également l'allocation dynamique de mémoire C nécessite des conventions explicites sur qui devrait free
une malloc
zone de mémoire empilée)
Ainsi, pour représenter des valeurs qui sont des nombres entiers ou des chaînes en boîte, ou une sorte de schéma -comme symbole , ou des vecteurs de valeurs, vous utiliserez conceptuellement une union étiquetée (mis en œuvre en tant qu'union de pointeurs) -Toujours à partir par le genre de type -, par exemple:
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.
};
Pour obtenir le type dynamique d'une valeur
enum value_kind_en value_type(value_t v) {
if (v.vptr != NULL) return *(v.vkind);
else return V_NONE;
}
Voici une "distribution dynamique" aux vecteurs:
struct vectvalue_st* dyncast_vector (value_t v) {
if (value_type(v) == V_VECTOR) return v->vvect;
else return NULL;
}
et un "accesseur sûr" à l'intérieur des vecteurs:
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;
}
Vous définissez généralement la plupart des fonctions courtes ci-dessus comme static inline
dans certains fichiers d'en-tête.
BTW, si vous pouvez utiliser le garbage collector de Boehm, vous pouvez alors coder assez facilement dans un style de niveau supérieur (mais dangereux), et plusieurs interpréteurs Scheme sont effectués de cette façon. Un constructeur de vecteur variadique pourrait être
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};
}
et si vous avez trois variables
value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;
vous pouvez construire un vecteur à partir d'eux en utilisant make_vector(3,v1,v2,v3)
Si vous ne voulez pas utiliser le garbage collector de Boehm (ou concevoir le vôtre), vous devez faire très attention à définir les destructeurs et à documenter qui, comment et quand la mémoire doit être free
-d; voir cet exemple. Vous pouvez donc utiliser malloc
(mais ensuite tester contre son échec) au lieu de GC_MALLOC
ci - dessus, mais vous devez soigneusement définir et utiliser une fonction de destructeurvoid destroy_value(value_t)
La force de C est d'être suffisamment bas pour rendre possible le code comme ci-dessus et définir vos propres conventions (particulières à votre logiciel).