2009-11-23 10 views
19

Questa domanda può sembrare piuttosto di base, ma provenendo da un background ingegneristico (non informatica), non ero sicuro di quali fossero i frammenti di "#" in qualche codice C++.Perché uno dovrebbe preoccuparsi delle direttive del preprocessore?

Una ricerca rapida mi ha portato alla pagina di esercitazione concisa e ben spiegata cplusplus sulle direttive del preprocessore.

Ma perché preoccuparsi del concetto delle direttive del preprocessore? Non è possibile scrivere codice equivalente che può assegnare valori alle costanti, definire subroutine/funzioni/macro e gestire errori?

Suppongo che alla fine voglio sapere quando è buona pratica utilizzare tali direttive per il preprocessore e quando non lo è.

+11

Questo mi diverte. :) – epochwolf

+0

Molti buoni punti in molte risposte ... mi ci vorrà un po 'di tempo per digerire tutto questo! – Zaid

+1

Devo wiki della community questa domanda? – Zaid

risposta

25

Si utilizzano le direttive del preprocessore quando è necessario eseguire operazioni diverse dall'ambito dell'applicazione reale. Ad esempio, vedrai che la pre-elaborazione viene eseguita per includere o meno il codice basato sull'architettura per cui è stato costruito l'eseguibile. Ad esempio:

#ifdef _WIN32 // _WIN32 is defined by Windows 32 compilers 
#include <windows.h> 
#else 
#include <unistd.h> 
#endif 

direttive del preprocessore sono utilizzati anche per la guardia include in modo che le classi/funzioni ecc non sono definite più di una volta.

#ifndef MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_ 
#define MY_CLASS_6C1147A1_BE88_46d8_A713_64973C9A2DB7_H_ 
    class MyClass { 
    .... 
    }; 
#endif 

Un altro uso è per incorporare il controllo delle versioni all'interno di codice e librerie.

Nella Makefile hai qualcosa sulla falsariga di:

-D MY_APP_VERSION=4.5.1 

Mentre nel codice hai

cout << "My application name version: " << MY_APP_VERSION << endl; 
+0

Ciò è particolarmente importante nei casi in cui esistono funzioni che si trovano su una piattaforma ma non un'altra, come le intrinseche del compilatore, che racchiudono gli opcode SSE sugli xcode e xX su PPC, ecc. In questo caso non esiste un'alternativa valida all'utilizzo #ifdef, perché i template proveranno a compilare il codice usando le funzioni inesistenti, anche se solo per poi gettarle via. – Crashworks

+0

Un nitpick: WIN32 è definito perché sono le impostazioni di progetto predefinite. Il compilatore stesso definisce _WIN32 (credo che MinGW o altri compilatori basati su Windows lo definiscano), e opzionalmente _WIN64, per descrivere la piattaforma. Definisce anche _MSC_VER come la stessa versione del compilatore. – Tom

+0

Hah .. scusa ... un altro pisolino. '__MY_HEADER_H__' non funziona, poiché' __ [A-Z] 'è tecnicamente riservato per l'implementazione. La maggior parte dei compilatori definisce * molte * impostazioni di configurazione, quindi non è improbabile che alla fine si imbatta in uno strano errore relativo alla macro da questo. – Tom

7

Perché direttive del preprocessore vengono eseguiti a accumulo tempo, mentre il codice si scrive otterrà eseguito al run tempo. Quindi le direttive del preprocessore ti danno la possibilità di modificare il tuo codice sorgente in modo programmatico.

Si noti che il preprocessore C è un meccanismo piuttosto rozzo per questo tipo di cose; Il sistema di template di C++ fornisce un framework molto più potente per la costruzione del codice in fase di compilazione. Altre lingue hanno caratteristiche di metaprogrammazione ancora più potenti (ad esempio, il sistema macro di Lisp).

+3

Per eseguire il nitpick un po ': le direttive del preprocessore vengono eseguite prima della compilazione. –

+0

@Nemanja: vero. Forse avrei dovuto dire "tempo di generazione eseguibile". La differenza è principalmente un dettaglio di implementazione del toolchain del compilatore, comunque. –

+1

Beh, no, deve essere * pre * elaborato prima che il compilatore guardi il codice. –

9

Risposta 1: codice condizionale che deve variare a seconda del tipo di computer su cui lavora.

Risposta 2: abilitazione e disabilitazione delle estensioni del linguaggio e delle funzionalità di compatibilità visualizzate in fase di compilazione.

Il preprocessore proveniva da C, dove c'erano molte cose che non si potevano esprimere. Un buon codice C++ trova meno motivi per usarlo rispetto al codice C, ma purtroppo non è del tutto inutile.

+2

