2016-05-17 33 views
7

Ho notato che la maggior parte dei valori predefiniti in libc sono scritti utilizzando le direttive #define. Ad esempio, il parametro whence prende uno in cui, a mia conoscenza, uno enum sarebbe stato migliore. Ci sono molti esempi come questo che dovrebbero ovviamente esistere per una ragione (diversa dai problemi di retro-compatibilità).Scegliere tra enum o definire in C?

Quindi mi chiedo in che caso è meglio utilizzare #define mentre enum è un type-safe alternative more easily discoverable.

Come esempio pratico si consideri un typedef che rappresenta un canale di input/output, come potrebbe essere il caso nel kernel. Il gpio può essere configurato in input o output. Vale la pena utilizzare una direttiva enum qui?

typedef struct gpio { 
    size_t port; 
    size_t bit; 
    enum { DIR_INPUT, DIR_OUTPUT } direction; // or `bool`? 
    bool value; 
} gpio; 

Si noti che il enum può essere implementato in tre diversi modi:

i) tipo:

typedef enum gpio_direction { 
    DIR_OUTPUT 
    DIR_INPUT 
} gpio_direction; 

ii) enum globale

enum gpio_direction { 
    DIR_OUTPUT 
    DIR_INPUT 
} gpio_direction; 

iii) Anonimo enum (come mostrato nel mio esempio).

+1

un problema con 'enum' è che gli enumeratori hanno sempre tipo' int'. Non possono essere usati per valori al di fuori dell'intervallo di 'int'. Un altro potenziale problema nel codice è che la dimensione dell'enumerazione può variare tra i compilatori (o anche le compilazioni, in teoria), il pasticcio con il layout della struttura. –

+1

Personalmente userei sempre un Enum quando il valore non ha importanza come nelle macchine a stati o nei campi conditon. In questo caso non paragono mai ENUM con int. Confronto sempre ENUM con ENUM. Nei casi in cui il valore reale è importante, utilizzerei sempre #defines. PER ESEMPIO. Valori di registro di configurazione hardware o campi Bit. – Schafwolle

+1

Si sta chiedendo che cosa è la migliore pratica nei sistemi embedded. libc è stato scritto da programmatori desktop. Ci sono più cose da considerare per i sistemi embedded, dove è molto più importante essere espliciti con i tipi. Se non stai chiedendo specificamente i sistemi embedded, allora [questo] (http://stackoverflow.com/questions/1674032/static-const-vs-define-vs-enum) è il duplicato. Nel complesso questo argomento è stato discusso all'infinito su SO, è possibile trovare molte informazioni a riguardo. – Lundin

risposta

5

Ci sono molti esempi come questo che dovrebbe esistere ovviamente per un motivo

Una ragione è che non c'è modo portatile per fare riferimento a un C enum dal codice assembly. Il codice di basso livello di solito era (e spesso lo è ancora) scritto in assembly, quindi le costanti usate da quel codice di basso livello verranno specificate da #define anziché da enum.

Un altro motivo è l'idioma if (verbosity & WAKE_THE_NEIGHBOURS) in cui un valore #defined viene utilizzato per specificare una posizione di bit.

Quindi mi chiedo, nel qual caso è meglio usare #define mentre enum è un tipo-sicura alternativa più facilmente individuabile

In tutti gli altri casi avrei (oggi - Utilizzare #define è anche un po 'di tradizione) utilizzare un enum, in modo che if (verbosity == RED) provocherà un avviso (se si utilizza ad esempio gcc con -Wall).

+0

Bene, è anche possibile usare '#ifndef _LANGUAGE_ASM \ n enum {...} \ n #else \ n #define ... \ n # endif' – nowox

+0

Il' if (verbosity & WAKE_THE_NEIGHBOURS) 'è molto buon punto – nowox

+0

'if (verbosity & WAKE_THE_NEIGHBOURS)' potrebbe anche essere usato con un enum però ... tranne che otterrete un tipo firmato."# Define" scritto in modo non corretto farà lo stesso, quindi non c'è alcuna differenza reale, a meno che il '# define' sia scritto con cura. – Lundin

3

Mentre è probabile che si debbano utilizzare altre soluzioni laddove sono disponibili, esistono comunque situazioni in cui sono richieste macro. Alcuni sono motivi storici, ma ci sono anche ragioni valide ancora oggi.

Per esempio si menziona l'argomento whence-fseek (cioè SEEK_SET, SEEK_CUR e `SEEK_END).Questi sono (per ragioni storiche) specificato di essere le macro nello standard - in modo che l'implementor ha definirli come macro di essere pienamente compatibile, qualche programmatore male potrebbe scrivere (e la colpa la libreria per le conseguenze):

#include <stdio.h> 

#ifndef SEEK_CUR 
int evil = *(int*)0; 
#endif 

ma, per quanto posso vedere non c'è nessun motivo per cui non potevano scrivere:

enum __WHENCE_T { 
    __SEEK_CUR, 
    __SEEK_END, 
    __SEEK_SET 
}; 

#define SEEK_CUR __SEEK_CUR 
#define SEEK_END __SEEK_END 
#define SEEK_SET __SEEK_SET 

Un'altra ragione storica è che il compilatore avrebbe potuto produrre codice più efficiente quando si utilizza le macro. Il codice che è stato scritto in quel momento potrebbe ancora essere in giro e contiene dei costrutti che funzionavano meglio allora (e funziona ancora abbastanza bene oggi).

I motivi moderni sono se è necessario utilizzare i valori al di fuori del compilatore C. Ad esempio, come detto, se si desidera utilizzare il valore nel codice assembler (o in un'altra lingua). Tutto quello che devi fare è assicurarti che l'intestazione non si espanda a qualcosa (che contiene codice C) a meno che non sia compilato con un compilatore C. Per esempio:

#define NUMBER 42 

#ifdef __STDC__ 
extern int variable; 

int function(void); 
#endif 

se incluso dal assembler macro NUMBER sarebbe ancora espandibile (ed espandere per la stessa cosa per i programmi C).