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

10

Diciamo che abbiamo una base di codice che viene utilizzata per molti clienti diversi e abbiamo del codice che è rilevante solo per i clienti di tipo X. È meglio usare le direttive del preprocessore per includere questo codice solo in costumista di tipo X, o da usare se le affermazioni? Per essere più chiaro:

// 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 preprocessore produce un ingombro del codice più piccolo e meno rami (sui compilatori non ottimizzanti)
  • Se le istruzioni risultano con il codice che viene sempre compilato, ad es. se qualcuno commette un errore che danneggerà il codice irrilevante per il progetto su cui lavora, l'errore verrà comunque visualizzato e non corromperà il codice base. 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 ...)

Che cosa è preferibile - quando si parla di una base di codice per molti clienti diversi?

    
posta MByD 24.11.2011 - 18:06
fonte

5 risposte

4

Penso che ci sia un vantaggio nell'usare un #define che non hai menzionato, e questo è il fatto che puoi impostare il valore sulla riga di comando (quindi, impostalo dal tuo script di costruzione one-step).

Oltre a questo, in genere è meglio evitare i macro. Non rispettano alcun ambito e ciò può causare problemi. Solo i compilatori molto stupidi non possono ottimizzare una condizione basata su una costante in fase di compilazione. Non so se questo è un problema per il tuo prodotto (ad esempio potrebbe essere importante mantenere il codice su piattaforme embedded).

    
risposta data 24.11.2011 - 18:27
fonte
12

Come per molte domande, la risposta a questa domanda è dipende . Invece di dire che è 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 è previsto che il codice non sia compilato . Questo può influenzare la struttura del modulo, le dipendenze e può consentire di selezionare i migliori segmenti di codice per gli aspetti delle prestazioni. Nei seguenti casi, è necessario dividere il codice solo con il preprocessore.

  1. Codice multipiattaforma:
    Ad esempio, quando il codice è compilato su piattaforme diverse, quando il codice ha dipendenza da specifici numeri di versione del sistema operativo (o anche dalla versione del compilatore - sebbene questo sia molto raro). Ad esempio, quando si hanno a che fare con le controparti di codice big-endien little-endien, devono essere separati dai preprocessori piuttosto che dalle costanti. Oppure se stai compilando il codice per Windows e Linux e alcune chiamate di sistema sono molto diverse.

  2. Patch sperimentali:
    Un altro caso in cui questo è giustificato è un codice sperimentale che è rischioso o alcuni moduli principali che devono essere omessi che avranno un collegamento significativo o una differenza di prestazioni. Il motivo per cui si vorrebbe disabilitare il codice tramite il preprocessore piuttosto che nascondersi sotto if () è perché potremmo non essere sicuri dei bug introdotti da questo specifico set di modifiche e stanno funzionando in base sperimentale. Se fallisce, non dobbiamo fare altro che disabilitare quel codice in produzione che riscrivere. A volte è ideale utilizzare #if 0 per commentare l'intero codice.

  3. Affrontare le dipendenze:
    Un altro motivo per cui si potrebbe voler generare Ad esempio, se non si desidera supportare le immagini JPEG, è possibile evitare di compilare quel modulo / stub e alla fine la libreria non collegherà (staticamente o dinamicamente) a quel modulo. A volte i pacchetti eseguono ./configure per identificare la disponibilità di tale dipendenza e se le librerie non sono presenti, (o l'utente non vuole abilitare) tale funzionalità è disabilitata automaticamente senza collegamento con quella libreria. Qui è sempre utile se queste direttive sono generate automaticamente.

  4. su licenza:
    Un esempio molto interessante della direttiva del preprocessore è ffmpeg . Ha codec che possono potenzialmente violare i brevetti con il suo uso. Se scarichi il codice sorgente e compili per l'installazione, ti chiede se vuoi o tieni da parte tali cose. Mantenere i codici nascosti in alcune condizioni se può ancora portarti in tribunale!

  5. Codice copia-incolla:
    A.k.a macro. Questo non è un consiglio per l'uso di macro - solo che le macro hanno un modo molto più efficace per applicare l'equivalente di copy-past . Ma usalo con molta cura; e usalo se sai cosa stai facendo. Le costanti, naturalmente, non possono farlo. Ma si può usare anche inline se questo è facile da fare.

Quindi quando usi le costanti? Quasi ovunque.

  1. Flusso di codice più aggiornato:
    In generale, quando si usano le costanti, è quasi indistinguibile dalle variabili regolari e, quindi, è un codice più leggibile. Se scrivi una routine che è di 75 linee - hai 3 o 4 righe dopo ogni 10 righe con #ifdef è MOLTO incapace di leggere . Probabilmente ha una costante primaria governata da #ifdef e la usa in un flusso naturale ogni dove.

  2. Codice ben indentato: Tutta la direttiva per il preprocessore, non funziona mai bene con altrimenti codice ben indentato . Anche se il tuo 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 "#" iniziale doveva essere sempre inserito nella prima colonna.

  3. Configurazione:
    Un altro motivo per cui costanti / o variabili hanno senso è che possono facilmente evolvere dall'essere o collegati a globali o in futuro possono essere estesi per essere derivati dai file di configurazione.

Un'ultima cosa:
Mai USARE direttive preprocessore #ifdef a #endif attraversando lo scope o { ... } . vale a dire inizio di #ifdef o fine di #endif su lati diversi di { ... } . Questo è estremamente cattivo; può essere fonte di confusione, può essere talvolta pericoloso.

Questo ovviamente non è un elenco esaustivo, ma mostra una netta differenza, in cui il metodo è più adatto. Non si tratta in realtà di che è meglio , è sempre più di quale è più naturale da utilizzare in un dato contesto.

    
risposta data 24.11.2011 - 19:10
fonte
4

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

    
risposta data 24.11.2011 - 18:44
fonte
2

Alcuni punti aggiuntivi degni di essere menzionati:

  1. Il compilatore può elaborare alcuni tipi di espressioni costanti che il preprocessore non può. Ad esempio, il preprocessore generalmente non ha modo di valutare sizeof() su tipi non primitivi. Questo potrebbe forzare l'uso di if() in alcuni casi.

  2. Il compilatore ignorerà la maggior parte dei problemi di sintassi nel codice che è saltato in #if (alcuni problemi relativi al preprocessore possono ancora causare problemi) ma insistono sulla correttezza sintattica per il codice saltato con if() . Quindi, se il codice che viene saltato per alcune build ma non altre diventa non valido come conseguenza delle modifiche altrove nel file (ad esempio identificatori rinominati) generalmente squawk su tutte le build se è disabilitato con if() ma non se è disabilitato da% codice%. A seconda del motivo per cui il codice viene saltato, potrebbe essere o non essere una buona cosa.

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

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

  5. La direttiva if può essere utilizzata per controllare quali macro vengono definite.

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

    
risposta data 20.03.2015 - 14:39
fonte
1

Per farla breve: i timori sulle direttive del preprocessore sono fondamentalmente 2, inclusioni multiple e codice forse meno leggibile e una logica più criptica.

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

    
risposta data 24.11.2011 - 18:27
fonte

Leggi altre domande sui tag