Ultimamente sto programmando un po’ di più in C per un progetto universitario di cui parlerò in futuro.
Una funzionalità che sto sfruttando per assicurarmi che siano sempre verificate le precondizioni all’inizio di una funzione sono le assert (o, se preferite, le asserzioni).
Che cosa sono
Le asserzioni sono dei semplici predicati che indicano una condizione che deve essere verificata in un certo punto del programma. Quindi qualsiasi test che ritorna un valore booleano può essere utilizzato come argomento per una asserzione.
Esempio
Ad esempio, se volessi che una funzione accetti solo numeri positivi potrei scrivere:
/* @file assert.c */
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int func(int x)
{
assert(x > 0);
return x;
}
int main(void)
{
int i = 0;
printf("Numero: %d\n", func(i));
return 0;
}
Eseguendo questo programma otterrei:
[justb@dellill C]$ gcc -Wall assert.c -o assert.out && ./assert.out assert.out: assert.c:8: func: Assertion `x > 0' failed. Annullato
L’esecuzione è stata annullata, ottenendo informazioni utili su cosa è andato storto, ma soprattutto dove è andato storto.
In produzione
Chiaramente la bruta interruzione di un programma non è sempre un comportamento accettabile. Secondo me l’uso delle asserzioni è molto utile durante lo sviluppo di un’applicazione, ma non può sostituire una robusta opera di error checking.
Inoltre, per applicazioni di una certa dimensione, l’aggiunta di molte asserzioni può causare un calo delle prestazioni, dovuto ai molti controlli aggiuntivi.
Così, per ottenere il meglio dei due approcci, è possibile indicare al preprocessore di ignorare le asserzioni nella build di produzione, passando il flag NDEBUG:
[justb@dellill C]$ gcc -Wall -DNDEBUG assert.c -o assert.out && ./assert.out Numero: 0
In questo modo non vi sarà nessun overhead nel programma finale dovuto alle varie assert.