Ich sehe oft Fälle, in denen die Verwendung eines Makros besser ist als die Verwendung einer Funktion.
Könnte mir jemand anhand eines Beispiels den Nachteil eines Makros gegenüber einer Funktion erklären?
Kyrol
Ich sehe oft Fälle, in denen die Verwendung eines Makros besser ist als die Verwendung einer Funktion.
Könnte mir jemand anhand eines Beispiels den Nachteil eines Makros gegenüber einer Funktion erklären?
Chiara Coetzee
Makros sind fehleranfällig, da sie auf Textsubstitution angewiesen sind und keine Typprüfung durchführen. Zum Beispiel dieses Makro:
#define square(a) a * a
funktioniert gut, wenn es mit einer Ganzzahl verwendet wird:
square(5) --> 5 * 5 --> 25
macht aber sehr seltsame Dinge, wenn es mit Ausdrücken verwendet wird:
square(1 + 2) --> 1 + 2 * 1 + 2 --> 1 + 2 + 2 --> 5
square(x++) --> x++ * x++ --> increments x twice
Das Setzen von Klammern um Argumente hilft, beseitigt diese Probleme jedoch nicht vollständig.
Wenn Makros mehrere Anweisungen enthalten, können Sie Probleme mit Kontrollflusskonstrukten bekommen:
#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;
Die übliche Strategie, dies zu beheben, besteht darin, die Anweisungen in eine “do { … } while (0)”-Schleife zu packen.
Wenn Sie zwei Strukturen haben, die zufällig ein Feld mit demselben Namen, aber unterschiedlicher Semantik enthalten, funktioniert möglicherweise dasselbe Makro auf beiden, mit seltsamen Ergebnissen:
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
Schließlich können Makros schwierig zu debuggen sein und seltsame Syntaxfehler oder Laufzeitfehler erzeugen, die Sie erweitern müssen, um sie zu verstehen (z. B. mit gcc -E), da Debugger Makros nicht schrittweise durchlaufen können, wie in diesem Beispiel:
#define print(x, y) printf(x y) /* accidentally forgot comma */
print("foo %s", "bar") /* prints "foo %sbar" */
Inline-Funktionen und -Konstanten helfen, viele dieser Probleme mit Makros zu vermeiden, sind aber nicht immer anwendbar. Wo Makros absichtlich verwendet werden, um polymorphes Verhalten zu spezifizieren, kann unbeabsichtigter Polymorphismus schwer zu vermeiden sein. C++ verfügt über eine Reihe von Funktionen wie Vorlagen, die dabei helfen, komplexe polymorphe Konstrukte auf typsichere Weise ohne die Verwendung von Makros zu erstellen. siehe Stroustrup Die Programmiersprache C++ für Details.
Was hat es mit der C++-Werbung auf sich?
– Schrittmacher
27. September 2013 um 8:58 Uhr
Stimmen Sie zu, dies ist eine C-Frage, keine Notwendigkeit, Vorurteile hinzuzufügen.
– Ideengeber42
10. September 2014 um 2:55 Uhr
C++ ist eine Erweiterung von C, die (unter anderem) Funktionen hinzufügt, die diese spezielle Einschränkung von C beheben sollen. Ich bin kein Fan von C++, aber ich denke, es gehört hier zum Thema.
– Chiara Coetzee
16. September 2014 um 22:30 Uhr
Makros, Inline-Funktionen und Vorlagen werden häufig verwendet, um die Leistung zu steigern. Sie werden überbeansprucht und neigen dazu, die Leistung aufgrund von Code-Bloat zu beeinträchtigen, was die Effektivität des CPU-Anweisungscache verringert. Wir können schnelle generische Datenstrukturen in C erstellen, ohne diese Techniken zu verwenden.
– Sam Watkins
26. Juni 2015 um 3:24 Uhr
Gemäß ISO/IEC 9899:1999 §6.5.1 „sollte der gespeicherte Wert eines Objekts zwischen dem vorherigen und dem nächsten Sequenzpunkt höchstens einmal durch die Auswertung eines Ausdrucks geändert werden.“ (Ähnliche Formulierungen gibt es in früheren und nachfolgenden C-Normen.) Also der Ausdruck x++*x++
kann nicht gesagt werden, um zu erhöhen x
zweimal; es ruft tatsächlich auf undefiniertes Verhaltenwas bedeutet, dass der Compiler tun kann, was er will – er könnte inkrementieren x
zweimal, oder einmal, oder gar nicht; es könnte mit einem Fehler abbrechen oder sogar Lass Dämonen aus deiner Nase fliegen.
– Psychonaute
1. September 2015 um 12:09 Uhr
zangw
Makrofunktionen:
Funktionsmerkmale:
Referenz “Ausführungsgeschwindigkeit ist schneller” erforderlich. Jeder auch nur halbwegs kompetente Compiler des letzten Jahrzehnts wird Funktionen problemlos inline integrieren, wenn er der Meinung ist, dass dies einen Leistungsvorteil bietet.
– Voo
26. Mai 2019 um 20:30 Uhr
Ist das nicht im Zusammenhang mit Low-Level-MCU (AVRs, dh ATMega32)-Computing, dass Makros die bessere Wahl sind, da sie den Aufrufstapel nicht vergrößern, wie es Funktionsaufrufe tun?
– HardyVeles
29. August 2019 um 22:22 Uhr
@hardyVeles Nicht so. Compiler, sogar für einen AVR, können Code sehr intelligent einbetten. Hier ist ein Beispiel: godbolt.org/z/Ic21iM
– Eduard
19. Oktober 2019 um 19:57 Uhr
Mystisch
Nebenwirkungen sind groß. Hier ist ein typischer Fall:
#define min(a, b) (a < b ? a : b)
min(x++, y)
wird erweitert zu:
(x++ < y ? x++ : y)
x
wird in derselben Anweisung zweimal erhöht. (und undefiniertes Verhalten)
Das Schreiben von mehrzeiligen Makros ist auch ein Schmerz:
#define foo(a,b,c) \
a += 10; \
b += 10; \
c += 10;
Sie erfordern eine \
am Ende jeder Zeile.
Makros können nichts “zurückgeben”, es sei denn, Sie machen daraus einen einzelnen Ausdruck:
int foo(int *a, int *b){
side_effect0();
side_effect1();
return a[0] + b[0];
}
Kann das in einem Makro nicht tun, es sei denn, Sie verwenden GCCs Anweisungsausdrücke. (BEARBEITEN: Sie können zwar einen Kommaoperator verwenden … das übersehen … Aber es ist möglicherweise immer noch weniger lesbar.)
Reihenfolge der Operationen: (mit freundlicher Genehmigung von @ouah)
#define min(a,b) (a < b ? a : b)
min(x & 0xFF, 42)
wird erweitert zu:
(x & 0xFF < 42 ? x & 0xFF : 42)
Aber &
hat einen niedrigeren Vorrang als <
. So 0xFF < 42
wird zuerst ausgewertet.
und keine Klammern mit Makroargumenten in der Makrodefinition zu setzen, kann zu Prioritätsproblemen führen: z. min(a & 0xFF, 42)
– au
1. Februar 2012 um 23:01 Uhr
Ah ja. Ich habe deinen Kommentar nicht gesehen, während ich den Beitrag aktualisiert habe. Das werde ich wohl auch erwähnen.
– Mystisch
1. Februar 2012 um 23:07 Uhr
Ideengeber42
Verwenden Sie im Zweifelsfall Funktionen (oder Inline-Funktionen).
Die Antworten hier erklären jedoch hauptsächlich die Probleme mit Makros, anstatt eine einfache Ansicht zu haben, dass Makros böse sind, weil dumme Unfälle möglich sind.
Sie können sich der Fallstricke bewusst sein und lernen, sie zu vermeiden. Dann verwenden Sie Makros nur, wenn es einen guten Grund dafür gibt.
Es gibt bestimmte außergewöhnlich Fälle, in denen die Verwendung von Makros Vorteile bietet, sind:
va_args
.__FILE__
, __LINE__
, __func__
). auf Vor-/Nachbedingungen prüfen, assert
bei Fehlern oder sogar statische Behauptungen, damit der Code bei unsachgemäßer Verwendung nicht kompiliert wird (meistens nützlich für Debug-Builds).struct
Mitglieder sind vor dem Casting anwesendfunc(FOO, "FOO");
könnten Sie ein Makro definieren, das die Zeichenfolge für Sie erweitert func_wrapper(FOO);
inline
Funktionen können eine Option sein).Zugegebenermaßen verlassen sich einige davon auf Compiler-Erweiterungen, die nicht Standard-C sind. Das bedeutet, dass Sie möglicherweise weniger portierbaren Code erhalten oder müssen ifdef
sie ein, sodass sie nur dann genutzt werden, wenn der Compiler dies unterstützt.
Beachten Sie dies, da dies eine der häufigsten Ursachen für Fehler in Makros ist (vorbeigehen x++
zum Beispiel, wo ein Makro mehrmals inkrementieren kann).
Es ist möglich, Makros zu schreiben, die Seiteneffekte mit mehrfacher Instanziierung von Argumenten vermeiden.
Wenn Sie möchten square
Makro, das mit verschiedenen Typen funktioniert und C11-Unterstützung bietet, könnten Sie dies tun …
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))
Dies ist eine Compiler-Erweiterung, die von GCC, Clang, EKOPath und Intel C++ unterstützt wird (aber nicht MSVC);
#define square(a_) __extension__ ({ \
typeof(a_) a = (a_); \
(a * a); })
Der Nachteil bei Makros ist also, dass Sie wissen müssen, wie Sie diese verwenden, und dass sie nicht so weit unterstützt werden.
Ein Vorteil ist, dass Sie in diesem Fall dasselbe verwenden können square
Funktion für viele verschiedene Typen.
Flexo
#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;
}
wohingegen:
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;
}
Verglichen mit:
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;
}
Es wird keine Typüberprüfung von Parametern und Code wiederholt, was zu Code-Bloat führen kann. Die Makrosyntax kann auch zu einer Reihe von seltsamen Grenzfällen führen, in denen Semikolons oder Rangfolge im Weg stehen können. Hier ist ein Link, der einige Makros demonstriert teuflisch
Jim Mcnamara
Ein Nachteil von Makros besteht darin, dass Debugger Quellcode lesen, der keine erweiterten Makros enthält, sodass es nicht unbedingt sinnvoll ist, einen Debugger in einem Makro auszuführen. Natürlich können Sie innerhalb eines Makros keinen Haltepunkt setzen, wie dies bei Funktionen der Fall ist.
Der Haltepunkt ist hier ein sehr wichtiger Deal, danke für den Hinweis.
– Hans
13. Januar 2017 um 6:07 Uhr
Stell die Frage auf den Kopf. In welcher Situation ist ein Makro besser? Verwenden Sie eine echte Funktion, es sei denn, Sie können nachweisen, dass ein Makro besser ist.
– David Heffernan
1. Februar 2012 um 22:56 Uhr