[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home] [volume] [parte]


Capitolo 574.   C: istruzioni del precompilatore

Il linguaggio C non può fare a meno del precompilatore e le sue direttive sono regolate dallo standard.

Il precompilatore è un programma, o quella parte del compilatore, che si occupa di pre-elaborare un sorgente per generarne uno nuovo, il quale poi viene compilato con tutte le trasformazioni apportate.

Tradizionalmente, in un sistema operativo che si rifà al modello dei sistemi Unix, il precompilatore è costituito dal programma cpp che può essere utilizzato direttamente o in modo trasparente dal compilatore cc. Volendo simulare i passaggi iniziali della compilazione di un programma ipotetico denominato prg.c, evidenziando il ruolo del precompilatore, questi si potrebbero esprimere così:

cpp -E -o prg.i prg.c[Invio]

cc -o prg.o prg.i[Invio]

...

In questo caso, il file prg.i generato dal precompilatore è quello che viene chiamato dalla documentazione standard una unità di traduzione. Una unità di traduzione singola può essere il risultato della fusione di diversi file, incorporati attraverso le direttive #include, come viene descritto nel capitolo. Ciò che occorre osservare è che, quando si parla di campo di azione legato al «file», ci si riferisce al file generato dal precompilatore, ovvero all'unità di traduzione.

Va osservato che esistono programmi che utilizzano il precompilatore del linguaggio C per fini estranei al linguaggio stesso. Per esempio i file di configurazione delle risorse di X (il sistema grafico) vengono fatti elaborare da cpp prima di essere interpretati.

574.1   Linguaggio a sé stante

Le direttive del precompilatore rappresentano un linguaggio a sé stante, con proprie regole. In generale:

Se appare un simbolo # privo di altre indicazioni, questo viene semplicemente ignorato dal precompilatore. Di solito le direttive del precompilatore si scrivono senza annidamenti, ma questo fatto rischia di rendere particolarmente complicata la lettura del sorgente. A ogni modo, se si usano gli annidamenti, di solito questi riguardano solo le altre direttive e non il codice del linguaggio C puro e semplice.

I commenti del linguaggio C possono apparire solo alla fine delle direttive, ma non in tutte; pertanto vanno usati con prudenza. Vengono usati sicuramente alla fine delle direttive #else e #endif per ricordare a quale condizione si riferiscono.

574.2   Direttiva «#include»

La direttiva #include permette di includere un file. Generalmente si tratta di un cosiddetto file di intestazione, contenente una serie di definizioni necessarie al file sorgente in cui vengono incorporate. Il file da incorporare può essere indicato delimitandolo con le parentesi angolari, oppure con gli apici doppi; il modo in cui si delimita il nome del file serve a stabilire come questo deve essere cercato:(1)

#include <file>
#include "file"

I due esempi seguenti mostrano la richiesta di includere il file stdio.h secondo le due forme possibili:

#include <stdio.h>
#include "stdio.h"

Delimitando il nome tra parentesi angolari si fa riferimento a un file che dovrebbe trovarsi in una posizione stabilita dalla configurazione del compilatore; per esempio, nel caso di GNU C in un sistema GNU/Linux, dovrebbe trattarsi della directory /usr/include/. Se invece si delimita il nome tra apici doppi, generalmente si fa riferimento a una posizione precisa nel file system, attraverso l'indicazione di un percorso (secondo la modalità prevista dal sistema operativo); pertanto, scrivendo il nome del file come nell'esempio, si dovrebbe intendere che la sua collocazione debba essere la directory corrente.

Di norma, quando si indica un file da includere delimitandolo con gli apici doppi e senza indicare alcun percorso, se questo file non si trova nella directory corrente, allora viene cercato nella directory predefinita, come se fosse stato indicato tra le parentesi angolari.

Un file incorporato attraverso la direttiva #include, può a sua volta fare lo stesso con altri; naturalmente, questa possibilità va considerata per evitare di includere più volte lo stesso file e di solito si usa un accorgimento che viene descritto più avanti nel capitolo.

574.3   Direttiva «#define»

La direttiva #define serve a definire quelle che sono note come macro, ovvero delle variabili del precompilatore che, successivamente, il precompilatore stesso espande secondo regole determinate. Lo standard del linguaggio C distingue queste macro in due categorie: object-like macro e function-like macro. Nel corso di questi capitoli si usa la definizione di macro-variabile nel primo caso e di macro-istruzione nel secondo.

