Ho sempre visto esempi e casi in cui l’utilizzo di una macro è migliore rispetto all’utilizzo della funzione.
Qualcuno potrebbe spiegarmi con un esempio lo svantaggio di una macro rispetto ad una funzione?
Le macro sono soggette a errori perché si basano sulla sostituzione testuale e non eseguono il controllo dei tipi. Ad esempio, questa macro:
#define square(a) a*a
funziona bene se usato con un numero intero:
square(5) --> 5*5 --> 25
ma fa cose molto strane se usato con espressioni:
square(1+2) --> 1+2*1+2 --> 1+2+2 --> 5 square(x++) --> x++*x++ --> increments x twice
Mettere le parentesi attorno agli argomenti aiuta ma non elimina completamente questi problemi.
Quando le macro contengono più istruzioni, puoi avere problemi con i costrutti del stream di controllo:
#define swap(x,y) t=x; x=y; y=t; if(x if(x if(x
La solita strategia per risolvere questo problema consiste nel mettere le istruzioni all'interno di un ciclo "do {...} while (0)".
Se hai due strutture che contengono un campo con lo stesso nome ma semantica diversa, la stessa macro potrebbe funzionare su entrambi, con risultati strani:
struct shirt { int numButtons; }; struct webpage { int numButtons; }; #define num_button_holes(shirt) ((shirt).numButtons * 4) struct webpage page; page.numButtons = 2; num_button_holes(page) -> 8
Infine, le macro possono essere difficili da eseguire il debug, producendo strani errori di syntax o errori di runtime che è necessario espandere per comprendere (ad esempio con gcc -E), perché i debugger non possono passare attraverso le macro, come in questo esempio:
#define print(x, y) printf(xy) /* accidentally forgot comma */ print("foo %s", "bar") /* prints "foo %sbar" */
Le funzioni e le costanti integrate aiutano a evitare molti di questi problemi con le macro, ma non sono sempre applicabili. Dove le macro vengono utilizzate deliberatamente per specificare il comportamento polimorfico, il polimorfismo non intenzionale può essere difficile da evitare. C ++ ha una serie di funzioni come i modelli per aiutare a creare costrutti polimorfici complessi in modo tipicamente sicuro senza l'uso di macro; vedere il linguaggio di programmazione C ++ di Stroustrup per i dettagli.
Gli effetti collaterali sono grandi. Ecco un caso tipico:
#define min(a,b) (a < b ? a : b) min(x++,y)
si espande a:
(x++ < y ? x++ : y)
x
viene incrementato due volte nella stessa dichiarazione. (e comportamento non definito)
Anche scrivere macro multi-linea è un problema:
#define foo(a,b,c) \ a += 10; \ b += 10; \ c += 10;
Richiedono un \
alla fine di ogni riga.
Le macro non possono "restituire" nulla a meno che non si faccia una singola espressione:
int foo(int *a, int *b){ side_effect0(); side_effect1(); return a[0] + b[0]; }
Non è ansible farlo in una macro a meno che non si usi la dichiarazione di espressione di GCC. (MODIFICA: puoi usare un operatore virgola però ... trascurato che ... Ma potrebbe essere ancora meno leggibile.)
Ordine delle operazioni: (per gentile concessione di @ouah)
#define min(a,b) (a < b ? a : b) min(x & 0xFF, 42)
si espande a:
(x & 0xFF < 42 ? x & 0xFF : 42)
Ma &
ha una precedenza inferiore a <
. Quindi 0xFF < 42
viene valutato per primo.
Funzioni macro :
Caratteristiche funzionali :
#define SQUARE(x) ((x)*(x)) int main() { int x = 2; int y = SQUARE(x++); // Undefined behavior even though it doesn't look // like it here return 0; }
mentre:
int square(int x) { return x * x; } int main() { int x = 2; int y = square(x++); // fine return 0; }
struct foo { int bar; }; #define GET_BAR(f) ((f)->bar) int main() { struct foo f; int a = GET_BAR(&f); // fine int b = GET_BAR(&a); // error, but the message won't make much sense unless you // know what the macro does return 0; }
Rispetto a:
struct foo { int bar; }; int get_bar(struct foo *f) { return f->bar; } int main() { struct foo f; int a = get_bar(&f); // fine int b = get_bar(&a); // error, but compiler complains about passing int* where // struct foo* should be given return 0; }
Non viene ripetuto alcun tipo di controllo dei parametri e del codice che può portare a un aumento di codice. La syntax della macro può anche portare a un numero qualsiasi di casi di edge strani in cui i punti e virgola o l’ordine di precedenza possono interferire. Ecco un link che mostra alcuni macro cattivi
In caso di dubbio, utilizzare le funzioni (o funzioni inline).
Tuttavia, le risposte qui spiegano principalmente i problemi con le macro, invece di avere una visione semplice del fatto che le macro sono malvagie perché sono possibili incidenti sciocchi.
Puoi essere consapevole delle insidie e imparare a evitarli. Quindi utilizzare le macro solo quando c’è una buona ragione per.
Esistono alcuni casi eccezionali in cui sono presenti vantaggi nell’utilizzo delle macro, tra cui:
va_args
a diverse funzioni invece di utilizzare va_args
di va_args
__FILE__
, __LINE__
, __func__
). controlla le condizioni pre / post, assert
in caso di fallimento o anche le assert
statiche, quindi il codice non verrà compilato in caso di uso improprio (utile soprattutto per le build di debug). struct
siano presenti prima del casting func(FOO, "FOO");
, potresti definire una macro che espande la stringa per te func_wrapper(FOO);
inline
possono essere un’opzione) . Certo, alcuni di questi si basano su estensioni del compilatore che non sono standard C. Il che significa che si può finire con un codice meno portabile, o ifdef
, in modo che vengano sfruttati solo quando il compilatore supporta.
Notando questo dato che è una delle cause più comuni di errori nelle macro (passando in x++
per esempio, dove una macro può incrementare più volte) .
è ansible scrivere macro che evitino effetti collaterali con più istanze di argomenti.
Se ti piace avere una macro square
che funziona con vari tipi e avere il supporto per C11, puoi farlo …
inline float _square_fl(float a) { return a * a; } inline double _square_dbl(float a) { return a * a; } inline int _square_i(int a) { return a * a; } inline unsigned int _square_ui(unsigned int a) { return a * a; } inline short _square_s(short a) { return a * a; } inline unsigned short _square_us(unsigned short a) { return a * a; } /* ... long, char ... etc */ #define square(a) \ _Generic((a), \ float: _square_fl(a), \ double: _square_dbl(a), \ int: _square_i(a), \ unsigned int: _square_ui(a), \ short: _square_s(a), \ unsigned short: _square_us(a))
Questa è un’estensione del compilatore supportata da GCC, Clang, EKOPath e Intel C ++ (ma non MSVC) ;
#define square(a_) __extension__ ({ \ typeof(a_) a = (a_); \ (a * a); })
Quindi lo svantaggio con i macro è che devi sapere come usarli all’inizio e che non sono supportati come ampiamente.
Un vantaggio è, in questo caso, è ansible utilizzare la stessa funzione square
per molti tipi diversi.
uno svantaggio delle macro è che i debugger leggono il codice sorgente, che non ha macro espanse, quindi eseguire un debugger in una macro non è necessariamente utile. Inutile dire che non è ansible impostare un punto di interruzione all’interno di una macro come è ansible con le funzioni.
Aggiungendo a questa risposta ..
Le macro sono sostituite direttamente nel programma dal preprocessore (poiché sono fondamentalmente direttive del preprocessore). Quindi usano inevitabilmente più spazio di memoria di una rispettiva funzione. D’altra parte, una funzione richiede più tempo per essere chiamata e per restituire risultati, e questo overhead può essere evitato usando macro.
Anche le macro hanno alcuni strumenti speciali che possono aiutare con la portabilità del programma su piattaforms diverse.
Ai macro non è necessario assegnare un tipo di dati per i loro argomenti in contrasto con le funzioni.
Nel complesso sono uno strumento utile nella programmazione. E sia le macroistruzioni che le funzioni possono essere utilizzate a seconda delle circostanze.
Le funzioni eseguono il controllo dei tipi. Questo ti dà un ulteriore livello di sicurezza.
Non ho notato, nelle risposte sopra, un vantaggio delle funzioni rispetto ai macro che penso sia molto importante:
Le funzioni possono essere passate come argomenti, le macro no.
Esempio concreto: vuoi scrivere una versione alternativa della funzione standard “strpbrk” che accetterà, piuttosto che un elenco esplicito di caratteri da cercare all’interno di un’altra stringa, una funzione (puntatore a a) che restituirà 0 fino a quando un personaggio non sarà trovato che passa qualche test (definito dall’utente). Un motivo per cui potresti voler fare questo è che puoi sfruttare altre funzioni di libreria standard: invece di fornire una stringa esplicita piena di punteggiatura, potresti passare invece ‘ispunct’ di ctype.h, ecc. Se ‘ispunct’ è implementato solo come una macro, questo non funzionerebbe.
Ci sono molti altri esempi. Ad esempio, se il confronto viene eseguito mediante macro piuttosto che funzione, non è ansible passarlo a “qsort” di stdlib.h.
Una situazione analoga in Python è “stampa” nella versione 2 rispetto alla versione 3 (istruzione non passabile e funzione passabile).