Intervengo brevemente per ricordare un paio di punti di riferimento.
1) Anche standard relativamente vecchi, sebbene scarsamente diffusi per motivi che non è possibile richiamare qui, risolvono definitivamente ogni dubbio sull'uso delle utilissime typedef "parlanti" per i tipi numerici, con denominazioni intuitivamente costruite sui campi <SIGNEDNESS><TYPE><SIZE>_t. La normativa
MISRA/C 2004 (minimo mutuabile per l'uso robusto del C in mercati anche blandamente normati, automotive in primis) contiene ad esempio la seguente regola:
6.3 (advisory): typedefs that indicate size and signedness should be used in place of the basic types.
Tale regola è riscontrabile universalmente nelle norme di stile almeno a partire dal ventennio precedente, a fortiori relative a cross-compiler embedded, ed è stata recepita già all'epoca dello standard C'99 che - ai paragrafi 7.8 e soprattutto 7.18 - definisce due header, portabili ed ampiamente preferenziali per simili definizioni. Naturalmente nulla cambia passando a C'11 e relativa Misra/C 2012.
Vengono dunque, in ultima analisi, da più parti (standard del linguaggio incluso) fortemente raccomandate definizioni come int16_t o uint64_t, contenute nello header minimale stdint.h e nella sua estensione (che lo include) inttypes.h. A margine sottolineo che Visual Studio è stato, in ordine di tempo, uno degli ultimi ambienti di sviluppo a larga diffusione per PC ad adeguarsi (solo dopo il 2008) alle raccomandazioni dello standard, per motivi storici: la presenza in parallelo di una denominazione arbitraria solo leggermente differente, fin dal progetto delle prime API di Windows. Tuttavia, nulla osta all'uso della sottosezione C della Boost Library, sempre più in odore di santità, che offre (oltre ad una cornucopia di altri benefits) anche tali header.
2) Non si dimentichi un ulteriore uso fondamentale, seppure non quotidiano nel codice mainstream, dei puntatori a funzione: il
dispatching, tipicamente operato su un array (eventualmente dinamico) di puntatori a funzione. Esso in genere assume la forma di una LUT implementata tramite un array di puntatori a funzione, e nella sostanza altro non è che l'epifenomeno, la versione edulcorata del linguaggio C, delle jump tables supportate in modo più o meno esplicito da un'infinità numerabile di CPU e piattaforme di elaborazione del real world, fin dagli albori.
3) Quanto all'uso di typedef per la definzione del prototipo di una callback e più in generale di un puntatore a funzione, esso è larghissimamente diffuso in numerose librerie ed implementazioni commerciali, e suffragato da numerose norme di stile. Restando al caso banale, ecco alcuni esempi di pessimo codice del real world, ridotti ad usum delphini per il buon Gila, che riassumono le più diffuse possibili alternative all'uso di typedef: tutte caratterizzate da inutile complessificazione sintattica o nella definizione della UDF di confronto, o nella chiamata della medesima da qsort(), lsearch(), bsearch() e quant'altro. Che succede in simili modelli errati, a livello sintattico, quando si ha a che fare con un array multidimensionale di puntatori a strutture, e magari gli elementi da confrontare sono a loro volta contenuti in una union subordinata?
Codice: Seleziona tutto
#define F1 ((struct file_entry **) (f1))
#define F2 ((struct file_entry **) (f2))
int cmp_files(const void *f1, const void *f2)
...
#undef F2
#undef F1
/************************************************/
int cmp( const void *a, const void *b)
{
const char *_a = *(const char **)a;
const char *_b = *(const char **)b;
...
}
/************************************************/
int comp(const void *a, const void *b)
{
unsigned int aux1, aux2;
aux1 = ((struct MyStruct_t**)a)->segment ;
aux2 = ((struct MyStruct_t**)b)->segment ;
return (aux1 < aux2 ? -1 : (aux1 > aux2));
}
/************************************************/
int Cmp(MyPtr_t *a, MyPtr_t *b)
{
return strcmp(a->name, b->name);
}
void MyFun(char *foo)
{
...
p = (MyPtr_t *) bsearch(...
(int(*)(const void*, const void*))Cmp);
...
}
/************************************************/
Nel caso specifico, l'uso di una typedef per il cast (peraltro univoca, nel caso banale delle funzioni della libreria standard e di tutte quelle - inumerevoli... - modellate sulla loro falsariga) risulta altamente preferibile a qualsiasi altra soluzione analoga, secondo un banale ma basilare criterio di economia e leggibilità - accolto in modo pressoché universale da guide di stile e manuali di engineering, a prescindere dal livello di normazione del settore specifico nel quale è applicato il software.
Codice: Seleziona tutto
typedef int (*fptr)(const void*, const void*);
...
qsort(Array, Asize, sizeof(MyStruct_t), (fptr)Cmp);
A proposito di normazione, un altro simpatico paradosso: i primi e massimi beneficiari delle norme di codifica applicate nei settori critici, a partire dalla banale MISRA/C, sarebbero per contro... i programmatori operanti nel mainstream. E' lì che si vedono i peggiori orrori, che spesso generano anche errori. Nei settori ad elevata normazione, infatti, la qualità media del codice (intesa nel senso più strettamente quantitativo, secondo i canoni e le metriche asseverate nel mondo dell'engineering e del refactoring) era già storicamente molto elevata anche prima della standardizzazione di
talune norme, o di loro revisioni profonde. Tali norme, ricordo, assieme all'uso sistematico (imposto per legge o almeno per contratto) di
metodi formali rigorosi per specifica e verifica come Z, CASL, VDM, CSP, CTL* e derivate, fino a OCL e all'interpretazione astratta... non provengono dai bui recessi della preistoria del software, ma hanno piuttosto sistematizzato e raccolto abitudini invalse e bodies of knowledge già in vigore da decenni nelle organizzazioni di maggiori dimensioni, e comunque di maggior qualità.
Incidenti come Therac-25 e Ariane V, eventi fortunatamente più unici che rari i quali hanno evidenziato in modo macroscopico alcune gravi carenze normative e procedurali nei settori applicativi più critici, si collocano grossolanamente nell'ultimo quarto di secolo... pauca intelligentibus.