Come sottoinsieme delle macro-variabili vengono considerate le costanti manifeste, per rappresentare dei valori semplici che si ripetono nel sorgente. Per esempio, NULL è la costante manifesta standard per rappresentare il puntatore nullo.

#define macro [sequenza_di_caratteri]

La direttiva #define usata secondo la sintassi mostrata consente di definire delle macro-variabili, ovvero ciò che lo standard definisce object-like macro. Ciò che si ottiene è la sostituzione nel sorgente del nome indicato con la sequenza di caratteri che lo segue. Si osservi l'esempio seguente:

#define SALUTO Ciao! Come stai?

In questo caso viene dichiarata la macro-variabile SALUTO in modo tale che tutte le occorrenze di questo nome, successive alla sua dichiarazione, vengano sostituite con Ciao! Come stai?. È molto importante comprendere questo particolare: tutto ciò che appare dopo il nome della macro, a parte lo spazio che lo separa, viene utilizzato nella sostituzione. L'esempio seguente, invece rappresenta un programma completo:

:-)

#include <stdio.h>
#define SALUTO "Ciao! come stai?\n"
int main (void)
{
    printf (SALUTO);
    return 0;
}

In questo caso, la macro-variabile SALUTO può essere utilizzata in un contesto in cui ci si attende una stringa letterale, perché include gli apici doppi che sono necessari per questo scopo. Nell'esempio si vede l'uso della macro-variabile come argomento della funzione printf() e l'effetto del programma è quello di mostrare il messaggio seguente:

Ciao! come stai?

È bene precisare che la sostituzione delle macro-variabili non avviene se i loro nomi appaiono tra apici doppi, ovvero all'interno di stringhe letterali. Si osservi l'esempio seguente:

