C Direttive e macro di programmazione/preprocessore

Le direttive sono istruzioni speciali dirette al preprocessore (direttiva preprocessore) o al compilatore (direttiva compilatore) su come dovrebbe elaborare parte o tutto il codice sorgente o impostare alcuni flag sull’oggetto finale e sono usate per rendere più semplice la scrittura del codice sorgente (più portabile per esempio) e per rendere il codice sorgente più comprensibile. Le direttive sono gestite dal preprocessore, che è un programma separato invocato dal compilatore o parte del compilatore stesso.

#includeEdit

C ha alcune funzionalità come parte del linguaggio e alcune altre come parte di una libreria standard, che è un repository di codice disponibile insieme a ogni compilatore C conforme allo standard. Quando il compilatore C compila il programma, di solito lo collega anche alla libreria C standard. Ad esempio, incontrando una direttiva #include <stdio.h>, sostituisce la direttiva con il contenuto dello stdio.h file di intestazione.

Quando si utilizzano le funzionalità della libreria, C richiede di dichiarare ciò che si sta utilizzando. La prima riga del programma è una direttiva di pre-elaborazione che dovrebbe essere simile a questa:

#include <stdio.h>

La riga precedente causa le dichiarazioni C che si trovano nello stdio.intestazione h da includere per l’uso nel programma. Di solito questo è implementato semplicemente inserendo nel tuo programma il contenuto di un file di intestazione chiamato stdio.h, situato in una posizione dipendente dal sistema. La posizione di tali file può essere descritta nella documentazione del compilatore. Un elenco di file di intestazione C standard è elencato di seguito nella tabella Intestazioni.

Lo stdio.l’intestazione h contiene varie dichiarazioni per input/output (I/O) utilizzando un’astrazione di meccanismi di I / O chiamati flussi. Ad esempio, esiste un oggetto di flusso di output chiamato stdout che viene utilizzato per emettere il testo sullo standard output, che di solito visualizza il testo sullo schermo del computer.

Se si utilizzano parentesi angolari come nell’esempio precedente, il preprocessore viene incaricato di cercare il file include lungo il percorso dell’ambiente di sviluppo per gli include standard.

#include "other.h"

Se si utilizzano le virgolette (” “), il preprocessore dovrebbe cercare in alcune posizioni aggiuntive, solitamente definite dall’utente, il file di intestazione e tornare ai percorsi di inclusione standard solo se non viene trovato in tali posizioni aggiuntive. È comune che questo modulo includa la ricerca nella stessa directory del file contenente la direttiva # include.

NOTA: dovresti controllare la documentazione dell’ambiente di sviluppo che stai utilizzando per qualsiasi implementazione specifica del fornitore della direttiva #include.

HeadersEdit

The C90 standard headers list:

  • <assert.h>
  • <ctype.h>
  • <errno.h>
  • <float.h>
  • <limits.h>
  • <locale.h>
  • <math.h>
  • <setjmp.h>
  • <signal.h>
  • <stdarg.h>
  • <stddef.h>
  • <stdio.h>
  • <stdlib.h>
  • <string.h>
  • <time.h>

Headers added since C90:

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <iso646.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>
  • <wchar.h>
  • <wctype.h>

#pragmaEdit

La direttiva pragma (pragmatic information) fa parte dello standard, ma il significato di qualsiasi pragma dipende da l’implementazione software dello standard utilizzato. La direttiva # pragma fornisce un modo per richiedere un comportamento speciale dal compilatore. Questa direttiva è molto utile per i programmi che sono insolitamente grandi o che hanno bisogno di sfruttare le capacità di un particolare compilatore.

I pragma sono usati all’interno del programma sorgente.

#pragma token(s)
  1. pragma è solitamente seguito da un singolo token, che rappresenta un comando per il compilatore da obbedire. È necessario verificare l’implementazione software dello standard C che si intende utilizzare per un elenco dei token supportati. Non sorprende che l’insieme di comandi che possono apparire nelle direttive #pragma sia diverso per ogni compilatore; dovrai consultare la documentazione del tuo compilatore per vedere quali comandi consente e cosa fanno quei comandi.

