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 in der Art, wie die Person es auf diese Weise benutzt. Wenn Sie Nägel einschlagen möchten, ist ein Hammer das perfekte Werkzeug.
Makros haben einige Aspekte, die sie "schlecht" machen (ich werde sie später näher erläutern und Alternativen vorschlagen):
- Sie können keine Makros debuggen.
- Makroerweiterung kann zu seltsamen Nebenwirkungen führen.
- Makros haben keinen "Namespace". Wenn Sie also ein Makro haben, das mit einem an anderer Stelle verwendeten Namen kollidiert, erhalten Sie Makroersetzungen, wo Sie es nicht wollten, und dies führt normalerweise zu seltsamen Fehlermeldungen.
- Makros können sich auf Dinge auswirken, die Sie nicht erkennen.
Lassen Sie uns hier ein wenig erweitern:
1) Makros können nicht debuggt werden.
Wenn Sie ein Makro haben, das in eine Zahl oder eine Zeichenfolge übersetzt wird, hat der Quellcode den Makronamen und viele Debugger können nicht "sehen", in was das Makro übersetzt wird. Sie wissen also nicht genau, was los ist.
Ersatz : Verwenden Sie enum
oderconst T
Bei "funktionsähnlichen" Makros verhält sich Ihr Makro wie eine einzelne Anweisung, unabhängig davon, ob es sich um eine oder hundert Anweisungen handelt, da der Debugger auf der Ebene "pro Quellzeile, auf der Sie sich befinden" arbeitet. Macht es schwer herauszufinden, was los ist.
Ersatz : Verwenden Sie Funktionen - Inline, wenn es "schnell" sein muss (aber beachten Sie, dass zu viel Inline keine gute Sache ist)
2) Makroerweiterungen können seltsame Nebenwirkungen haben.
Der berühmte ist #define SQUARE(x) ((x) * (x))
und die Verwendung x2 = SQUARE(x++)
. Das führt dazu x2 = (x++) * (x++);
, dass, selbst wenn es ein gültiger Code wäre [1], mit ziemlicher Sicherheit nicht das wäre, 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 anderes Beispiel ist "wenn sonst" in Makros. Sagen wir, wir haben folgendes:
#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 tatsächlich völlig falsch ....
Ersatz : echte Funktionen.
3) Makros haben keinen Namespace
Wenn wir ein Makro haben:
#define begin() x = 0
und wir haben einen 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;
Welche Fehlermeldung erhalten Sie Ihrer Meinung nach und wo suchen Sie nach einem Fehler [vorausgesetzt, Sie haben das Startmakro, das sich in einer Header-Datei befindet, die jemand anderes geschrieben hat, vollständig vergessen oder gar nicht gekannt? [und noch mehr Spaß, wenn Sie dieses Makro vor dem Einschließen einfügen - Sie würden in seltsamen Fehlern ertrinken, die absolut keinen Sinn ergeben, wenn Sie sich den Code selbst ansehen.
Ersetzung : Nun, es gibt nicht einmal eine Ersetzung als eine "Regel" - verwenden Sie nur Großbuchstaben für Makros und niemals alle Großbuchstaben für andere Dinge.
4) Makros haben Effekte, die Sie nicht erkennen
Nehmen Sie diese Funktion:
#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();
}
Ohne das Makro zu betrachten, würden Sie denken, dass begin eine Funktion ist, die x nicht beeinflussen sollte.
Diese Art von Dingen, und ich habe viel komplexere Beispiele gesehen, kann Ihren Tag WIRKLICH durcheinander bringen!
Ersetzung : Verwenden Sie entweder kein Makro, um x festzulegen, oder übergeben Sie x als Argument.
Es gibt Zeiten, in denen die Verwendung von Makros definitiv von Vorteil ist. Ein Beispiel ist das Umschließen einer Funktion mit Makros, um Datei- / Zeileninformationen weiterzugeben:
#define malloc(x) my_debug_malloc(x, __FILE__, __LINE__)
#define free(x) my_debug_free(x, __FILE__, __LINE__)
Jetzt können wir my_debug_malloc
als reguläres Malloc im Code verwenden, aber es hat zusätzliche Argumente. Wenn es also zum Ende kommt und wir scannen, "welche Speicherelemente nicht freigegeben wurden", können wir drucken, wo die Zuordnung vorgenommen wurde Der Programmierer kann das Leck aufspüren.
[1] Es ist ein undefiniertes Verhalten, eine Variable mehr als einmal "in einem Sequenzpunkt" zu aktualisieren. Ein Sequenzpunkt ist nicht genau dasselbe wie eine Aussage, aber für die meisten Absichten und Zwecke sollten wir ihn so betrachten. Das x++ * x++
wird also x
zweimal aktualisiert , was undefiniert ist und wahrscheinlich zu unterschiedlichen Werten auf verschiedenen Systemen und auch zu unterschiedlichen Ergebniswerten führen wird x
.
#pragma
ist kein Makro.