:-(

#include <stdio.h>
#define SALUTO Ciao! come stai?
int main (void)
{
    printf ("SALUTO\n");
    return 0;
}

In questo caso, la funzione printf() emette effettivamente la parola SALUTO e non avviene alcuna espansione di macro:

SALUTO

Una volta compreso il meccanismo basilare della direttiva #define si può osservare che questa può essere utilizzata in modo più complesso, facendo anche riferimento ad altre macro già definite:

#define UNO 1
#define DUE UNO+UNO
#define TRE DUE+UNO

In presenza di una situazione come questa, utilizzando la macro TRE, si ottiene prima la sostituzione con DUE+UNO, quindi con UNO+UNO+1, infine con 1+1+1 (dopo, tocca al compilatore).

Tradizionalmente i nomi delle macro-variabili vengono definiti utilizzando solo lettere maiuscole, in modo da poterli distinguere facilmente nel sorgente.

Come è possibile vedere meglio in seguito, è sensato anche dichiarare una macro senza alcuna corrispondenza. Ciò può servire per le direttive #ifdef e #ifndef.

Nella definizione di una macro-variabile può apparire l'operatore ##, con lo scopo di attaccare ciò che si trova alle sue estremità. Si osservi l'esempio seguente:

#include <stdio.h>
#define UNITO 1234 ## 5678
int main (void)
{
    printf ("%d\n", UNITO);
    return 0;
}

Eseguendo questo programma si ottiene semplicemente l'emissione del numero 12 345 678. Questo operatore può servire anche per unire assieme il nome di una macro-variabile, benché questo sia poco consigliabile:

#include <stdio.h>
#define MIAMACRO 12345678
#define UNITO MI ## A ## MA ## CRO
int main (void)
{
    printf ("%d\n", UNITO);
    return 0;
}

574.4   Direttiva «#define» con parametri

La direttiva #define può essere usata per creare una macro-istruzione, ovvero una cosa che viene usata con l'apparenza di una funzione:

#define macro(parametro[, parametro]...) sequenza_di_caratteri

Per comprendere il meccanismo è meglio avvalersi di esempi. In quello seguente, l'istruzione i = QUADRATO(i) si traduce in i = (i)*(i):

#define QUADRATO(A)       (A)*(A)
...
...
i = QUADRATO (i);
...

Si osservi il fatto che, nella definizione, la stringa di sostituzione è stata composta utilizzando le parentesi: ciò permette di evitare problemi successivamente, nelle precedenze di valutazione delle espressioni, se l'argomento della funzione simulata attraverso la macro-istruzione è composto:

...
i = QUADRATO (123 * 34 + 3);
...

In questo caso, la sostituzione genera i = (123 * 34 + 3)*(123 * 34 + 3) e si può vedere che le parentesi sono appropriate. L'esempio seguente, costituito da un programma completo, mostra l'uso di due parametri:

#include <stdio.h>
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
int main (void)
{
    printf ("valore massimo tra %d e %d: %d\n", 3, 4, MAX (3, 4));
    return 0;
}

La macro-istruzione MAX (3, 4) si traduce in ((3) > (4) ? (3) : (4)).

È molto importante fare attenzione alla spaziatura nella dichiarazione di una macro-istruzione: si può scrivere #define MAX(x,y) ..., #define MAX( x,y) ..., #define MAX(x,y ) ..., #define MAX(x, y) ..., ecc. Quello che invece non si può proprio è l'inserimento di uno spazio tra il nome della macro-istruzione e la parentesi quadra aperta. Pertanto, se si scrive #define MAX (x, y) ... si commette un errore!

Al contrario, quando la macro-istruzione viene richiamata, questo spazio può essere inserito senza problemi, come apparso già negli esempi.

Nella definizione di una macro-istruzione può essere usato l'operatore ## già descritto nella sezione precedente. Nell'esempio seguente si ottiene di visualizzare il numero 12 345 678:

#include <stdio.h>
#define UNISCI(A, B) A ## B
int main (void)
{
    printf ("%d\n", UNISCI(1234, 5678));
    return 0;
}

Inoltre, è disponibile l'operatore # che ha lo scopo di racchiudere tra apici doppi la metavariabile che lo segue immediatamente. Si osservi l'esempio seguente:

#include <stdio.h>
#define STRINGATO(a) # a
#define SALUTO STRINGATO (Ciao! come stai?\n)
int main (void)
{
    printf (SALUTO);
    return 0;
}

Prima viene definita la macro-istruzione STRINGATO, con la quale si vuole che il suo argomento sia raccolto tra apici doppi. Subito dopo viene definita la macro-variabile SALUTO che viene rimpiazzata da STRINGATO (Ciao! come stai?\n) e quindi da "Ciao! come stai?\n". Alla fine, il programma mostra regolarmente il messaggio già visto in un altro esempio precedente:

Ciao! come stai?

Si osservi cosa accadrebbe modificando l'esempio nel modo seguente, dove si vuole che la macro-istruzione STRINGATO utilizzi due parametri:

...
#define STRINGATO(a, b) # a # b
#define SALUTO STRINGATO (Ciao!, come stai?\n)
...

Evidentemente si vuole che i due argomenti forniti alla macro-istruzione STRINGATO siano raccolti ognuno tra apici doppi, pertanto la macro-variabile si trova a essere dichiarata, sostanzialmente come "Ciao!" "come stai?\n". Alla fine il risultato mostrato dal programma è differente, perché la sequenza delle due stringhe viene intesa come una sequenza sola, ma in tal caso manca lo spazio tra le due parti:

Ciao!come stai?

Si può complicare ulteriormente l'esempio per dimostrare fino a dove si estende la competenza dell'operatore #:

#include <stdio.h>
#define STRINGATO(a, b) # a , b
#define SALUTO STRINGATO (%d un amore\n, 6)
int main (void)
{
    printf (SALUTO);
    return 0;
}

Qui gli spazi sono importanti, infatti, la macro-istruzione STRINGATO si traduce in "a" , b e la virgola non avrebbe potuto essere unita alla lettera «a», altrimenti sarebbe stata inserita dentro la coppia di apici doppi. La macro-variabile SALUTO si traduce poi in "%d un amore\n" , 6, pertanto, alla fine, il programma mostra il messaggio seguente:

6 un amore

Per concludere viene mostrato un esempio ulteriore, con il quale si crea una sorta di funzione che il precompilatore deve trasformare in un blocco di istruzioni. Viene simulato il comportamento della funzione standard strncpy(), senza però restituire un valore:

#include <stdio.h>

#define STRNCPY(DST, ORG, N) { \
    char *restrict s1 = (DST); \
    const char *restrict s2 = (ORG); \
    size_t n = (N); \
    int i; \
    for (i = 0; i < n && s2[i] != 0; i++) \
      { \
        s1[i] = s2[i]; \
      } \
    s1[i] = 0; }

