Ich habe immer Beispiele und Fälle gesehen, in denen die Verwendung eines Makros besser ist als die Verwendung von Funktionen.
Könnte mir jemand anhand eines Beispiels den Nachteil eines Makros gegenüber einer Funktion erklären?
c
function
c-preprocessor
Kyrol
quelle
quelle
Antworten:
Makros sind fehleranfällig, da sie auf Textersetzung beruhen und keine Typprüfung durchführen. Zum Beispiel dieses Makro:
funktioniert gut, wenn es mit einer ganzen Zahl verwendet wird:
macht aber sehr seltsame Dinge, wenn es mit Ausdrücken verwendet wird:
Das Setzen von Klammern um Argumente hilft, beseitigt diese Probleme jedoch nicht vollständig.
Wenn Makros mehrere Anweisungen enthalten, können Probleme mit Kontrollflusskonstrukten auftreten:
Die übliche Strategie, um dies zu beheben, besteht darin, die Anweisungen in eine "do {...} while (0)" - Schleife zu setzen.
Wenn Sie zwei Strukturen haben, die zufällig ein Feld mit demselben Namen, aber unterschiedlicher Semantik enthalten, funktioniert möglicherweise dasselbe Makro für beide, mit seltsamen Ergebnissen:
Schließlich kann es schwierig sein, Makros zu debuggen, was zu seltsamen Syntax- oder Laufzeitfehlern führt, die Sie erweitern müssen, um sie zu verstehen (z. B. mit gcc -E), da Debugger keine Makros durchlaufen können, wie in diesem Beispiel:
Inline-Funktionen und -Konstanten helfen, viele dieser Probleme mit Makros zu vermeiden, sind jedoch nicht immer anwendbar. Wenn Makros absichtlich zur Angabe des polymorphen Verhaltens verwendet werden, kann es schwierig sein, unbeabsichtigten Polymorphismus zu vermeiden. C ++ verfügt über eine Reihe von Funktionen, z. B. Vorlagen, mit denen komplexe polymorphe Konstrukte typsicher ohne Verwendung von Makros erstellt werden können. Weitere Informationen finden Sie in Stroustrups Programmiersprache C ++ .
quelle
x++*x++
kann also nicht sagen, dass der Ausdruckx
zweimal inkrementiert wird. Es ruft tatsächlich undefiniertes Verhalten auf , was bedeutet, dass der Compiler frei ist, alles zu tun, was er will - es kannx
zweimal oder einmal oder gar nicht erhöht werden . Es könnte mit einem Fehler abgebrochen werden oder sogar Dämonen aus der Nase fliegen lassen .Makrofunktionen :
Funktionsmerkmale :
quelle
Nebenwirkungen sind groß. Hier ist ein typischer Fall:
wird erweitert auf:
x
wird in derselben Anweisung zweimal inkrementiert. (und undefiniertes Verhalten)Das Schreiben von mehrzeiligen Makros ist ebenfalls ein Problem:
Sie benötigen ein
\
am Ende jeder Zeile.Makros können nichts "zurückgeben", es sei denn, Sie machen es zu einem einzelnen Ausdruck:
Dies ist in einem Makro nur möglich, wenn Sie die Ausdrucksanweisung von GCC verwenden. (BEARBEITEN: Sie können zwar einen Kommaoperator verwenden ... haben das übersehen ... aber es ist möglicherweise immer noch weniger lesbar.)
Reihenfolge der Operationen: (mit freundlicher Genehmigung von @ouah)
wird erweitert auf:
Hat aber
&
eine niedrigere Priorität als<
. So0xFF < 42
wird zuerst ausgewertet.quelle
min(a & 0xFF, 42)
Beispiel 1:
wohingegen:
Beispiel 2:
Verglichen mit:
quelle
Verwenden Sie im Zweifelsfall Funktionen (oder Inline-Funktionen).
Die Antworten hier erklären jedoch meistens 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. Verwenden Sie dann Makros nur, wenn es einen guten Grund dafür gibt.
Es gibt bestimmte Ausnahmefälle , in denen die Verwendung von Makros Vorteile bietet. Dazu gehören:
va_args
.Beispiel: https://stackoverflow.com/a/24837037/432509 .
(
__FILE__
,__LINE__
,__func__
). Überprüfen Sie, ob Pre- / Post-Bedingungen vorliegen, ob einassert
Fehler aufgetreten ist oder ob statische Zusicherungen vorliegen, damit der Code bei unsachgemäßer Verwendung nicht kompiliert wird (meistens nützlich für Debug-Builds).struct
vor dem Gießen vorhanden sind(kann für polymorphe Typen nützlich sein) .
Oder überprüfen Sie, ob ein Array eine Längenbedingung erfüllt.
Siehe: https://stackoverflow.com/a/29926435/432509
func(FOO, "FOO");
Sie könnten ein Makro definieren, das die Zeichenfolge für Sie erweitertfunc_wrapper(FOO);
(Die Zuweisung zu mehreren Variablen für Operationen pro Pixel ist ein Beispiel, bei dem Sie möglicherweise ein Makro einer Funktion vorziehen ... obwohl dies immer noch stark vom Kontext abhängt, da
inline
Funktionen eine Option sein können.) .Zugegeben, einige davon basieren auf Compiler-Erweiterungen, die nicht Standard C sind. Dies bedeutet, dass Sie möglicherweise weniger portablen Code haben oder
ifdef
diesen benötigen, sodass sie nur dann genutzt werden, wenn der Compiler dies unterstützt.Vermeiden der Instanziierung mehrerer Argumente
Dies ist eine der häufigsten Fehlerursachen in Makros (
x++
z. B. wenn ein Makro mehrmals inkrementiert wird) .Es ist möglich, Makros zu schreiben, die Nebenwirkungen durch mehrfache Instanziierung von Argumenten vermeiden.
C11 Generisch
Wenn Sie ein
square
Makro haben möchten, das mit verschiedenen Typen funktioniert und C11-Unterstützung bietet, können Sie dies tun ...Anweisungsausdrücke
Dies ist eine Compiler-Erweiterung, die von GCC, Clang, EKOPath und Intel C ++ (jedoch nicht von MSVC) unterstützt wird .
Der Nachteil bei Makros ist also, dass Sie wissen müssen, um diese zu verwenden, und dass sie nicht so weit verbreitet sind.
Ein Vorteil ist, dass Sie in diesem Fall dieselbe
square
Funktion für viele verschiedene Typen verwenden können.quelle
Es wird keine Typprüfung von Parametern und Code wiederholt, was zu einem Aufblähen des Codes führen kann. Die Makrosyntax kann auch zu einer beliebigen Anzahl seltsamer Randfälle führen, in denen Semikolons oder Rangfolgen im Weg stehen können. Hier ist ein Link, der etwas Makro- Übel demonstriert
quelle
Ein Nachteil von Makros besteht darin, dass Debugger Quellcode lesen, der keine erweiterten Makros enthält. Daher ist es nicht unbedingt sinnvoll, einen Debugger in einem Makro auszuführen. Natürlich können Sie in einem Makro keinen Haltepunkt setzen, wie Sie es mit Funktionen können.
quelle
Funktionen führen eine Typprüfung durch. Dies gibt Ihnen eine zusätzliche Sicherheitsebene.
quelle
Hinzufügen zu dieser Antwort ..
Makros werden vom Präprozessor direkt in das Programm eingesetzt (da es sich im Grunde genommen um Präprozessoranweisungen handelt). Sie belegen also zwangsläufig mehr Speicherplatz als eine entsprechende Funktion. Andererseits benötigt eine Funktion mehr Zeit, um aufgerufen zu werden und Ergebnisse zurückzugeben, und dieser Overhead kann durch die Verwendung von Makros vermieden werden.
Makros verfügen auch über einige spezielle Tools, die bei der Programmportabilität auf verschiedenen Plattformen hilfreich sein können.
Makros muss im Gegensatz zu Funktionen kein Datentyp für ihre Argumente zugewiesen werden.
Insgesamt sind sie ein nützliches Werkzeug bei der Programmierung. Abhängig von den Umständen können sowohl Makroanweisungen als auch Funktionen verwendet werden.
quelle
In den obigen Antworten habe ich keinen Vorteil von Funktionen gegenüber Makros bemerkt, den ich für sehr wichtig halte:
Funktionen können als Argumente übergeben werden, Makros nicht.
Konkretes Beispiel: Sie möchten eine alternative Version der Standardfunktion 'strpbrk' schreiben, die anstelle einer expliziten Liste von Zeichen, nach denen in einer anderen Zeichenfolge gesucht werden soll, eine (Zeiger auf eine) Funktion akzeptiert, die 0 zurückgibt, bis ein Zeichen ist gefunden, die einen Test besteht (benutzerdefiniert). Ein Grund, warum Sie dies tun möchten, besteht darin, dass Sie andere Standardbibliotheksfunktionen nutzen können: Anstatt eine explizite Zeichenfolge voller Interpunktion bereitzustellen, können Sie stattdessen ctype.hs 'ispunct' usw. übergeben. Wenn 'ispunct' nur als implementiert wurde ein Makro, das würde nicht funktionieren.
Es gibt viele andere Beispiele. Wenn Ihr Vergleich beispielsweise eher durch ein Makro als durch eine Funktion durchgeführt wird, können Sie ihn nicht an 'qsort' von stdlib.h übergeben.
Eine analoge Situation in Python ist 'print' in Version 2 vs. Version 3 (nicht passable Anweisung vs. passable Funktion).
quelle
Wenn Sie die Funktion als Argument an das Makro übergeben, wird sie jedes Mal ausgewertet. Wenn Sie beispielsweise eines der beliebtesten Makros aufrufen:
so wie das
functionThatTakeLongTime wird fünfmal ausgewertet, was die Leistung erheblich beeinträchtigen kann
quelle