2009-10-28 7 views
11

In una precedente question quello che pensavo fosse una buona risposta stata bocciata per l'uso suggerito di macroQuando si dovrebbero usare macro invece di funzioni in linea?

#define radian2degree(a) (a * 57.295779513082) 
#define degree2radian(a) (a * 0.017453292519) 

invece di funzioni inline. Si prega di scusare la domanda newbie, ma in questo caso cosa c'è di così male in macro?

+9

In questo caso, degree2radian (45 + a/2.0) non fa ciò che pensavi. Parlate sempre di ogni argomento dell'espansione - beh, quasi sempre; le principali eccezioni sono quando fai cose come incollare o stringere i token. –

+3

quello che dice Jonathan - una cosa peggiore di vedere una definizione di macro che è un rompicoglioni da leggere a causa di tutti i dannati paren, sta arrivando attraverso una definizione di macro che non ha tutti quei dannati paren. –

+3

C'è anche la convenzione ALLCAPS per le macro, che almeno le inserisce nel proprio pseudo-namespace. –

risposta

6

C'è un paio di cose strettamente malvagie sulle macro.

Sono in elaborazione di testo e non hanno ambito. Se si utilizza #define foo 1, qualsiasi utilizzo successivo di foo come identificatore avrà esito negativo. Ciò può portare a errori di compilazione e errori di runtime difficili da trovare.

Non accettano argomenti nel senso normale. È possibile scrivere una funzione che richiederà due valori int e restituirà il valore massimo, poiché gli argomenti verranno valutati una volta e i valori utilizzati successivamente. Non è possibile scrivere una macro per farlo, poiché valuterà almeno un argomento due volte e fallirà con qualcosa come max(x++, --y).

Anche le insidie ​​sono comuni. È difficile avere più affermazioni al loro interno e richiedono parecchie parentesi forse superflue.

Nel tuo caso, è necessario parentesi:

#define radian2degree(a) (a * 57.295779513082) 

necessità di essere

#define radian2degree(a) ((a) * 57.295779513082) 

e sei ancora calpestare chiunque scrive una funzione radian2degree in qualche ambito interno, sicuro che quella la definizione funzionerà nel suo stesso ambito.

+1

Trovo sempre che le affermazioni di "caratteristica 'x' è il male non usarlo mai" sembrano implicare che i programmatori siano immensamente stupidi. Consigliamo che le macro vengano denominate in un modo che si identifica come macro quando utilizzate: RADIAN2DEGREEmac o simile. –

+5

"La caratteristica X è il male" non significa "non usare la caratteristica X", e non ho proprio detto che le macro erano malvagie in se stesse. La convenzione all-caps è preziosa, ma non risolve tutti i problemi. Lo stesso con la rigorosa parentesi e gli altri trucchi. Ti consiglio di evitare le macro di tipo funzionale, se possibile, perché di solito ci sono modi migliori per fare tutto ciò che stai cercando di fare. –

+1

@David Buona risposta ma non ha risposto alla domanda: "Quando dovresti usare le macro invece delle funzioni inline?" Sembra che tu abbia risposto al contrario: perché non dovresti usare macro. Ma quando è "meglio" usare una macro invece di una funzione? – styfle

1

se possibile, utilizzare sempre la funzione inline. Questi sono tipicamente e non possono essere facilmente ridefiniti.

definisce può essere ridefinito indefinito, e non vi è alcun tipo di controllo.

+1

Lo svantaggio in C (al contrario di C++) è che non tutti i compilatori necessariamente li supportano o che sono predefiniti in una modalità in cui sono supportati. Sono stati aggiunti in C99, ma alcuni compilatori (MSVC, ad esempio) non sono conformi a C99. Non so se MSVC supporta questo bit dello standard, perché potrebbe anche avere supporto in C++ in agguato. Tuttavia, non ha altre funzioni C99 che sarebbe molto bello avere. –

+2

Un compilatore intelligente ignorerà la direttiva 'inline' e in linea o non come indica la singola chiamata. –

+0

@David +1, in linea è una raccomandazione pura. – LB40

7

Per questa specifica macro, se lo uso come segue:

int x=1; 
x = radian2degree(x); 
float y=1; 
y = radian2degree(y); 

ci sarebbe nessun tipo di controllo, e x, y conterrà valori diversi.

Inoltre, il seguente codice

float x=1, y=2; 
float z = radian2degree(x+y); 

non farà quello che pensi, dal momento che si tradurrà in

float z = x+y*0.017453292519; 

invece di

float z = (x+y)+0.017453292519; 

che è il risultato atteso.

Questi sono solo alcuni esempi per il comportamento scorretto e le macro di uso improprio potrebbero avere.

Modifica

si può vedere discussioni aggiuntive su questo here

+3

Buona risposta. Vale anche la pena menzionare l'inquinamento dello spazio dei nomi (ad esempio, non posso più dichiarare una variabile locale o un campo struct chiamato 'radian2degree'). –

+0

Non capisco la tua prima lamentela. x non verrà trasformato in virgola mobile dalla macro, ma dalla moltiplicazione. –

+1

il mio punto era che entrambi gli usi sono validi e verranno compilati, ma non vedrai nessuna indicazione per questo. la tua macro _should_ accetta solo il numero di punti mobili, ma accetta qualsiasi cosa. – Amirshk