int main (void)
{
    char stringa[100];
    STRNCPY (stringa, "Buon giorno a tutti!", 50)       // Niente ";"
    printf ("%s\n", stringa);
    return 0;
}

Si può vedere che, per richiamare questa macro-istruzione, non si richiede che le sia aggiunto il punto e virgola. Infatti, la macro in sé si espande in un raggruppamento tra parentesi graffe, che non ne ha bisogno; d'altra parte, volendoglielo aggiungere, non si può creare alcun problema.

La dichiarazione di una macro-istruzione può prevedere una quantità variabile di parametri, come avviene già per le funzioni (sezione 578.3). Per ottenere questo si aggiungono dei puntini di sospensione alla fine dell'elenco dei parametri fissi, quindi, si utilizza la parola chiave __VA_ARGS__ per individuare gli argomenti opzionali. L'esempio seguente riproduce il funzionamento di printf(), richiamando la stessa funzione:

#include <stdio.h>

#define PRINTF(A, ...) printf (A, __VA_ARGS__)

int main (void)
{
    PRINTF ("I primi numeri interi: %d, %d, %d\n", 1, 2, 3);
    return 0;
}

Questa volta il punto e virgola finale serve, perché non è stato incluso nella definizione della macro-istruzione.

A proposito di __VA_ARGS__ va ancora osservato che individua sì gli argomenti opzionali, ma di questi ne deve essere specificato almeno uno. Pertanto, la macro-istruzione PRINTF(), per come è stata dichiarata nell'esempio precedente, va usata sempre con almeno due argomenti. In questo caso, per poter usare la macro-istruzione con un argomento solo, la sua definizione va modificata nel modo seguente:

...
#define PRINTF(...) printf (__VA_ARGS__)
...

574.5   Direttive «#if», «#else», «#elif» e «#endif»

Le direttive #if, #else, #elif e #endif, permettono di delimitare una porzione di codice che debba essere utilizzato o ignorato in relazione a una certa espressione che può essere calcolata solo attraverso definizioni precedenti.

#if espressione
    espressione
[#elif espressione
     espressione]
...
[#else
     espressione]
#endif

Le espressioni che rappresentano le condizioni da valutare seguono regole equivalenti a quelle del linguaggio, tenendo conto che se si vogliono usare delle variabili, queste possono solo essere quelle del precompilatore. L'esempio seguente mostra la dichiarazione di una macro-variabile a cui si associa un numero, quindi si vede un confronto basato sul valore in cui si espande la macro-variabile stessa:

#define DIM_MAX 1000
...
...
int main (void)
{
...
#if DIM_MAX>100
    printf ("Dimensione enorme.\n");
    ...
#else
    printf ("Dimensione normale.\n");
    ...
#endif
...
}

L'esempio mostra il confronto tra la macro-variabile DIM_MAX e il valore 100. Essendo stata dichiarata per tradursi in 1 000, il confronto è equivalente a 1 000 > 100 che risulta vero, pertanto il compilatore include solo le istruzioni relative.

Gli operatori di confronto che si possono utilizzare per le espressioni logiche sono i soliti, in particolare, è bene ricordare che per valutare l'uguaglianza si usa l'operatore ==, come nell'esempio successivo:

#define NAZIONE ita
...
...
int main (void)
{
#if NAZIONE==ita
    char valuta[] = "EUR";
    ...
#elif NAZIONE==usa
    char valuta[] = "USD";
    ...
#endif
...
}

Queste direttive condizionali possono essere annidate; inoltre possono contenere anche altri tipi di direttiva del precompilatore.

574.6   Direttive «#if defined», «#if !defined», «#ifdef» e «#ifndef»

Nelle espressioni che esprimono una condizione per la direttiva #if è possibile usare l'operatore defined, seguito dal nome di una macro-variabile. La condizione defined macro si avvera se la macro indicata risulta definita, anche se dovesse essere priva di valore. Per converso, la condizione !defined macro si avvera quando la macro non risulta definita.

La direttiva #if defined può essere abbreviata come #ifdef, mentre #if !defined si può esprimere come #ifndef.

#define DEBUG
...
int main (void)
{
...
#if defined DEBUG
    printf ("Punto di controllo n. 1\n");
    ...
#endif // DEBUG
...
}
#define DEBUG
...
int main (void)
{
...
#ifdef DEBUG
    printf ("Punto di controllo n. 1\n");
    ...
#endif // DEBUG
...
}

