Ho sempre visto esempi e casi in cui usare una macro è meglio che usare la funzione.
Qualcuno potrebbe spiegarmi con un esempio lo svantaggio di una macro rispetto ad una funzione?
Ho sempre visto esempi e casi in cui usare una macro è meglio che usare la funzione.
Qualcuno potrebbe spiegarmi con un esempio lo svantaggio di una macro rispetto ad una funzione?
Risposte:
Le macro sono soggette a errori perché si basano sulla sostituzione testuale e non eseguono il controllo del tipo. 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 quando viene usato con le 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 flusso di controllo:
#define swap(x, y) t = x; x = y; y = t;
if (x < y) swap(x, y); -->
if (x < y) t = x; x = y; y = t; --> if (x < y) { t = x; } x = y; y = t;
La strategia usuale per risolvere questo problema è 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 entrambe, con strani risultati:
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 sintassi o errori di runtime che devi espandere per comprendere (ad esempio con gcc -E), perché i debugger non possono passare da una macro all'altra, come in questo esempio:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Le funzioni e le costanti inline aiutano a evitare molti di questi problemi con le macro, ma non sono sempre applicabili. Quando le macro vengono utilizzate deliberatamente per specificare il comportamento polimorfico, può essere difficile evitare il polimorfismo involontario. Il C ++ ha una serie di caratteristiche 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.
x++*x++
non si può dire che l'espressione incrementa x
due volte; in realtà invoca un comportamento indefinito , il che significa che il compilatore è libero di fare tutto ciò che vuole: può incrementare x
due volte, o una volta, o per niente; potrebbe abortire con un errore o persino far volare i demoni fuori dal naso .
Funzionalità macro :
Caratteristiche della funzione :
Gli effetti collaterali sono importanti. Ecco un caso tipico:
#define min(a, b) (a < b ? a : b)
min(x++, y)
viene espanso a:
(x++ < y ? x++ : y)
x
viene incrementato due volte nella stessa istruzione. (e comportamento indefinito)
Anche la scrittura di macro su più righe è 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 tu non ne faccia una singola espressione:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Non puoi farlo in una macro a meno che non usi l'istruzione di espressione di GCC. (EDIT: Puoi usare un operatore virgola anche se ... 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)
viene espanso a:
(x & 0xFF < 42 ? x & 0xFF : 42)
Ma &
ha una precedenza inferiore a <
. Quindi 0xFF < 42
viene valutato prima.
min(a & 0xFF, 42)
#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;
}
In caso di dubbio, utilizzare le funzioni (o le funzioni inline).
Tuttavia le risposte qui spiegano principalmente i problemi con le macro, invece di avere una semplice visione che le macro sono dannose perché sono possibili incidenti stupidi.
Puoi essere consapevole delle insidie e imparare a evitarle. Quindi usa le macro solo quando c'è una buona ragione per farlo.
Ci sono alcuni casi eccezionali in cui ci sono vantaggi nell'utilizzo di macro, questi includono:
va_args
. __FILE__
, __LINE__
, __func__
). controllare le condizioni pre / post, assert
in caso di errore o anche statiche-asserisce in modo che il codice non venga compilato in caso di uso improprio (utile soprattutto per le build di debug).struct
membri siano presenti prima del casting func(FOO, "FOO");
, potresti definire una macro che espande la stringa per tefunc_wrapper(FOO);
inline
funzioni potrebbero essere un'opzione) .Certo, alcuni di questi si basano su estensioni del compilatore che non sono standard C. Significa che potresti ritrovarti con codice meno portabile, o doverlo inserire ifdef
, quindi vengono sfruttati solo quando il compilatore supporta.
Notando questo poiché è una delle cause più comuni di errori nelle macro (passando x++
ad esempio, dove una macro può aumentare più volte) .
è possibile scrivere macro che evitano effetti collaterali con più istanze di argomenti.
Se ti piace avere square
macro che funzioni con vari tipi e avere il supporto C11, potresti 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 delle macro è che devi sapere come usarle per cominciare e che non sono supportate così ampiamente.
Un vantaggio è che, in questo caso, puoi utilizzare la stessa square
funzione per molti tipi diversi.
Nessun controllo del tipo di parametri e codice viene ripetuto, il che può causare un aumento del codice. La sintassi della macro può anche portare a un numero qualsiasi di casi limite strani in cui i punti e virgola o l'ordine di precedenza possono intralciare. Ecco un link che dimostra alcune macro male
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 puoi impostare un punto di interruzione all'interno di una macro come puoi fare con le funzioni.
Le funzioni eseguono il controllo del tipo. Questo ti dà un ulteriore livello di sicurezza.
Aggiungendo a questa risposta ..
Le macro vengono sostituite direttamente nel programma dal preprocessore (dato che fondamentalmente sono 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 restituire risultati e questo sovraccarico può essere evitato utilizzando le macro.
Anche le macro hanno alcuni strumenti speciali che possono aiutare con la portabilità del programma su piattaforme diverse.
A differenza delle funzioni, non è necessario assegnare alle macro un tipo di dati per i loro argomenti.
Nel complesso sono uno strumento utile nella programmazione. E sia le macroistruzioni che le funzioni possono essere utilizzate a seconda delle circostanze.
Non ho notato, nelle risposte sopra, un vantaggio delle funzioni rispetto alle macro che penso sia molto importante:
Le funzioni possono essere passate come argomenti, le macro no.
Esempio concreto: si desidera scrivere una versione alternativa della funzione standard 'strpbrk' che accetti, piuttosto che un elenco esplicito di caratteri da cercare all'interno di un'altra stringa, una funzione (puntatore a una) che restituirà 0 finché un carattere non è ha rilevato che supera alcuni test (definito dall'utente). Uno dei motivi per cui potresti volerlo fare è che puoi sfruttare altre funzioni di libreria standard: invece di fornire una stringa esplicita piena di punteggiatura, potresti invece passare "ispunct" a ctype.h, ecc. Se "ispunct" è stato implementato solo come una macro, questo non funzionerebbe.
Ci sono molti altri esempi. Ad esempio, se il confronto viene eseguito da macro anziché da funzione, non è possibile passarlo a "qsort" di stdlib.h.
Una situazione analoga in Python è "print" nella versione 2 rispetto alla versione 3 (istruzione non passabile contro funzione passabile).
Se passi la funzione come argomento alla macro, verrà valutata ogni volta. Ad esempio, se chiami una delle macro più popolari:
#define MIN(a,b) ((a)<(b) ? (a) : (b))
come quello
int min = MIN(functionThatTakeLongTime(1),functionThatTakeLongTime(2));
functionThatTakeLongTime verrà valutata 5 volte, il che può far diminuire in modo significativo le prestazioni