
Benutzer1849534
Ich habe das immer gefragt, aber ich habe nie eine wirklich gute Antwort bekommen; Ich denke, dass fast jeder Programmierer schon vor dem Schreiben des ersten „Hello World“ auf einen Ausdruck wie „Makros sollten niemals verwendet werden“, „Makros sind böse“ und so weiter gestoßen ist. Meine Frage ist: Warum? Gibt es mit dem neuen C++11 nach so vielen Jahren eine echte Alternative?
Der einfache Teil dreht sich um Makros wie #pragma
die plattformspezifisch und Compiler-spezifisch sind, und meistens haben sie schwerwiegende Fehler wie #pragma once
das ist in mindestens 2 wichtigen Situationen fehleranfällig: gleicher Name in verschiedenen Pfaden und mit einigen Netzwerk-Setups und Dateisystemen.
Aber was ist im Allgemeinen mit Makros und Alternativen zu ihrer Verwendung?

Matt Petersson
Makros sind wie jedes andere Werkzeug – ein Hammer, der bei einem Mord verwendet wird, ist nicht böse, weil es ein Hammer ist. Es ist böse, wie die Person es auf diese Weise benutzt. Wenn Sie Nägel einschlagen möchten, ist ein Hammer ein perfektes Werkzeug.
Es gibt ein paar Aspekte bei Makros, die sie “schlecht” machen (ich werde später darauf eingehen und Alternativen vorschlagen):
- Makros können nicht debuggt werden.
- Die Makroerweiterung kann zu seltsamen Nebeneffekten führen.
- Makros haben keinen “Namespace”, wenn Sie also ein Makro haben, das mit einem anderswo verwendeten Namen kollidiert, erhalten Sie Makroersetzungen, wo Sie es nicht wollten, und dies führt normalerweise zu seltsamen Fehlermeldungen.
- Makros können Dinge beeinflussen, von denen Sie nichts wissen.
Lassen Sie uns hier also etwas erweitern:
1) Makros können nicht debuggt werden.
Wenn Sie ein Makro haben, das in eine Zahl oder einen String übersetzt wird, enthält der Quellcode den Makronamen, und viele Debugger können nicht „sehen“, was das Makro übersetzt. Sie wissen also nicht wirklich, was los ist.
Ersatz: Verwenden enum
oder const T
Da der Debugger bei “funktionsähnlichen” Makros auf einer Ebene “pro Quellzeile, wo Sie sich befinden” arbeitet, verhält sich Ihr Makro wie eine einzelne Anweisung, egal ob es sich um eine oder hundert Anweisungen handelt. Macht es schwer herauszufinden, was los ist.
Ersatz: Verwenden Sie Funktionen – Inline, wenn es “schnell” sein muss (aber Vorsicht, zu viel Inline ist nicht gut)
2) Makroerweiterungen können seltsame Nebeneffekte haben.
Der berühmte ist #define SQUARE(x) ((x) * (x))
und die Verwendung x2 = SQUARE(x++)
. Das führt zu x2 = (x++) * (x++);
was, auch wenn es sich um einen gültigen Code handelte [1], wäre mit ziemlicher Sicherheit nicht das, was der Programmierer wollte. Wenn es eine Funktion wäre, wäre es in Ordnung, x++ auszuführen, und x würde nur einmal inkrementieren.
Ein weiteres Beispiel ist “if else” in Makros, sagen wir, wir haben dies:
#define safe_divide(res, x, y) if (y != 0) res = x/y;
und dann
if (something) safe_divide(b, a, x);
else printf("Something is not set...");
Es wird eigentlich völlig falsch….
Ersatz: reelle Funktionen.
3) Makros haben keinen Namensraum
Wenn wir ein Makro haben:
#define begin() x = 0
und wir haben Code in C++, der begin verwendet:
std::vector<int> v;
... stuff is loaded into v ...
for (std::vector<int>::iterator it = myvector.begin() ; it != myvector.end(); ++it)
std::cout << ' ' << *it;
Nun, welche Fehlermeldung erhalten Sie Ihrer Meinung nach und wo suchen Sie nach einem Fehler? [assuming you have completely forgotten – or didn’t even know about – the begin macro that lives in some header file that someone else wrote? [and even more fun if you included that macro before the include – you’d be drowning in strange errors that makes absolutely no sense when you look at the code itself.
Replacement: Well there isn’t so much as a replacement as a “rule” – only use uppercase names for macros, and never use all uppercase names for other things.
4) Macros have effects you don’t realize
Take this function:
#define begin() x = 0
#define end() x = 17
... a few thousand lines of stuff here ...
void dostuff()
{
int x = 7;
begin();
... more code using x ...
printf("x=%d\n", x);
end();
}
Now, without looking at the macro, you would think that begin is a function, which shouldn’t affect x.
This sort of thing, and I’ve seen much more complex examples, can REALLY mess up your day!
Replacement: Either don’t use a macro to set x, or pass x in as an argument.
There are times when using macros is definitely beneficial. One example is to wrap a function with macros to pass on file/line information:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Now we can use my_debug_malloc
as the regular malloc in the code, but it has extra arguments, so when it comes to the end and we scan the “which memory elements hasn’t been freed”, we can print where the allocation was made so the programmer can track down the leak.
[1] Es ist ein undefiniertes Verhalten, eine Variable mehr als einmal “an einem Sequenzpunkt” zu aktualisieren. Ein Sequenzpunkt ist nicht genau dasselbe wie eine Anweisung, aber für die meisten Absichten und Zwecke sollten wir es so betrachten. Also tun x++ * x++
werde Dich auf dem Laufenden halten x
zweimal, was undefiniert ist und wahrscheinlich zu unterschiedlichen Werten auf verschiedenen Systemen und zu unterschiedlichen Ergebniswerten führen wird x
auch.

utnapistim
Das Sprichwort „Makros sind böse“ bezieht sich normalerweise auf die Verwendung von #define, nicht auf #pragma.
Konkret bezieht sich der Ausdruck auf diese beiden Fälle:
mit dem neuen C++ 11 gibt es nach so vielen Jahren eine echte Alternative ?
Ja, für die Elemente in der obigen Liste (magische Zahlen sollten mit const/constexpr definiert werden und Ausdrücke sollten mit definiert werden [normal/inline/template/inline template] Funktionen.
Hier sind einige der Probleme, die durch die Definition magischer Zahlen als Makros und das Ersetzen von Ausdrücken durch Makros entstehen (anstatt Funktionen zum Auswerten dieser Ausdrücke zu definieren):
-
Beim Definieren von Makros für magische Zahlen behält der Compiler keine Typinformationen für die definierten Werte. Dies kann zu Kompilierungswarnungen (und Fehlern) führen und Leute verwirren, die den Code debuggen.
-
Beim Definieren von Makros anstelle von Funktionen erwarten Programmierer, die diesen Code verwenden, dass sie wie Funktionen funktionieren, und das tun sie nicht.
Betrachten Sie diesen Code:
#define max(a, b) ( ((a) > (b)) ? (a) : (b) )
int a = 5;
int b = 4;
int c = max(++a, b);
Sie würden erwarten, dass a und c nach der Zuweisung zu c 6 sind (wie es bei Verwendung von std::max anstelle des Makros der Fall wäre). Stattdessen führt der Code Folgendes aus:
int c = ( ((++a) ? (b)) ? (++a) : (b) ); // after this, c = a = 7
Darüber hinaus unterstützen Makros keine Namespaces, was bedeutet, dass das Definieren von Makros in Ihrem Code den Clientcode auf die Namen beschränkt, die sie verwenden können.
Das bedeutet, wenn Sie das Makro oben (für max) definieren, können Sie dies nicht mehr tun #include <algorithm>
in einem der folgenden Codes, es sei denn, Sie schreiben ausdrücklich:
#ifdef max
#undef max
#endif
#include <algorithm>
Makros anstelle von Variablen / Funktionen zu haben bedeutet auch, dass Sie ihre Adresse nicht nehmen können:
-
Wenn ein Makro als Konstante zu einer magischen Zahl ausgewertet wird, können Sie es nicht per Adresse übergeben
-
Für ein Makro als Funktion können Sie es nicht als Prädikat verwenden oder die Adresse der Funktion nehmen oder sie als Funktor behandeln.
Edit: Als Beispiel die richtige Alternative zum #define max
Oben:
template<typename T>
inline T max(const T& a, const T& b)
{
return a > b ? a : b;
}
Dies macht alles, was das Makro tut, mit einer Einschränkung: Wenn die Typen der Argumente unterschiedlich sind, zwingt Sie die Vorlagenversion, explizit zu sein (was tatsächlich zu sichererem, expliziterem Code führt):
int a = 0;
double b = 1.;
max(a, b);
Wenn dieses Maximum als Makro definiert ist, wird der Code kompiliert (mit einer Warnung).
Wenn dieses Maximum als Vorlagenfunktion definiert ist, weist der Compiler auf die Mehrdeutigkeit hin, und Sie müssen beides sagen max<int>(a, b)
oder max<double>(a, b)
(und geben Sie damit ausdrücklich Ihre Absicht an).

Phaazon
Ein häufiges Problem ist folgendes:
#define DIV(a,b) a / b
printf("25 / (3+2) = %d", DIV(25,3+2));
Es wird 10 ausgegeben, nicht 5, weil der Präprozessor es auf diese Weise erweitert:
printf("25 / (3+2) = %d", 25 / 3 + 2);
Diese Version ist sicherer:
#define DIV(a,b) (a) / (b)
Makros sind besonders wertvoll, um generischen Code zu erstellen (die Parameter von Makros können alles sein), manchmal mit Parametern.
Außerdem wird dieser Code an der Stelle platziert (dh eingefügt), an der das Makro verwendet wird.
OTOH, ähnliche Ergebnisse können erzielt werden mit:
-
überladene Funktionen (verschiedene Parametertypen)
-
Vorlagen, in C++ (generische Parametertypen und -werte)
-
Inline-Funktionen (Platzieren Sie den Code dort, wo sie aufgerufen werden, anstatt zu einer Einzelpunktdefinition zu springen – dies ist jedoch eher eine Empfehlung für den Compiler).
edit: Warum das Makro schlecht ist:
1) keine Typprüfung der Argumente (sie haben keinen Typ), können also leicht missbraucht werden 2) erweitern sich manchmal zu sehr komplexem Code, der in der vorverarbeiteten Datei schwer zu identifizieren und zu verstehen ist 3) es ist leicht, Fehler zu machen -anfälliger Code in Makros, wie zum Beispiel:
#define MULTIPLY(a,b) a*b
und dann anrufen
MULTIPLY(2+3,4+5)
das sich ausdehnt
2+3*4+5 (und nicht in: (2+3)*(4+5)).
Um letzteres zu haben, sollten Sie Folgendes definieren:
#define MULTIPLY(a,b) ((a)*(b))
Ich glaube nicht, dass etwas falsch daran ist, Präprozessordefinitionen oder Makros zu verwenden, wie Sie sie nennen.
Sie sind ein (Meta-)Sprachkonzept, das in c/c++ zu finden ist, und wie jedes andere Tool können sie Ihnen das Leben erleichtern, wenn Sie wissen, was Sie tun. Das Problem mit Makros ist, dass sie vor Ihrem c/c++-Code verarbeitet werden und neuen Code generieren, der fehlerhaft sein und Compiler-Fehler verursachen kann, die alles andere als offensichtlich sind. Auf der positiven Seite können sie Ihnen helfen, Ihren Code sauber zu halten und Ihnen bei richtiger Verwendung viel Tipparbeit zu ersparen, also kommt es auf Ihre persönlichen Vorlieben an.