I due esempi equivalenti mostrano il caso in cui sia dichiarata una macro DEBUG (che non si traduce in alcunché) e in base alla sua esistenza viene incluso il codice che mostra un messaggio particolare.

#define OK
...
int main (void)
{
#if !defined OK
    printf ("Punto di controllo n. 1\n");
    ...
#endif // OK
...
}
#define OK
...
int main (void)
{
#ifndef OK
    printf ("Punto di controllo n. 1\n");
    ...
#endif // OK
...
}

Questi due esempi ulteriori sono analoghi a quanto già mostrato, con la differenza che le istruzioni controllate vengono incluse nella compilazione solo se la macro indicata non è stata dichiarata.

Quando si scrivono delle condizioni basate sull'esistenza o meno di una macro, è bene aggiungere alla conclusione un commento con cui si ricorda a quale macro si sta facendo riferimento, in modo da districarsi più facilmente in presenza di più livelli di annidamento. Ma occorre fare molta attenzione, perché se si commettono errori con questi commenti il compilatore non può dare alcuna segnalazione in merito e si rende incomprensibile il sorgente alla rilettura successiva.

Esiste una situazione ricorrente in cui viene utilizzata la direttiva #if !defined o #ifndef che è bene conoscere. Spesso i file di intestazione che vengono inclusi con direttive #include includono a loro volta tutto quello che serve loro, ma così facendo c'è la possibilità che lo stesso file venga incluso più volte. Per evitare di prendere in considerazione una seconda volta lo stesso file, si usa un artificio molto semplice, come si vede nel listato successivo che riproduce il contenuto del file stdbool.h di una libreria standard ipotetica:

#ifndef _STDBOOL_H
#define _STDBOOL_H      1

#define bool    _Bool
#define true    1
#define false   0
#define __bool_true_false_are_defined   1

#endif // _STDBOOL_H

Come si vede, se il codice viene eseguito per la prima volta, la condizione ifndef _STDBOOL_H non si avvera e di conseguenza la macro-variabile _STDBOOL_H viene creata effettivamente e quindi viene considerato tutto il resto del codice fino alla direttiva #endif. Ma quando si tenta di eseguire lo stesso codice per la seconda volta, o per altre volte successive, dato che la macro-variabile _STDBOOL_H risulta già definita, questo codice viene ignorato semplicemente, senza altre conseguenze.

Le direttive che consentono di compilare selettivamente solo una porzione del codice, consentono di realizzare del codice molto sofisticato, ma rischiano di renderlo estremamente complesso da interpretare attraverso la lettura umana. Pertanto, è bene limitarne l'uso alle situazioni che sono utili effettivamente.

574.7   Direttiva «#undef»

La direttiva #undef permette di eliminare una macro a un certo punto del sorgente:

#undef macro

Si mostra un esempio molto semplice, nel quale prima si dichiara la macro-variabile NAZIONE, poi, quando non serve più, questa viene eliminata.

#define NAZIONE ita
...
/* In questa posizione, NAZIONE risulta definita */
...
#undef NAZIONE
...
/* In questa posizione, NAZIONE non è definita */
...

574.8   Direttiva «#line»

Di norma, il compilatore abbastanza evoluto consente di inserire nel file eseguibile delle informazioni che consentano di abbinare il codice eseguibile alle righe del file sorgente originale. Per esempio, con GNU C si può usare l'opzione -gstabs e altre simili. Naturalmente, in condizioni normali il compilatore conta da solo le righe e annota il nome del file sorgente originale.

Con la direttiva #line è possibile istruire il compilatore in modo che tenga in considerazione un numero di riga differente, ma soprattutto consente di specificare a quale file sorgente ci si vuole riferire.

#line n_riga ["nome_file_sorgente"]

C'è da osservare che, per il programmatore, è poco probabile che sia necessario indicare una riga diversa nello stesso sorgente. In effetti, diventa più utile se si abbina il nome di un altro file. Per comprendere come possa essere utilizzata questa possibilità, occorre ipotizzare la costruzione di un altro compilatore per un linguaggio nuovo, con il quale si genera codice in linguaggio C. A titolo di esempio si suppone di volere tradurre il file hanoi.pseudo che si vede nel listato 574.32 in un sorgente C, denominato hanoi.c, mantenendo il riferimento alle righe originali.

