Macro Predefinite in Linguaggio C

Oltre alle macro definite dall'utente, lo standard C definisce alcune macro predefinite che possono essere utilizzate in un programma C.

Tali macro forniscono informazioni sul programma in fase di compilazione, come la data e l'ora di compilazione, il nome del file sorgente e il numero di riga, la conformità allo standard C e la versione dello standard adottata.

In questa lezione vedremo una carrellata, ovviamente non esaustiva, delle macro predefinite più comuni in linguaggio C.

Macro Predefinite

Tutti i compilatori C forniscono un insieme di macro predefinite che possono essere utilizzate in un programma C. Queste macro sono definite dal compilatore e non possono essere modificate.

Tipicamente, tali macro rappresentano costanti numeriche o letterali di stringa.

Macro __DATE__ e __TIME__

Queste due macro restituiscono la data e l'ora corrente in cui il programma è stato compilato.

In particolare, __DATE__ contiene la data nel formato Mmm gg aaaa del giorno in cui il programma è stato compilato, mentre __TIME__ contiene l'ora nel formato hh:mm:ss.

Queste due macro sono utili per includere informazioni sulla data e l'ora di compilazione all'interno del programma e, quindi, per poter discriminare versioni differenti di uno stesso programma.

Ad esempio, possiamo usare queste due macro nel nostro codice come segue:

/* ... */

printf("Programma Esempio\n");
printf("Compilato il %s alle %s\n", __DATE__, __TIME__);

/* ... */

Provando a compilare ed eseguire il programma, otteniamo un output simile a questo:

Programma Esempio
Compilato il Mar 12 2022 alle 14:34:21
Definizione

Macro __DATE__ e __TIME__

  • __DATE__: Macro predefinita che restituisce la data di compilazione del programma nel formato Mmm gg aaaa.
  • __TIME__: Macro predefinita che restituisce l'ora di compilazione del programma nel formato hh:mm:ss.

Macro __FILE__ e __LINE__

Le macro predefinite __FILE__ e __LINE__ restituiscono il nome del file sorgente e il numero di riga in cui si trovano rispettivamente.

Uno degli utilizzi fondamentali di queste due macro è il debugging e il logging del codice.

Nel primo caso, esse vengono utilizzare per identificare il punto di un programma che ha provocato un errore. Possiamo, infatti, utilizzare queste due macro per stampare il nome del file e il numero di riga in cui si è verificato un errore.

/* ... */

if (condizione_errore) {
    printf("Errore nel file %s alla riga %d\n", __FILE__, __LINE__);
}

/* ... */

In questo caso, se si verifica la condizione condizione_errore, il programma stamperà il nome del file e il numero di riga in cui si è verificato l'errore. Ad esempio:

Errore nel file main.c alla riga 42

Un altro importante utilizzo è il logging. Quando il programma compie una certa azione o esegue una certa operazione, possiamo stampare il nome del file e il numero di riga in cui si è verificata l'azione.

/* ... */

Operazione1();
printf("Operazione1 completata nel file %s alla riga %d\n", __FILE__, __LINE__);

Operazione2();
printf("Operazione2 completata nel file %s alla riga %d\n", __FILE__, __LINE__);

/* ... */

In questo caso, il programma stamperà il nome del file e il numero di riga in cui si è completata l'operazione. Ad esempio:

Operazione1 completata nel file main.c alla riga 42
Operazione2 completata nel file main.c alla riga 45
Definizione

Macro __FILE__ e __LINE__

  • __FILE__: Macro predefinita che restituisce il nome del file sorgente.
  • __LINE__: Macro predefinita che restituisce il numero di riga in cui si trova la macro.

Macro __STDC__ e __STDC_VERSION__

La macro predefinita __STDC__ è utilizzata per verificare se il compilatore C è conforme allo standard C. Oggigiorno è poco usata, in quanto se un compilatore non è conforme allo standard significa che non adotta lo standard C89 e quindi si tratta di un compilatore obsoleto.

In ogni caso vale 1 se il compilatore è conforme allo standard C, altrimenti è indefinita.

/* ... */

if (__STDC__) {
    printf("Il compilatore è conforme allo standard C\n");
} else {
    printf("Il compilatore non è conforme allo standard C\n");
}

Ne vedremo un possibile utilizzo nella lezione sulla compilazione condizionale.

Direttamente collegata alla macro __STDC__ è la macro __STDC_VERSION__, che restituisce la versione dello standard C adottata dal compilatore.

In particolare, se la macro __STDC__ è definita, e quindi il compilatore è conforme allo standard, la macro __STDC_VERSION__ restituirà un valore differente a seconda dello standard adottato. In particolare, i suoi valori saranno:

Valore Standard C
199409L C89
199901L C99
201112L C11
201710L C17
Tabella 1: Valori della macro __STDC_VERSION__ a seconda dello standard C adottato dal compilatore.

In particolare, i valori rappresentano l'anno e il mese in cui lo standard è stato ratificato.

Definizione

Macro __STDC__ e __STDC_VERSION__

  • __STDC__: Macro predefinita che vale 1 se il compilatore è conforme allo standard C, altrimenti è indefinita.
  • __STDC_VERSION__: Macro predefinita che restituisce la versione dello standard C adottata dal compilatore sotto forma di anno e mese.

Macro __STDC_HOSTED__

Per comprendere cosa faccia la macro __STDC_HOSTED__, dobbiamo prima capire cosa sia un ambiente hosted e freestanding.