indiangarg
Makros in C/C++ können als wichtiges Werkzeug zur Versionskontrolle dienen. Derselbe Code kann mit einer geringfügigen Konfiguration von Makros an zwei Clients geliefert werden. Ich benutze Dinge wie
#define IBM_AS_CLIENT
#ifdef IBM_AS_CLIENT
#define SOME_VALUE1 X
#define SOME_VALUE2 Y
#else
#define SOME_VALUE1 P
#define SOME_VALUE2 Q
#endif
Diese Art von Funktionalität ist ohne Makros nicht ohne weiteres möglich. Makros sind eigentlich ein großartiges Softwarekonfigurations-Management-Tool und nicht nur eine Möglichkeit, Verknüpfungen für die Wiederverwendung von Code zu erstellen. Das Definieren von Funktionen zwecks Wiederverwendbarkeit in Makros kann durchaus zu Problemen führen.

indiangarg
Präprozessor-Makros sind nicht böse, wenn sie für beabsichtigte Zwecke verwendet werden, wie zum Beispiel:
- Erstellen verschiedener Releases derselben Software mithilfe von Konstrukten vom Typ #ifdef, z. B. das Release von Fenstern für verschiedene Regionen.
- Zum Definieren von Codetest-bezogenen Werten.
Alternativen-
Für ähnliche Zwecke kann man eine Art von Konfigurationsdateien im ini-, xml-, json-Format verwenden. Ihre Verwendung hat jedoch Laufzeiteffekte auf den Code, die ein Präprozessormakro vermeiden kann.
9985600cookie-checkWarum sind Präprozessor-Makros böse und was sind die Alternativen?yes
#pragma
ist kein Makro.– FooF
26. Dezember 2012 um 13:47 Uhr
@foof Präprozessordirektive ?
– Benutzer1849534
26. Dezember 2012 um 13:51 Uhr
@ user1849534: Ja, das ist es … und Ratschläge zu Makros sprechen nicht darüber
#pragma
.– Ben Voigt
26. Dezember 2012 um 13:52 Uhr
Du kannst viel damit machen
constexpr
,inline
Funktionen undtemplates
aberboost.preprocessor
undchaos
zeigen, dass Makros ihre Berechtigung haben. Ganz zu schweigen von Konfigurationsmakros für verschiedene Compiler, Plattformen usw.– Brandon
26. Dezember 2012 um 13:52 Uhr
Siehe auch “Sind alle Makros böse?”
– Brad Larson
♦
23. Januar 2013 um 18:56 Uhr