Listato 574.32. Il file hanoi.pseudo.

      1 HANOI (N, P1, P2)
      2     IF N > 0
      3         THEN
      4             HANOI (N-1, P1, 6-P1-P2)
      5             scrivi: "Muovi l'anello" N "dal piolo" P1 "al piolo" P2
      6             HANOI (N-1, 6-P1-P2, P2)
      7     END IF
      8 END HANOI
      9 
     10 MAIN ()
     11     HANOI (3, 1, 2)
     12 END MAIN

Per ottenere il risultato atteso, il file hanoi.c deve contenere diverse direttive #line, come si vede nel listato 574.33, anche se alcune di quelle potrebbero essere omesse, contando sull'incremento automatico da parte del compilatore.

Listato 574.33. Il file hanoi.c.

#include <stdio.h>

#line 1 "hanoi.pseudo"
void hanoi (int N, int P1, int P2)
{
    #line 2 "hanoi.pseudo"
    if (N > 0)
      {
        #line 4 "hanoi.pseudo"
        hanoi (N-1, P1, 6-P1-P2);
        #line 5 "hanoi.pseudo"
        printf ("Muovi l'anello %d dal piolo %d al piolo %d\n", N, P1, P2);
        #line 6 "hanoi.pseudo"
        hanoi (N-1, 6-P1-P2, P2);
        #line 7 "hanoi.pseudo"
      }
    #line 8 "hanoi.pseudo"
}

#line 10 "hanoi.pseudo"
int main (void)
{
    #line 11 "hanoi.pseudo"
    hanoi (3, 1, 2);
    #line 12 "hanoi.pseudo"
    return 0;
    #line 12 "hanoi.pseudo"
}

La compilazione del file hanoi.c potrebbe avvenire nel modo seguente:

cc -Wall -gstabs hanoi.c

Si dovrebbe ottenere il file eseguibile a.out e si verifica sommariamente se funziona:

./a.out

Muovi l'anello 1 dal piolo 1 al piolo 2
Muovi l'anello 2 dal piolo 1 al piolo 3
Muovi l'anello 1 dal piolo 2 al piolo 3
Muovi l'anello 3 dal piolo 1 al piolo 2
Muovi l'anello 1 dal piolo 3 al piolo 1
Muovi l'anello 2 dal piolo 3 al piolo 2
Muovi l'anello 1 dal piolo 1 al piolo 2

Il risultato è quello previsto. Se lo si esegue con l'ausilio di programmi come GDB, si può osservare che il riferimento al sorgente originale è quello del file hanoi.pseudo:

gdb a.out

(gdb) break main[Invio]

Breakpoint 1 at 0x80483d8: file hanoi.pseudo, line 11.

(gdb) run[Invio]

Starting program: /home/tizio/a.out
...
Breakpoint 1, main () at hanoi.pseudo:11
11          HANOI (3, 1, 2)

(gdb) stepi[Invio]

0x080483e0      11          HANOI (3, 1, 2)

(gdb) stepi[Invio]

0x080483e8      11          HANOI (3, 1, 2)

(gdb) stepi[Invio]

0x080483ef      11          HANOI (3, 1, 2)

(gdb) stepi[Invio]

hanoi (n=3, p1=1, p2=2) at hanoi.pseudo:2
2           IF N > 0

(gdb) stepi[Invio]

0x08048355      2           IF N > 0

(gdb) stepi[Invio]

0x08048357      2           IF N > 0

(gdb) stepi[Invio]

2           IF N > 0
(gdb) stepi
0x0804835e      2           IF N > 0

(gdb) stepi[Invio]

4                   HANOI (N-1, P1, 6-P1-P2)

(gdb) stepi[Invio]

0x08048363      4                   HANOI (N-1, P1, 6-P1-P2)

(gdb) quit[Invio]

Figura 574.46. Esecuzione controllata del programma attraverso DDD.

hanoi

574.9   Direttiva «#error»

La direttiva #error serve a generare un messaggio diagnostico in fase di compilazione, normalmente con lo scopo di interrompere lì il procedimento. In pratica è un modo per interrompere la compilazione già in fase di elaborazione da parte del precompilatore, al verificarsi di certe condizioni.

#error messaggio

Il messaggio viene trattato in modo letterale, senza l'espansione delle macro.

