Da qualche tempo ho ripreso a programmare in puro C per dei miei progetti personali, e quando dico puro intendo utilizzando essenzialmente la libreria standard come unico ausilio e primitive di sistema ove possibile.Non amo appoggiarmi ad API terze parti, per due (credo) ottime ragioni: esiste un tempo di apprendimento della loro logica non sempre nullo, e quasi sempre si adotta solo una minima percentuale delle funzioni date: che spreco di bit!
Poi una ragione diciamo di orgoglio, è che mi piace conoscere e trovare tecniche e soluzioni a problemi per mio conto, quasi sempre cercando (è un istinto della mia generazione) di sprecare pochi bit e pochi cicli CPU! Che noia, eh?!
Certo dopo qualche anno di C++, Java e Objective-C (in ordine cronologico), la mancanza di alcuni costrutti o strumenti si è fatta sentire: ci si vizia facilmente! Però si sopravvive.
In particolare un mio programma, instaurando un dialogo simbolico con un suo client aveva la necessità di cercare nelle sue strutture di memoria mediate il simbolo rappresentante un attributo di struttura: un classico esempio di reflection.
Abituato nell’ultimo periodo al paradigma Key-Value-Coding, la sua assenza mi pesava. Un approccio classico switch-case comportava la codifica numerica dei simboli (siamo in C, non PHP!). Una sequenza di if-else-if con strcmp “a manciate” non mi sembrava elegante: quindi?
Quindi perché non trovare un modo di implementare un abbozzo di reflection istruendo il programma sulla sua struttura?
Occorreva una tabella di meta-dati: nome del membro della struttura, spiazzamento da base di memoria della struttura e una funzione associata che applicasse un qualche algoritmo (a me serviva una comparazione tra i valori della struttura e un parametro di riferimento) tenendo conto del tipo dato.
Soluzione? La vecchia e cara aritmetica degli indirizzi!
Certo, come tutti sappiamo le cose così si complicano e diventano maledettamente illeggibili, ma per fortuna l’ambiente di sviluppo in Linux (a già dimenticavo! stavo implementando per questo specifico sistema) mi è venuto incontro con una macro di pre-processing che ha semplificato e reso leggibile il tutto.
Si tratta di “offsetof” definita in stddef.h e usata in ambito sviluppo kernel (con alcune varianti possibili): ovviamente troverete il file in /usr/src/linux-header-$(uname -r)/include/linux che quindi divrete avere.
Dunque con una struttura formata da
- const char *
- size_t
- int (*)(void *, void *)
ho potuto così creare una lookup table con cui ispezionare in riflessione la memoria del programma (relativamente a una certa struttura dati).
Ovviamente il size_t popolato dalla macro “offsetof” , mente la funzione nel mio caso doveva essere capace di comparare due argomenti del tipo pari all’attributo considerato, di cui appunto un argomento sarebbe stato proprio l’attributo.
Per accedere al dato nella struttura una volta riconosciuto il nome, la funzione di lookup non doveva fare altro che convertire in char * l’indirizzo base della struttura stessa e sommarci l’offset dato nella riga della lookup table che aveva consentito il riconoscimento, ottenendo un puntatore che convertito in void * e passato assieme al parametro di riferimento alla funzione associata che avrebbe provveduto (conoscendo il tipi originale) ad eseguire quanto voluto in modo coerente.
Basta ?
Forse no in termini assoluti, ma per la mia implementazione avere una struttura con 2 attributi o 50 è stato solo un gioco di riempimento della (o delle) lookup table, dopodiché lo stesso codice ha fatto quanto previsto in tutte le circostanze e in tutti gli aggiornamenti della struttura dati (e lookup table).
Alla prossima!