C é uma linguagem de baixo nível, quase um montador portátil, portanto suas estruturas de dados e construções de linguagem estão próximas do metal (as estruturas de dados não têm custos ocultos - exceto restrições de preenchimento, alinhamento e tamanho impostas pelo hardware e pela ABI ). Portanto, C realmente não possui digitação dinâmica nativamente. Mas se você precisar, você pode adotar uma convenção de que todos os seus valores são agregados, começando com algumas informações de tipo (por exemplo, algumas enum
...); utilização union
-s e (para a matriz como as coisas) membro da matriz flexível em struct
contendo também o tamanho da matriz.
(ao programar em C, é de sua responsabilidade definir, documentar e seguir convenções úteis - principalmente pré e pós-condições e invariantes; também a alocação dinâmica de memória C requer convenções free
explicativas sobre quem deve alguma malloc
zona de memória acumulada)
Portanto, para representar valores que são inteiros ou cadeias de caracteres em caixa, ou algum tipo de símbolo semelhante ao esquema ou vetores de valores, você usará conceitualmente uma união marcada (implementada como uma união de ponteiros) sempre começando pelo tipo de tipo -, por exemplo:
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.
};
Para obter o tipo dinâmico de algum valor
enum value_kind_en value_type(value_t v) {
if (v.vptr != NULL) return *(v.vkind);
else return V_NONE;
}
Aqui está um "elenco dinâmico" para vetores:
struct vectvalue_st* dyncast_vector (value_t v) {
if (value_type(v) == V_VECTOR) return v->vvect;
else return NULL;
}
e um "acessador seguro" dentro de vetores:
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;
}
Você geralmente define a maioria das funções curtas acima, como static inline
em algum arquivo de cabeçalho.
BTW, se você pode usar o coletor de lixo da Boehm, poderá codificar com bastante facilidade em um estilo de nível superior (mas não seguro), e vários intérpretes do Scheme são feitos dessa maneira. Um construtor de vetor variável pode ser
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 você tiver três variáveis
value_t v1 = somevalue(), v2 = otherval(), v3 = NULL_VALUE;
você pode construir um vetor deles usando make_vector(3,v1,v2,v3)
Se você não quiser usar o coletor de lixo de Boehm (ou criar o seu próprio), tenha muito cuidado ao definir destruidores e documentar quem, como e quando a memória deve ser free
-d; veja este exemplo. Então você pode usar malloc
(mas testar contra a falha) em vez de GC_MALLOC
acima, mas precisa definir e usar com cuidado alguma função destruidoravoid destroy_value(value_t)
A força de C é ter um nível baixo o suficiente para tornar possível o código acima e definir suas próprias convenções (específicas ao seu software).