#if ! __STDC_IEC_559__
#error compilatore non conforme alle specifiche IEC 60599!
#endif

L'esempio mostra una situazione verosimile per l'utilizzo della direttiva #error, dove si controlla che il valore in cui si espande la macro-variabile __STDC_IEC_559__ sia diverso da zero, ma se non è così viene visualizzato il messaggio di errore e la compilazione dovrebbe venire interrotta.

574.10   Macro predefinite

Lo standard del C prevede che il compilatore disponga di alcune macro-variabili predefinite, elencate sinteticamente nella tabella successiva.

Tabella 574.48. Macro-variabili predefinite secondo lo standard.

Macro-variabile Descrizione
__DATE__
__TIME__
La data e l'ora della compilazione sono accessibili attraverso le macro-variabili __DATE__ e __TIME__. Il formato della prima macro-variabile è "Mmm gg aaaa" e quello della seconda è "hh:mm:ss". Come si vede, le due macro-variabili si espandono in una stringa delimitata correttamente da apici doppi.
__FILE__
__LINE__
Attraverso le macro-variabili __FILE__ e __LINE__ il programma può accedere all'informazione sul nome del file sorgente e della riga originale. Il nome del file e il numero della riga possono essere alterati attraverso la direttiva #line.
__STDC__
__STDC_HOSTED__
__STDC_VERSION__
La macro-variabile __STDC__ che si espande nel valore 1 sta a indicare che si tratta di un compilatore conforme allo standard; la macro __STDC_HOSTED__, se si espande nel valore 1, indica una conformità stretta, definita come hosted implementation; la macro __STDC_VERSION__ si espande nella versione dello standard. Il valore in cui si espande la terza macro-variabile contiene l'anno e il mese, come per esempio 199901L, con la specificazione che si tratta di una costante numerica di tipo long int.
__STDC_IEC_559__
Se esiste la macro-variabile __STDC_IEC_599__ che si espande nel valore 1, si intende indicare la conformità alle specifiche dello standard IEC 60559, inerenti l'aritmetica a virgola mobile.
__STDC_IEC_559_COMPLEX__
Se esiste la macro-variabile
__STDC_IEC_599_COMPLEX__
che si espande nel valore 1, si intende indicare la conformità alle specifiche dello standard IEC 60559, inerenti l'aritmetica «complessa».
__STDC_ISO_10646__
Se esiste la macro-variabile __STDC_ISO_10646__, questa dovrebbe espandersi nella versione dello standard ISO/IEC 10646 che riguarda la codifica universale dei caratteri. La versione che si ottiene è un numero contenente l'anno e il mese, seguito dalla lettera «L», a indicare che si tratta di una costante numerica di tipo long int.

A parte il caso di __FILE__ e __LINE__, le macro-variabili si espandono in un valore fisso.

574.11   Pragma

Attraverso i «pragma» è possibile dare al compilatore delle istruzioni che sono al di fuori dello standard. Il pragma, in sé, è un messaggio testuale che viene passato al compilatore, il quale può interpretarlo in fase di precompilazione o in quella successiva. Lo standard prevede due forme per esprimere un pragma al compilatore:

#pragma messaggio
_Pragma ("messaggio")

Il testo che compone il pragma nella sua prima forma viene trattato letteralmente, mentre quello del secondo modello richiede la protezione di alcuni caratteri: \" e \\ corrispondono rispettivamente a " e \. I due esempi seguenti sono equivalenti:

#pragma GCC dependency "parse.y"
_Pragma ("GCC dependency \"parse.y\"")

Lo standard prevede anche che sia possibile creare delle macro-istruzioni che incorporino un pragma, come nell'esempio seguente:

#define DO_PRAGMA(x) _Pragma (#x)
DO_PRAGMA (GCC dependency "parse.y")

1) Lo standard non impone che si tratti di file veri e propri; tuttavia, in un sistema Unix o in qualunque altro sistema operativo analogo, questi sarebbero file da cercare secondo criteri stabiliti, come viene descritto.


Appunti di informatica libera 2008 --- Copyright © 2000-2008 Daniele Giacomini -- <appunti2 (ad) gmail·com>


Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome c_istruzioni_del_precompilatore.htm

[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [indice analitico] [home]

Valid ISO-HTML!

CSS validator!

Gjlg Metamotore e Web Directory