0

macro sono il male perché si può finire per passare più di una variabile o uno scalare ad esso e questo potrebbe risolvere in un comportamento indesiderato (definire una macro massima per determinare il massimo tra a e b ma passare a ++ e b ++ alla macro e vedere cosa succede).

0

Se la funzione verrà comunque allineata, non vi è alcuna differenza di prestazioni tra una funzione e una macro. Tuttavia, ci sono diverse differenze di usabilità tra una funzione e una macro, tutte favorevoli all'utilizzo di una funzione.

Se si crea correttamente la macro, non ci sono problemi. Ma se usi una funzione, il compilatore lo farà correttamente per te ogni volta. Pertanto, l'utilizzo di una funzione rende più difficile la scrittura di codice errato.

2

Le macro vengono spesso utilizzate in modo abusivo e si possono facilmente commettere errori come illustrato nell'esempio. Prendere l'espressione radian2degree (1 + 1):

  • con la macro si espanderà a 1 + 1 * 57.29 ... = 58.29 ...
  • con una funzione che sarà quello che vuoi che essere, ovvero (1 + 1) * 57.29 ... = ...

Più in generale, le macro sono il male perché assomigliare funzioni in modo da indurti a utilizzarli come funzioni ma hanno regole sottili della propria. In questo caso, il modo corretto sarebbe quello di scrivere, sarebbe (notare il paranthesis intorno a):

#define radian2degree(a) ((a) * 57.295779513082) 

Ma si dovrebbero usare per le funzioni inline. Vedere questi collegamenti dal C++ FAQ Lite per ulteriori esempi di macro malvagi e le loro sottigliezze:

+1

+1 ma a) i macro non sono "cattivi in ​​generale". Possono essere maltrattati, ma non sono la radice di tutti i mali, più di quanto non siano i 'goto's. b) Perché collegarsi alle FAQ C++ per una domanda C? Non c'è nessun tag C++ e nessun C++ - informazioni specifiche nella domanda. –

+0

Hai ragione riguardo al "male in generale", era troppo duro e generale. L'ho modificato Correlato a b), per quanto vedo tutte le informazioni nei collegamenti alle domande frequenti in C++ che ho dato si applica anche a C e penso che i problemi/sottigliezze delle macro siano chiaramente spiegati lì così li vedo come link preziosi per questa domanda . Quindi perché non collegarsi alle FAQ del C++? –

1

preprocessore del compilatore è una cosa finnicky e quindi un terribile candidato per trucchi intelligenti. Come altri hanno sottolineato, è facile per il compilatore fraintendere la tua intenzione con la macro, ed è facile per te fraintendere ciò che la macro in realtà farà, ma soprattutto, non puoi entrare in macro nel debugger!

8

La maggior parte delle altre risposte spiega perché le macro sono malvagie, incluso il modo in cui il tuo esempio ha un difetto di macro uso comune. Ecco la presa di Stroustrup: http://www.research.att.com/~bs/bs_faq2.html#macro

Ma la tua domanda è stata chiedendo a quali macro sono ancora valide.Ci sono alcune cose in cui le macro sono migliori di funzioni inline, ed è lì che si sta facendo le cose che semplicemente non può essere fatto con le funzioni inline, come ad esempio:

  • gettone incollare
  • che fare con i numeri di linea o tale (come per la creazione di messaggi di errore in assert())
  • che fare con le cose che non sono espressioni (ad esempio il numero di implementazioni di offsetof() uso utilizzando un nome di tipo per creare un'operazione di fusione)
  • la macro per ottenere un conteggio di elementi dell'array (non possono farlo con una funzione, come il nome dell'array decaduta anche su un puntatore e asily)
  • creare 'tipo polimorfici' cose di funzione simile a C in cui i modelli non sono disponibili

Ma con un linguaggio che ha le funzioni inline, gli usi più comuni di macro non dovrebbe essere necessario. Sono anche riluttante a usare macro quando ho a che fare con un compilatore C che non supporta le funzioni inline. E cerco di non usarli per creare funzioni di tipo agnostico se possibile (creando invece diverse funzioni con un indicatore di tipo come parte del nome).

Ho anche spostato l'utilizzo di enumerazioni per le costanti numeriche denominate anziché #define.

+0

Un altro caso utile per macro è per l'inizializzazione di oggetti nel segmento dati: int global = radian2degree (2 * PI); – calandoa

+0

Sì, raramente mi occupo io stesso di numeri in virgola mobile, quindi di solito uso le enumerazioni ora anziché le macro per le costanti numeriche con nome. Ma chiaramente non si può fare per i float come 'PI', quindi sarebbe una macro. –

+0

Sui compilatori con estensioni gcc, le macro, a differenza delle funzioni inline, possono verificare se i loro argomenti possono essere valutati come costanti. Se si desidera avere una "funzione" come 'reverse8bits (x)' che calcola un valore byte invertito, potrebbe essere meglio usare '((x & 128) >> 7) | ((x & 64) >> 5) ...) 'quando' x' è una costante (nel qual caso l'espressione brutta produce una costante) ma chiama una routine di libreria quando 'x' è una variabile. Vorrei che 'C' consentisse il sovraccarico delle funzioni inline in base al fatto che gli argomenti fossero costanti in fase di compilazione, poiché sarebbe più pulito, e ... – supercat