È meglio usare la direttiva preprocessore o se l'istruzione (costante)?


10

Diciamo che abbiamo una base di codice che viene utilizzata per molti clienti diversi e che contiene un codice che è rilevante solo per i clienti di tipo X. È meglio usare le direttive del preprocessore per includere questo codice solo nel cliente di tipo X, oppure utilizzare se dichiarazioni? Per essere più chiari:

// some code
#if TYPE_X_COSTUMER  = 1
// do some things
#endif
// rest of the code

o

if(TYPE_X_COSTUMER) {
    // do some things
}

Gli argomenti a cui posso pensare sono:

  • La direttiva del preprocessore comporta un ingombro ridotto del codice e meno rami (su compilatori non ottimizzati)
  • Se le istruzioni risultano con codice che viene sempre compilato, ad esempio se qualcuno commetterà un errore che danneggerà il codice irrilevante per il progetto su cui lavora, l'errore verrà comunque visualizzato e non corromperà la base di codice. Altrimenti non sarà a conoscenza della corruzione.
  • Mi è sempre stato detto di preferire l'uso del processore rispetto all'uso del preprocessore (se questo è un argomento a tutti ...)

Cosa è preferibile quando si parla di una base di codice per molti clienti diversi?


3
Quali sono le probabilità che spedirai una build non creata usando un compilatore ottimizzante? E se succede, l'impatto avrà importanza (soprattutto se messo in contesto con tutte le altre ottimizzazioni che ti mancheranno)?

@delnan: il codice viene utilizzato per molte piattaforme diverse con molti compilatori diversi. E per quanto riguarda l'impatto - potrebbe in alcuni casi.
MByD,

Ti è stato detto di preferire qualcosa? È come forzare qualcuno a mangiare cibo dal KFC mentre non gli piace il pollo.
destra del

@WTP - no non lo ero, sono interessato a saperne di più, quindi prenderei decisioni migliori in futuro.
MByD,

Nel tuo secondo esempio, è TYPE_X_CUSTOMERancora una macro preprocessore?
detenere il

Risposte:


5

Penso che ci sia un vantaggio nell'utilizzare un #define che non hai menzionato, ed è il fatto che puoi impostare il valore sulla riga di comando (quindi, impostarlo dal tuo script di build in un passaggio).

A parte questo, è generalmente meglio evitare le macro. Non rispettano alcun ambito e ciò può causare problemi. Solo compilatori molto stupidi non possono ottimizzare una condizione basata su una costante tempo di compilazione. Non so se questo sia un problema per il tuo prodotto (ad esempio, potrebbe essere importante mantenere il codice piccolo su piattaforme integrate).


In realtà, questo è per i sistemi embedded
MByD

Il problema è che i sistemi embedded di solito usano alcuni compilatori antichi (come ad esempio gcc 2.95).
BЈовић

@VJo - GCC è il caso fortunato :)
MByD

Provalo allora. Se include il codice inutilizzato e ha dimensioni considerevoli, scegli le definizioni.
Tamás Szelei,

Se vuoi essere in grado di impostarlo tramite la riga di comando del compilatore, utilizzerei comunque una costante nel codice stesso e solo #definire il valore assegnato a quella costante.
CodesInChaos

12

Come per molte domande, la risposta di questa domanda è che dipende . Invece di dire quale è meglio, ho piuttosto fornito esempi e obiettivi in ​​cui uno è migliore dell'altro.

Sia il preprocessore che la costante hanno i propri luoghi di utilizzo appropriato.