Inoltre, dato che C++ ha "in linea" non dovresti usare # defini macro in C++ per provare la velocità. Vedi anche http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Preprocessor_Macros –

+0

@Harold: dove è inline menzionato in questa risposta? –

5

E 'utilizzato più di frequente per due cose che saranno più difficili da organizzare senza di essa:

  1. Include guards.
  2. Diverse sezioni di codice per diverse piattaforme.
1

No, in realtà non è possibile ottenere senza il preprocessore in tutti i casi. Uno dei miei macro preferiti è

#define IFDEBUG if(DEBUG==1) 
//usage: 
IFDEBUG{ 
    printf("Dump of stuff:.,,,"); 
}else{ 
    //..do release stuff 
} 

Senza macro, avrei (forse molto) spazio sprecato nel eseguibile finale

E inoltre è necessario rendersi conto, C/C++ non ha alcun tipo di tipo di pacchetto require o altro sistema simile. Quindi, senza il preprocessore, non c'è modo di impedire la duplicazione del codice. (i file di intestazione non possono essere inclusi)

+4

Questo lascerà un test "if" nell'eseguibile finale: vuoi #ifdef guards attorno al codice DEBUG-only e poi compilare con o senza -DDEBUG per controllare se il printf è nel codice del programma. –

+0

La tua corretta, ho dato un cattivo esempio. Ma vorrei pregare che stavo usando un compilatore abbastanza competente per ottimizzare via 'if (0) {..}' – Earlz

+0

Bad esempio, IMO; perché non semplicemente "#if DEBUG definito"? – Clifford

0

E 'un sostituto migliore del nulla per ottenere alcune funzionalità di riflessione fuori C++.

Molto utile per generare variabili e stringhe con gli stessi nomi.

+0

Il C++ 0x non ha il supporto per la riflessione (ish) in esso con i vettori e tutto il jazz? – Earlz

+0

No. E cosa intendi per vettori? std :: vector <> è in circolazione da 15 anni circa. – Macke

1

Ma perché preoccuparsi del concetto delle direttive del preprocessore? Non è possibile scrivere codice equivalente che può assegnare valori alle costanti, definire subroutine/funzioni/macro e gestire errori?

Il suo uso è molto basso in C++, le caratteristiche del linguaggio sono state create per evitare problemi associati al preprocessore.

Immagino che alla fine voglio sapere quando è buona pratica utilizzare tali direttive per il preprocessore e quando non lo è.

In generale, le fonti C++ sono spesso considerate come una cattiva pratica, in particolare quando c'è un mezzo per farlo utilizzando altre funzionalità linguistiche. È richiesto per alcune cose (ad esempio programmi basati su piattaforma/build e programmi generativi). In breve, di solito c'è una sostituzione che si adatta bene. (come costante definire come enum o modelli in linea anziché macro). Se ti trovi a utilizzarne uno e non sei sicuro, basta chiedere/cercare se esiste un modo migliore per dichiarare this_code_snippet in C++, senza il preprocessore.

0

Il preprocessore C esegue un numero di attività, alcune ma non tutte hanno alternative migliori in C++. Dove C++ ha un'alternativa migliore usarlo. Quelle alternative includono modelli, inlining e variabili const (un ossimoro, ma è quello che viene chiamato dallo standard) al posto delle macro #define.

Tuttavia ci sono poche cose di cui non vorresti fare a meno o semplicemente non puoi fare a meno; #include per esempio è essenziale, e quando si codifica per più piattaforme o configurazioni, la compilazione condizionale rimane utile (anche se dovrebbe essere usata con parsimonia in tutti i casi).

In alcuni casi, le estensioni specifiche del compilatore controllate tramite #pragma potrebbero essere inevitabili.

+0

Sì ... Non riesco a immaginare come '# include' e' # pragma' sia scritto in qualsiasi altro modo – Zaid

+0

Bene puoi evitare #include copiando tutto il contenuto in un unico file! ;) Ecco perché è una buona cosa non è una brutta cosa. #pragma rimane negativo anche quando devi ricorrere ad esso - per ragioni di portabilità. – Clifford

+1