Ad esempio una delle direttive del preprocessore più implementate,#pragma once quando viene posizionata all’inizio di un file di intestazione, indica che il file in cui risiede verrà saltato se incluso più volte dal preprocessore.

NOTA: esistono altri metodi per eseguire questa azione che viene comunemente definita come utilizzo di include guards.

#defineEdit

ATTENZIONE: le macro del preprocessore, sebbene allettanti, possono produrre risultati abbastanza inaspettati se non eseguiti correttamente. Tieni sempre presente che le macro sono sostituzioni testuali fatte al tuo codice sorgente prima che venga compilato qualsiasi cosa. Il compilatore non sa nulla delle macro e non le vede mai. Questo può produrre errori oscuri, tra gli altri effetti negativi. Preferisci usare le funzionalità del linguaggio, se ce ne sono equivalenti (Nell’esempio usa const int o enum invece di #defined costanti).

Detto questo, ci sono casi in cui le macro sono molto utili (vedere la macrodebug di seguito per un esempio).

La direttiva #define viene utilizzata per definire valori o macro che vengono utilizzati dal preprocessore per manipolare il codice sorgente del programma prima che venga compilato. Poiché le definizioni del preprocessore vengono sostituite prima che il compilatore agisca sul codice sorgente, eventuali errori introdotti da #define sono difficili da rintracciare.

Per convenzione, i valori definiti usando #define sono denominati in maiuscolo. Anche se così facendo non è un requisito, è considerata una pessima pratica fare altrimenti. Ciò consente di identificare facilmente i valori durante la lettura del codice sorgente.

Oggi, #define viene utilizzato principalmente per gestire le differenze di compilatore e piattaforma. Ad esempio, una definizione potrebbe contenere una costante che è il codice di errore appropriato per una chiamata di sistema. L’uso di # define dovrebbe quindi essere limitato a meno che non sia assolutamente necessario; le istruzioni typedef e le variabili costanti possono spesso eseguire le stesse funzioni in modo più sicuro.

Un’altra caratteristica del comando #define è che può prendere argomenti, rendendolo piuttosto utile come creatore di pseudo-funzioni. Considera il seguente codice:

#define ABSOLUTE_VALUE( x ) ( ((x) < 0) ? -(x) : (x) )...int x = -1;while( ABSOLUTE_VALUE( x ) ) {...}

È generalmente una buona idea usare parentesi extra quando si usano macro complesse. Si noti che nell’esempio precedente, la variabile “x” è sempre all’interno del proprio set di parentesi. In questo modo, verrà valutato per intero, prima di essere confrontato con 0 o moltiplicato per -1. Inoltre, l’intera macro è circondata da parentesi, per evitare che venga contaminata da altro codice. Se non stai attento, corri il rischio di far interpretare erroneamente il tuo codice dal compilatore.

A causa degli effetti collaterali è considerata una pessima idea usare le funzioni macro come descritto sopra.

int x = -10;int y = ABSOLUTE_VALUE( x++ );

Se ABSOLUTE_VALUE() fosse una funzione reale ‘x’ avrebbe ora il valore di ‘-9’, ma poiché era un argomento in una macro è stato espanso due volte e quindi ha un valore di -8.

Esempio:

Per illustrare i pericoli delle macro, considera questa macro ingenua

#define MAX(a,b) a>b?a:b

e il codice

i = MAX(2,3)+5;j = MAX(3,2)+5;

Dai un’occhiata a questo e considera quale potrebbe essere il valore dopo l’esecuzione. Le istruzioni vengono trasformate in

int i = 2>3?2:3+5;int j = 3>2?3:2+5;

Quindi, dopo l’esecuzione i=8 e j=3 invece del risultato atteso di i=j=8! Questo è il motivo per cui sei stato avvertito di usare un set extra di parentesi sopra, ma anche con questi, la strada è piena di pericoli. L’avviso per il lettore può rapidamente realizzare che se a o b contiene le espressioni, la definizione deve parenthesize di ogni a,b nella definizione di macro, come:

#define MAX(a,b) ((a)>(b)?(a):(b))

Questo funziona, fornito a,b non hanno effetti collaterali. Infatti,

i = 2;j = 3;k = MAX(i++, j++);

risulterebbe in k=4, i=3 e j=5. Questo sarebbe molto sorprendente per chiunque si aspetti che MAX () si comporti come una funzione.

Quindi qual è la soluzione corretta? La soluzione non è usare affatto la macro. Una funzione globale, in linea, come questa

inline int max(int a, int b) { return a>b?a:b }

non ha nessuna delle insidie sopra, ma non funzionerà con tutti i tipi.

NOTA: La dichiarazione esplicita inlinenon è realmente necessaria a meno che la definizione non si trovi in un file di intestazione, poiché il compilatore può eseguire funzioni in linea per te (con gcc questo può essere fatto con -finline-functionso -O3). Il compilatore è spesso migliore del programmatore nel prevedere quali funzioni valgono la pena di inlining. Inoltre, le chiamate di funzione non sono molto costose (lo erano in passato).

Il compilatore è in realtà libero di ignorare la parola chiave inline. È solo un suggerimento (tranne che inline è necessario per consentire la definizione di una funzione in un file di intestazione senza generare un messaggio di errore dovuto alla definizione della funzione in più di un’unità di traduzione).

(#,##)

Gli operatori # e ## vengono utilizzati con la macro #define. L’uso di # fa sì che il primo argomento dopo # venga restituito come stringa tra virgolette. Ad esempio, il comando

#define as_string( s ) # s

farà sì che il compilatore trasformi questo comando

puts( as_string( Hello World! ) ) ;

in

puts( "Hello World!" );

Usando ## concatena ciò che è prima del ## con ciò che è dopo. Ad esempio, il comando

#define concatenate( x, y ) x ## y...int xy = 10;...

farà girare il compilatore

printf( "%d", concatenate( x, y ));

in

printf( "%d", xy);

che, ovviamente, mostrerà 10 allo standard output.

È possibile concatenare un argomento macro con un prefisso o suffisso costante per ottenere un identificatore valido come in

#define make_function( name ) int my_ ## name (int foo) {}make_function( bar )

che definirà una funzione chiamata my_bar(). Ma non è possibile integrare un argomento macro in una stringa costante utilizzando l’operatore di concatenazione. Per ottenere un tale effetto, è possibile utilizzare la proprietà ANSI C che due o più costanti di stringa consecutive sono considerate equivalenti a una singola costante di stringa quando incontrate. Usando questa proprietà, si può scrivere

#define eat( what ) puts( "I'm eating " #what " today." )eat( fruit )

che il macro-processore trasformerà in

puts( "I'm eating " "fruit" " today." )

che a sua volta sarà interpretato dal parser C come una singola costante di stringa.

Il seguente trucco può essere usato per trasformare una costante numerica in letterali di stringa

#define num2str(x) str(x)#define str(x) #x#define CONST 23puts(num2str(CONST));

Questo è un po ‘ complicato, dal momento che è espanso in 2 passaggi. Prima num2str(CONST)viene sostituito constr(23), che a sua volta viene sostituito con"23". Questo può essere utile nel seguente esempio:

#ifdef DEBUG#define debug(msg) fputs(__FILE__ ":" num2str(__LINE__) " - " msg, stderr)#else#define debug(msg)#endif

Questo ti darà un bel messaggio di debug incluso il file e la riga in cui è stato emesso il messaggio. Se il DEBUG non è definito, tuttavia, il messaggio di debug scomparirà completamente dal codice. Fai attenzione a non usare questo tipo di costrutto con tutto ciò che ha effetti collaterali, poiché questo può portare a bug, che appaiono e scompaiono a seconda dei parametri di compilazione.

macrosEdit

Le macro non sono controllate dal tipo e quindi non valutano gli argomenti. Inoltre, non obbediscono correttamente all’ambito, ma semplicemente prendono la stringa passata a loro e sostituiscono ogni occorrenza dell’argomento macro nel testo della macro con la stringa effettiva per quel parametro (il codice viene letteralmente copiato nella posizione da cui è stato chiamato).

Un esempio su come usare una macro:

 #include <stdio.h> #define SLICES 8 #define ADD(x) ( (x) / SLICES ) int main(void) { int a = 0, b = 10, c = 6; a = ADD(b + c); printf("%d\n", a); return 0; }

— il risultato di “una” dovrebbe essere “2” (b + c = 16 -> passare per AGGIUNGERE -> 16 / FETTE -> risultato è “2”)

NOTA:
di solito è cattiva pratica per definire le macro per le intestazioni.

Una macro dovrebbe essere definita solo quando non è possibile ottenere lo stesso risultato con una funzione o qualche altro meccanismo. Alcuni compilatori sono in grado di ottimizzare il codice in cui le chiamate a piccole funzioni vengono sostituite con codice in linea, negando ogni possibile vantaggio di velocità.L’utilizzo di typedefs, enums e inline (in C99) è spesso un’opzione migliore.

Una delle poche situazioni in cui le funzioni inline non funzioneranno-quindi sei praticamente costretto a usare macro simili a funzioni-è inizializzare le costanti di tempo di compilazione (inizializzazione statica delle strutture).Ciò accade quando gli argomenti della macro sono letterali che il compilatore può ottimizzare in un altro letterale.

#errorEdit

La direttiva #error interrompe la compilazione. Quando ne viene rilevato uno, lo standard specifica che il compilatore deve emettere una diagnostica contenente i token rimanenti nella direttiva. Questo è principalmente usato per scopi di debug.

I programmatori usano “#error” all’interno di un blocco condizionale, per fermare immediatamente il compilatore quando “#if” o “#ifdef”-all’inizio del blocco-rileva un problema in fase di compilazione.Normalmente il compilatore salta il blocco (e la direttiva “#error” al suo interno) e la compilazione procede.

 #error message

#warningEdit

Molti compilatori supportano una direttiva #warning. Quando ne viene rilevato uno, il compilatore emette una diagnostica contenente i token rimanenti nella direttiva.

 #warning message

#undefEdit

La direttiva #undef non definisce una macro. Non è necessario che l’identificatore sia stato definito in precedenza.

#if,#else,#elif,#endif (condizionali)Modifica

Il comando #if controlla se un’espressione condizionale di controllo viene valutata a zero o diversa da zero e esclude o include rispettivamente un blocco di codice. Ad esempio:

 #if 1 /* This block will be included */ #endif #if 0 /* This block will not be included */ #endif

L’espressione condizionale può contenere qualsiasi operatore C ad eccezione degli operatori di assegnazione, degli operatori di incremento e decremento, dell’operatore address-of e dell’operatore sizeof.

Un operatore univoco utilizzato nella pre-elaborazione e in nessun altro luogo è l’operatore definito. Restituisce 1 se il nome della macro, facoltativamente racchiuso tra parentesi, è attualmente definito; 0 se non.

Il comando #endif termina un blocco iniziato da #if#ifdef, o #ifndef.

Il comando #elif è simile a#if, tranne per il fatto che viene utilizzato per estrarne uno da una serie di blocchi di codice. Ad esempio:

 #if /* some expression */ : : : #elif /* another expression */ : /* imagine many more #elifs here ... */ : #else /* The optional #else block is selected if none of the previous #if or #elif blocks are selected */ : : #endif /* The end of the #if block */

#ifdef,#ifndefEdit

Il comando #ifdef è simile a #if, tranne per il fatto che il blocco di codice che lo segue è selezionato se viene definito un nome di macro. In questo senso,

#ifdef NAME

è equivalente a

#if defined NAME

La #ifndef comando è simile al #ifdef, salvo che la prova è invertito:

#ifndef NAME

è equivalente a

#if !defined NAME

#lineEdit

Questa direttiva per il preprocessore è utilizzato per impostare il nome del file e il numero della riga che segue la direttiva di nuovi valori. Questo è usato per impostare le macro _ _ FILE _ _ e _ _ LINE__.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.