C jest językiem niskiego poziomu, prawie przenośnym asemblerem, więc jego struktury danych i konstrukcje językowe są zbliżone do metalu (struktury danych nie mają ukrytych kosztów - oprócz wypełnienia, wyrównania i ograniczeń wielkości narzuconych przez sprzęt i ABI ). Tak więc C rzeczywiście nie ma natywnego pisania dynamicznego. Ale jeśli potrzebujesz, możesz przyjąć konwencję, że wszystkie twoje wartości są agregatami, zaczynając od pewnych informacji o typie (np. Niektóre enum
...); Zastosowanie union
-s i (w przypadku, tablicowej przedmiotów) elastycznego członu matrycy na struct
zawierającego również rozmiar tablicy.
(programując w C, Twoim obowiązkiem jest zdefiniowanie, udokumentowanie i przestrzeganie użytecznych konwencji - w szczególności warunków wstępnych i dodatkowych oraz niezmienników; również dynamiczna alokacja pamięci C wymaga wyjaśnienia konwencji, kto powinien mieć strefę pamięci o free
dużym malloc
zasięgu)
Tak więc, aby przedstawić wartości, które są liczbami całkowitymi w ramkach, ciągami znaków lub jakimś symbolem podobnym do schematu lub wektorem wartości, koncepcyjnie użyjesz oznaczonego połączenia (zaimplementowanego jako połączenie wskaźników) - zawsze zaczynając od rodzaju -, np .:
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.
};
Aby uzyskać dynamiczny typ jakiejś wartości
enum value_kind_en value_type(value_t v) {
if (v.vptr != NULL) return *(v.vkind);
else return V_NONE;
}
Oto „dynamiczny rzut” na wektory:
struct vectvalue_st* dyncast_vector (value_t v) {
if (value_type(v) == V_VECTOR) return v->vvect;
else return NULL;
}
oraz „bezpieczny akcesorium” wewnątrz wektorów:
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;
}
Zazwyczaj zdefiniujesz większość krótkich funkcji powyżej, jak static inline
w niektórych plikach nagłówkowych.
BTW, jeśli możesz użyć zbieracza śmieci Boehm'a, możesz dość łatwo kodować w stylu wyższego poziomu (ale niebezpiecznym), a kilka interpretatorów schematów jest wykonywanych w ten sposób. Wariacyjny konstruktor wektorowy może być
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};
}
a jeśli masz trzy zmienne
value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;
możesz zbudować z nich wektor za pomocą make_vector(3,v1,v2,v3)
Jeśli nie chcesz używać zbieracza śmieci Boehm'a (lub zaprojektować swój własny), powinieneś bardzo ostrożnie definiować destruktory i dokumentować, kto, jak i kiedy pamięć powinna być free
-d; zobacz ten przykład. Możesz więc użyć malloc
(ale następnie przetestować pod GC_MALLOC
kątem jego awarii) zamiast powyższego, ale musisz dokładnie zdefiniować i użyć funkcji destruktoravoid destroy_value(value_t)
Siła C ma być na niskim poziomie, aby umożliwić kod jak wyżej i zdefiniować własne konwencje (szczególnie dla twojego oprogramowania).