... o creando un costrutto di linguaggio appropriato per la gestione di interfacce con altre unità di compilazione, piuttosto che basarsi su questo hack 40 anni di preprocessore. (Non dimentichiamo il '# ifdef' in più che ognuno deve racchiudere tutto nel proprio file incluso per ottenere il comportamento desiderato corretto di un'interfaccia reale). –

6

La preelaborazione si verifica prima che il codice sia compilato. E 'opportuno in casi come i seguenti

#ifdef WIN32 
#include <something.h> 
#elseif LINUX 
#include <somethingelse.h> 
#endif 

file header Ovviamente tra cui si desidera fare in fase di compilazione non runtime. Non possiamo farlo con le variabili.

D'altra parte.Con C++ è buona pratica e molto incoraggiato per sostituire espressioni costanti come il seguente esempio

#define PI 3.141592654 
with 
const double PI=3.141592654; 

Il motivo è che si ottiene una corretta fusione di caratteri e la manipolazione tipo di dati.

anche

#define MAX(x,y) (x) > (y) ? (x) : (y) 

non è molto bello perché si può scrivere

int i = 5 
int max = MAX(i++, 6); 

Il preprocessore sostituirebbe quello con:

int max = (i++) > (6) ? (i++) : (6); 

che non è chiaramente intenzione di dare la destinato risultato.

Invece, MAX dovrebbe essere una funzione (non una macro). Se è una funzione, può anche fornire il tipo nel parametro.

Ho visto il preprocessore utilizzato per tutti i tipi di cose interessanti. Come la dichiarazione di parole chiave della lingua. In questo caso può essere di aiuto nella leggibilità.

In breve, utilizzare il preprocessore per le cose che devono accadere in un tipo di compilazione come le direttive condizionali incluse. Evita di usarlo per le costanti. Evita le macro e usa invece le funzioni dove possibile.

+0

Improbabile che io debba mai utilizzare la compatibilità multipiattaforma, ma ottengo l'immagine. – Zaid

+0

Si noti che max() è già presente nella libreria standard C++, tutti ben strutturati e roba del genere. – ceo

2

In generale, le direttive del preprocessore non devono essere utilizzate. Purtroppo, a volte è necessario in C e C++.

C originariamente definito la lingua in modo tale che davvero non si poteva fare nulla di serio con esso senza utilizzare il preprocessore. Il linguaggio non aveva altro supporto per la creazione di programmi modulari, costanti, codice in linea o programmazione generica.

C++ elimina la maggior parte di questi problemi ma la struttura è ancora lì, quindi viene ancora utilizzata. (È interessante notare che non è quello di modularizzazione. Siamo ancora bloccati con #include),

Se si desidera confrontare un linguaggio creato a un livello simile di astrazione per attività simili che non hanno un preprocessore, andare a prendere un guarda Ada.

2

Molti linguaggi di programmazione hanno meta-programmazione, dove potrete scrivere il codice per il compilatore da seguire, piuttosto che l'ambiente di runtime.

Ad esempio, in C++, abbiamo modelli che ci consentono di istruire il compilatore a generare un determinato codice in base a un tipo o anche a una costante in fase di compilazione. Lisp è forse l'esempio più famoso di una lingua con funzionalità avanzate di meta-programmazione.

Le direttive/macro del preprocessore C sono solo un'altra forma di "meta-programmazione", sebbene una forma relativamente più grezza di quella disponibile in altre lingue. Le direttive del preprocessore istruiscono il compilatore a fare certe cose in fase di compilazione, tali da ignorare determinati codici su determinate piattaforme o trovare e sostituire una stringa nel codice con un'altra stringa. Questo sarebbe impossibile da eseguire in runtime, dopo che il codice è già stato compilato.

Quindi, in sostanza, il preprocessore C è una prima forma di "meta-programmazione" o programmazione di compilatori.

+0

Hmm ... mai sentito parlare di meta-programmazione prima. Mi piace il modo in cui hai generalizzato l'uso delle direttive del preprocessore. – Zaid

1

Un po 'di storia qui: C++ è stato sviluppato da C, che aveva bisogno del preprocessore molto più del C++. Ad esempio, per definire una costante in C++, dovresti scrivere qualcosa come const int foo = 4;, ad esempio, invece di #define FOO 4 che è l'equivalente C approssimativo. Sfortunatamente, troppe persone hanno portato le loro abitudini al preprocessore da C a C++.

Ci sono diversi usi ragionevoli del preprocessore in C++. L'utilizzo di #include per i file di intestazione è praticamente necessario. È anche utile per la compilazione condizionale, inclusa l'intestazione include le guardie, quindi è possibile aggiungere un'intestazione a #include diverse volte (ad esempio in intestazioni diverse) e elaborarla solo una volta. L'istruzione assert è in realtà una macro del preprocessore e ci sono alcuni usi simili.

Oltre a quelli, ci sono pochi usi legittimi in C++.

+0

'# include', e l'associato' #ifdef ... # define' sono richiesti solo perché né C né C++ hanno ritenuto opportuno creare una funzionalità di interfaccia appropriata, come si può trovare in Pascal, Modula-2, Ada e praticamente ogni lingua moderna. Questo è solo un hack preprocessore a cui tutti si sono abituati. –

Problemi correlati