In caso di pre-processore, il codice viene rimosso prima del tempo di compilazione. Quindi, è più adatto per le situazioni in cui si prevede che il codice non sia compilato . Ciò può influire sulla struttura del modulo, sulle dipendenze e può consentire la selezione dei migliori segmenti di codice per gli aspetti delle prestazioni. Nei seguenti casi, si deve dividere il codice usando solo il preprocessore.

  1. Codice multipiattaforma: ad
    esempio, quando il codice viene compilato su piattaforme diverse, quando il codice dipende da specifici numeri di versione del sistema operativo (o anche dalla versione del compilatore, anche se questo è molto raro). Ad esempio, quando si ha a che fare con controparti big-endien del codice little-endien, devono essere separate da preprocessori piuttosto che da costanti. O se stai compilando un codice per Windows e Linux e alcune chiamate di sistema sono molto diverse.

  2. Patch sperimentali: un
    altro caso in cui ciò è giustificato è un codice sperimentale che è rischioso o alcuni moduli importanti che devono essere omessi e che presentano differenze significative in termini di collegamento o prestazioni. Il motivo per cui si vorrebbe disabilitare il codice tramite preprocessore anziché nascondersi sotto if () è perché potremmo non essere sicuri dei bug introdotti da questo specifico set di modifiche e stiamo eseguendo su base sperimentale. Se fallisce, non dobbiamo fare altro che disabilitare quel codice in produzione che riscrivere. Qualche volta è l'ideale da usare #if 0 per commentare l'intero codice.

  3. Gestire le dipendenze: un
    altro motivo per cui potresti voler generare Ad esempio, se non vuoi supportare immagini JPEG, puoi aiutare a sbarazzarti della compilazione di quel modulo / stub e alla fine la libreria non si collegherà (staticamente o dinamicamente) a quello modulo. A volte i pacchetti vengono eseguiti ./configureper identificare la disponibilità di tale dipendenza e se le librerie non sono presenti (o l'utente non desidera abilitare) tale funzionalità viene disabilitata automaticamente senza collegamento con quella libreria. Qui è sempre utile se queste direttive sono generate automaticamente.

  4. Licenze:
    un esempio molto interessante di direttiva preprocessore è ffmpeg . Ha codec che possono potenzialmente violare i brevetti con il suo uso. Se scarichi il sorgente e compili per l'installazione, ti viene chiesto se desideri o tenere lontano tali contenuti. Mantenere i codici nascosti sotto alcuni se le condizioni possono ancora farti arrivare in tribunale!

  5. Copia e incolla del codice:
    macro Aka. Questo non è un consiglio per un uso eccessivo delle macro - solo che le macro hanno un modo molto più potente per applicare l' equivalente copia-passato . Ma usalo con grande cura; e usalo se sai cosa stai facendo. Le costanti ovviamente, non possono farlo. Ma si possono usare anche le inlinefunzioni se è facile farlo.

Quindi quando usi le costanti?
Quasi ovunque.

  1. Flusso di codice più ordinato:
    in generale, quando si usano le costanti, è quasi indistinguibile dalle variabili regolari e, quindi, è un codice meglio leggibile. Se scrivi una routine di 75 righe, hai 3 o 4 righe ogni 10 righe con #ifdef MOLTO impossibile da leggere . Probabilmente data una costante primaria governata da #ifdef, e usala in un flusso naturale ovunque.

  2. Codice ben rientrato: tutte le direttive del preprocessore, non funzionano mai bene con codice altrimenti ben rientrato . Anche se il compilatore consente il rientro di #def, il preprocessore Pre-ANSI C non ha consentito lo spazio tra l'inizio di una riga e il carattere "#"; il "#" principale doveva essere sempre posizionato nella prima colonna.

  3. Configurazione: un
    altro motivo per cui le costanti / o variabili hanno senso è che possono facilmente evolversi dall'essere collegate a globuli o in futuro possono essere estese per essere derivate dai file di configurazione.

Un'ultima cosa:
non usare mai le direttive del preprocessore #ifdefper #endif attraversare l'ambito o { ... }. vale a dire l'inizio #ifdefo la fine di #endifdiversi lati di { ... }. Questo è estremamente negativo; può essere fonte di confusione, a volte può essere pericoloso.

Questo ovviamente non è un elenco esaustivo, ma mostra una netta differenza, in cui quale metodo è più adatto da usare. Non si tratta davvero di quale sia meglio , è sempre più di quello che è più naturale usare in un determinato contesto.


Grazie per la risposta lunga e dettagliata. Ero a conoscenza della maggior parte delle cose che hai menzionato e sono state omesse dalla domanda poiché la domanda non è se usare affatto le capacità del preprocessore, ma su un tipo specifico di utilizzo. Ma penso che il punto fondamentale che hai sottolineato sia vero.
MByD,

1
"Non UTILIZZARE mai le direttive del preprocessore #ifdef su #endif che attraversano l'ambito o {...}" dipende dall'IDE. Se IDE riconosce blocchi di preprocessore inattivi e li comprime o mostra con colori diversi, non è confuso.
Abyx,

@Abyx è vero che l'IDE può aiutare. Tuttavia, in molti casi in cui le persone si sviluppano sotto la piattaforma * nix (linux ecc.) - la scelta degli editor, ecc. Sono molte (VI, emacs e così via). Quindi qualcun altro potrebbe utilizzare un editor diverso dal tuo.
Dipan Mehta,

4

I tuoi clienti hanno accesso al tuo codice? Se sì, allora il preprocessore potrebbe essere un'opzione migliore. Abbiamo qualcosa di simile e utilizziamo i flag di tempo di compilazione per vari clienti (o caratteristiche specifiche del cliente). Quindi uno script potrebbe estrarre il codice specifico del cliente e lo spediamo. I clienti non sono a conoscenza di altri clienti o di altre caratteristiche specifiche del cliente.


3

Alcuni punti aggiuntivi degni di menzione:

  1. Il compilatore può elaborare alcuni tipi di espressioni costanti che il preprocessore non può. Ad esempio, il preprocessore non avrà in genere modo di valutare sizeof()tipi non primitivi. Ciò può forzare l'uso di if()in alcuni casi.

  2. Il compilatore ignorerà la maggior parte dei problemi di sintassi nel codice che viene ignorato #if(alcuni problemi relativi al preprocessore possono ancora causare problemi) ma insistono sulla correttezza sintattica del codice ignorato if(). Pertanto, se il codice che viene ignorato per alcune build ma non per altre diventa invalido a seguito di modifiche altrove nel file (ad es. Identificatori rinominati), in genere squawk su tutte le build se è disabilitato con if()ma non se è disabilitato da #if. A seconda del motivo per cui il codice viene ignorato, potrebbe essere o meno una buona cosa.

  3. Alcuni compilatori ometteranno il codice non raggiungibile mentre altri no; le dichiarazioni di durata dell'archiviazione statica all'interno di un codice non raggiungibile, tuttavia, inclusi i valori letterali di stringa, possono allocare spazio anche se nessun codice può mai utilizzare lo spazio così allocato.

  4. Le macro possono utilizzare i if()test al loro interno, ma purtroppo non esiste alcun meccanismo per il preprocessore per eseguire qualsiasi logica condizionale all'interno di una macro [si noti che per l'uso all'interno delle macro, l' ? :operatore può spesso essere migliore di if, ma si applicano gli stessi principi].

  5. La #ifdirettiva può essere utilizzata per controllare quali macro vengono definite.

Penso che if()sia spesso più pulito nella maggior parte dei casi ad eccezione di quelli che implicano il prendere decisioni in base a quale compilatore sta usando o quali macro dovrebbero essere definite; alcuni dei problemi di cui sopra potrebbero richiedere l'uso #if, tuttavia, anche nei casi in cui ifsembrerebbe più pulito.


1

Per farla breve: i timori per le direttive del preprocessore sono sostanzialmente 2, inclusioni multiple e forse un codice meno leggibile e una logica più criptic.

Se il tuo progetto è piccolo con quasi nessun grande piano per il futuro, penso che entrambe le opzioni offrano lo stesso rapporto tra pro e contro, ma se il tuo progetto sarà enorme, considera qualcos'altro come la scrittura di un file di intestazione separato se vuoi comunque usare le direttive del preprocessore, ma tieni presente che questo tipo di approccio di solito rende il codice meno leggibile ed assicurati che il nome di quelle costanti significhi qualcosa per la logica aziendale del programma stesso.


Questa è un'ottima base di codice e le definizioni si trovano in alcune intestazioni separate.
MByD,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.