Quando si parla di implementazione del linguaggio C si intende l'insieme di compilatore, librerie e software aggiuntivi necessari all'esecuzione di un programma C.

A partire dallo standard C99, il linguaggio C definisce due tipi di implementazioni:

  • Hosted: un'implementazione hosted è un'implementazione che fornisce un ambiente completo per l'esecuzione di un programma C. Questo ambiente include librerie standard, un sistema operativo e un'interfaccia utente.

    Il vincolo imposto dallo standard è che qualunque implementazione hosted deve essere in grado di compilare ed eseguire un programma scritto in linguaggio C standard.

  • Freestanding: un'implementazione freestanding è un'implementazione che non fornisce un ambiente completo. Non è detto che siano presenti le librerie e i file header dello standard C. Un'implementazione di questo tipo viene spesso utilizzata per sistemi embedded, ad esempio i microcontrollori come i PIC, gli AVR o Arduino.

La macro __STDC_HOSTED__ è utilizzata per verificare se il compilatore C è in grado di compilare un programma hosted o freestanding. Nel primo caso, la macro vale 1, altrimenti è indefinita.

Definizione

Macro __STDC_HOSTED__

La macro __STDC_HOSTED__, definita a partire dallo standard C99, vale 1 se il compilatore C è in grado di compilare un programma hosted, altrimenti è indefinita.

Altre macro standard

Oltre alle macro predefinite che abbiamo visto, lo standard C99 definisce altre macro che possono essere utili in determinate situazioni.

Ad esempio, possiamo citare:

  • __STDC_IEC_559__: vale 1 se il compilatore supporta il formato a virgola mobile IEC 60559 (conosciuto come IEEE 754).
  • __STDC_IEC_559_COMPLEX__: vale 1 se il compilatore supporta il formato a virgola mobile IEC 60559 per i numeri complessi.
  • __STDC_ISO_10646__: vale 1 se il compilatore supporta il formato di codifica dei caratteri ISO 10646.

Analogamente, dal momento che lo standard C11 introduce nuove funzionalità, sono state introdotte nuove macro predefinite per verificarne la presenza:

  • __STDC_NO_THREADS__: vale 1 se il compilatore non supporta i thread.
  • __STDC_NO_ATOMICS__: vale 1 se il compilatore non supporta le operazioni atomiche.

Queste due macro riguardano le nuove funzionalità introdotte dallo standard C11 per la programmazione concorrente che vedremo nelle prossime lezioni.

Identificatore __func__

Concludiamo questa lezione sulle macro predefinite con un'ultimo identificatore speciale introdotto nel C99: __func__.

L'identificatore __func__ non è una macro, quindi in realtà andrebbe affrontato in un'altra lezione. Tuttavia, il suo utilizzo è sempre congiunto alle macro __FILE__ e __LINE__, per cui ci è sembrato opportuno trattarlo in questa lezione.

Ciascuna funzione C, inclusa la funzione main, ha accesso a questo speciale identificatore che memorizza il nome della funzione correntemente in esecuzione. A livello pratico, è come se il compilatore, all'inizio di ogni funzione, dichiarasse una variabile statica costante in questo modo:

static const char __func__[] = "nome_funzione";

Dove nome_funzione è il nome della funzione corrente.

Non si tratta di una macro, ma il suo utilizzo è simile a quello delle macro __FILE__ e __LINE__, e viene molto utilizzata per debugging e logging.

Possiamo, ad esempio, definire due macro per il logging:

#define INGRESSO_FUNZIONE() printf("Ingresso in %s\n", __func__)
#define USCITA_FUNZIONE() printf("Uscita da %s\n", __func__)

E utilizzarle all'interno delle nostre funzioni:

void funzione() {
    INGRESSO_FUNZIONE();

    /* ... */

    USCITA_FUNZIONE();
}

In questo modo, il programma stamperà il nome della funzione in cui si entra e da cui si esce:

Ingresso in funzione
Uscita da funzione

Un altro possibile utilizzo è quello di passare __func__ ad una funzione, in maniera tale che la funzione chiamata sappia il nome della funzione chiamante.

void funzione_di_logging(const char *nome_funzione) {
    printf("Chiamata da %s\n", nome_funzione);
}

void funzione() {
    funzione_di_logging(__func__);
}

In questo caso, la funzione funzione_di_logging stamperà il nome della funzione chiamante:

Chiamata da funzione
Definizione

Identificatore __func__

L'identificatore __func__ non è una macro, ma una variabile statica costante che memorizza il nome della funzione correntemente in esecuzione.

In Sintesi

Questa lezione è stata utile per mostrare una carrellata, non esaustiva, delle macro predefinite in linguaggio C. Queste macro sono utili per ottenere informazioni sul programma in fase di compilazione, come la data e l'ora di compilazione, il nome del file sorgente e il numero di riga, la conformità allo standard C e la versione dello standard adottata.

Spesso, molti compilatori C forniscono anche delle macro aggiuntive che possono essere utili in determinate situazioni. In questi casi conviene sempre fare riferimento ai manuali del compilatore per avere informazioni dettagliate sulle macro supportate.

Infine, abbiamo visto l'identificatore __func__, che non è una macro, ma una variabile statica costante che memorizza il nome della funzione correntemente in esecuzione. Questo identificatore è molto utile per il debugging e il logging del codice.

Nella prossima lezione, studieremo le macro con numero variabile di parametri, una delle nuove funzionalità introdotte dallo standard C99.