Direttive per il preprocessore
Da Hacknowledge.
Ogni compilatore traduce le istruzioni di un file sorgente in linguaggio macchina. Il programmatore generalmente non è consapevole del lavoro del compilatore: si fornisce delle istruzioni di un linguaggio di alto livello per evitare le complessità gestionali del linguaggio macchina. Ma, comunque, è importante poter comunicare con il compilatore. Il C fa uso del preprocessore per estendere la sua potenza e la sua notazione, consentendo al programmatore un'interazione con il compilatore. L'identificatore delle righe che riguardano le direttive ad esso è #, che nel C ANSI può essere anche preceduto da spazi mentre nel C Tradizionale deve trovarsi all'inizio della riga. Le direttive non fanno comunque parte della grammatica del linguaggio, ampliano solo l'ambiente di programmazione. Per lo standard ANSI, le direttive sono le seguenti:
#define #error #include #elif #if #line #else #ifdef #pragma #endif #ifndef #undef #warning
Indice |
[modifica] La direttiva #include
Solitamente anche nei programmi più banali si usa la direttiva #include per, appunto, includere nel sorgente file esterni o librerie.
Per includere una libreria si usano le parentesti angolari < e >, mentre per includere un file esterno o magari nella stessa cartella del programma si usano i doppi apici ".
Un esempio di inclusione di una libreria e un file che si trova nella cartella superiore di dove si trova il sorgente in cui la includiamo:
#include <stdio.h> #include "../file1.h"
In questo caso il preprocessore quando incontrerà queste righe le sostituirà con il contenuto del file richiamato.
In Unix solitamente i file d'intestazione specificati nelle parentesi angolari si trovano nel percorso /usr/include/.
Nei file inclusi possono naturalmente anche esserci altre direttive al preprocessore che verranno poi a loro volta "lavorate".
[modifica] La direttiva #define
La direttiva #define si usa, appunto, per definire qualcosa ad esempio:
#define scrivi printf
In questo caso la definizione è scrivi che va a sostituire la parola printf quindi nel corso del programma al posto di:
printf("Ciao preprocessore!");
Si potrà scrivere
scrivi("Ciao preprocessore!");
Comunque il define può anche definire numeri, simboli o altro. Vari esempi di define:
#define EQ == #define OK printf("OK\n"); #define DEBUG 1
Nell'esempio sopra visualizzato si può notare il cosidetto "ZUCCHERO SINTATTICO" dato che ogni tanto a un programmatore in C può scappare di mettere un solo = nelle uguaglianze così con la definizione EQ == si potrà scrivere così:
if ( a EQ b ) ...
Evitando errori di sintassi.
Un'altra cosa da notare è la definizione DEBUG molto utile nelle fasi di test di un programma che si può usare nel controllo del flusso tramite sempre direttive al preprocessore che vedremo adesso.
[modifica] Controllo del flusso
Con le direttive al preprocessore si può esegure anche un flusso del controllo ( if, else ) utilizzando le direttive #if, #else, #elif, #endif e #ifdef.
Iniziamo a spiegarli dai primi cioè #if, #else, #elif e #endif che corrispondo al controllo del flusso normalmente utilizzato: if, else, else if mentre l'ultimo #endif è "originale" del preprocessore.
Esempio:
#include <stdio.h> #define A 2 #define B 4 int main(void) { #if A == 2 || B == 2 printf("A o B sono uguali a 2\n"); #elif A == 4 && B == 4 printf("A e B sono uguali a 4\n"); #elif A != B printf("A è diversa da B\n"); #else printf("A e B sono di un valore non definito\n"); #endif return 0; }
Si possono notare le seguenti cose:
- Le variabili su cui eseguire controlli devono essere definite tramite #define
- Anche nel controllo del flusso tramite direttive al preprocessore si possono eseguire controlli con || ( OR ), && ( AND ) e != ( NOT ).
- La direttiva #endif "dice" al preprocessore che il controllo del flusso è finito.
Per eseguire un debug con questo sistema si potrebbe inserire qualcosa tipo:
#if DEBUG 1 printf("x = %d\n", x); printf("y = %s\n", y); ... #endif
Ma ora vedremo con la direttiva #ifdef cosa si può fare, in pratica "ifdef" sta per "se definito" quindi si può tramite essa controllare se una variabile è stata definita o meno e con l'aggiunta delle direttive #undef e #ifndef vedremo cosa si può fare con l'esempio seguente:
#include <stdio.h> #define NUMERO 4 int main(void) { #ifndef NUMERO #define NUMBER 4 #ifdef NUMBER #undef NUMBER #define NUMERO 4 #endif return 0; }
Innanzitutto chiariamo cosa vuol dire ifndef e undef, la prima equivale a "se non è definito" ( if not define ) mentre la seconda equivale a "togli la definizione" ( undefine ).
Nell'esempio sopra definiamo NUMERO dopodichè all'interno del corpo main iniziamo col verificare se non è definito numero, se ciò è vero definiamo NUMBER, se invece è definito NUMBER togliamo la definizione di NUMBER e definiamo NUMERO. Dopodichè si esce dal programma.
La direttiva #undef diciamo che è inutile nei piccoli programmi, ma risulta utilissima nei programmi di grandi dimensioni composti magari da molti file e da molte persone che ci lavorano e senza andare in giro o sfogliare tra i file se una cosa è stata definita o meno questa semplice direttiva ci facilita la vita.
L'uso di #ifdef è utilissimo nel caso in cui si vogliano usare dei file header. Infatti, un file header potrebbe essere incluso in due diversi file sorgenti che si vanno a compilare insieme, e questo potrebbe generare ambiguità ed errori in fase di compilazione (funzioni o dati che risulterebbero dichiarati due volte). Per evitare questo problema si usano proprio le direttive al preprocessore. Nell'header che andremo a creare avremo una cosa del genere:
- Se la variabile _NOMEHEADER_H non è definita
- Definisci la variabile
- Dichiara tutto il contenuto dell'header
- Altrimenti, termina la dichiarazione dell'header
In codice:
#ifndef _MIOHEADER_H #define _MIOHEADER_H // Qui metto tutte le mie funzioni e i miei dati #endif
In pratica, una volta definita la macro _MIOHEADER_H il file header non verrà più incluso in nessun altro file, risolvendo quindi gli eventuali problemi di header definiti due o più volte.
[modifica] Macro con parametri
Con la direttiva #define si possono definire macro con parametri. Il "come" lo vedremo nell'esempio seguente:
#define PD(x) ((x) * (x))
PD sta per "Potenza di Due" in pratica se nel corso del programma si esegue qualcosa tipo:
a = PD(3);
il preprocessore la espandera in questo modo:
a = ((3) * (3));
Un'altro esempio per concludere aggiungerei:
#define minore(x, y) (((x) < (y)) ? (x) : (y))
Questa definizione una volta richiamata rilascia il valore più piccolo tra i due numeri dati. Naturalmente l'utilizzo epsanso di parentesi consente di essere sicuri che una volta espansa la stringa tutte le operazioni vengano eseguite nel modo desiderato.
[modifica] Macro predefinite
Nel C esistono 5 tipi di macro già definite sempre disponibili che non possono essere ridefinite dal programmatore. Si possono vedere nello schema seguente:
/* MACRO || COSA CONTIENE */ __DATE__ /* Una stringa che contiene la data corrente */ __FILE__ /* Una stringa che contiene il nome del file */ __TIME__ /* Una stringa che contiene l'ora corrente */ __LINE__ /* Un intero che raprresenta il numero di riga corrente */ __STDC__ /* Un intero diverso da 0 se l'implementazione segue lo standard ANSI C */
[modifica] Operatori # e ##
Questo tipo di operatori sono disponibili solo nel C ANSI. L'operatore unario # trasforma un parametro formale di una definizione di macro in una stringa ad esempio:
#define nomi(a, b) printf("Ciao " #a " e " #b "! Benvenuti!\n");
da richiamare nel corpo main con:
nomi(HdS619, BlackLight);
Una volta espanso dal preprocessore questa linea diventerà:
printf("Ciao " "HdS619" " e " "BlackLight" "! Benvenuti!\n");
Ora invece vediamo l'operatore binario ## che serve a concatenare token. Ad esempio:
#include <stdio.h> #define X(y) x ## y X(3) = X(4) = X(12) = ...
verrà espanso in:
x3 = x4 = x12 = ...
In pratica si può pensare che "colleghi" i due parametri x e y.
[modifica] Direttive #error e #warning
Le direttive #error e #warning servono rispettivamente per dare errori nella compilazione oppure avvisi.
Solitamente queste due direttive vengono usate insieme a quelle che controllano il "flusso di compilazione" ( #else, #if, #undef, ecc... ).
La loro sintassi è la seguente:
#error Messaggio di errore #warning Messaggio di avvertimento

