Angenommen, wir haben eine Codebasis, die für viele verschiedene Kunden verwendet wird, und wir haben Code, der nur für Kunden vom Typ X relevant ist. Ist es besser, Präprozessoranweisungen zu verwenden, um diesen Code nur in Kunden vom Typ X aufzunehmen, oder if-Anweisungen verwenden? Um klarer zu sein:
// some code
#if TYPE_X_COSTUMER = 1
// do some things
#endif
// rest of the code
oder
if(TYPE_X_COSTUMER) {
// do some things
}
Die Argumente, über die ich nachdenken kann, sind:
- Die Präprozessor-Direktive führt zu einem geringeren Code-Footprint und weniger Verzweigungen (bei nicht optimierenden Compilern).
- Wenn Anweisungen zu Code führen, der immer kompiliert wird, z. B. wenn jemand einen Fehler macht, der den irrelevanten Code für das Projekt, an dem er arbeitet, beschädigt, wird der Fehler weiterhin angezeigt und die Codebasis wird nicht beschädigt. Andernfalls wird ihm die Korruption nicht bekannt sein.
- Mir wurde immer gesagt, ich solle die Verwendung des Prozessors der Verwendung des Präprozessors vorziehen (wenn dies überhaupt ein Argument ist ...)
Was ist vorzuziehen - wenn es um eine Codebasis für viele verschiedene Kunden geht?
TYPE_X_CUSTOMER
noch ein Präprozessor-Makro?Antworten:
Ich denke, es gibt einen Vorteil bei der Verwendung eines #define, den Sie nicht erwähnt haben, und das ist die Tatsache, dass Sie den Wert in der Befehlszeile festlegen können (also in Ihrem einstufigen Build-Skript festlegen).
Ansonsten ist es im Allgemeinen besser, Makros zu vermeiden. Sie respektieren kein Scoping und das kann Probleme verursachen. Nur sehr dumme Compiler können eine Bedingung basierend auf einer Kompilierungszeitkonstante nicht optimieren. Ich weiß nicht, ob dies ein Problem für Ihr Produkt darstellt (zum Beispiel kann es wichtig sein, den Code auf eingebetteten Plattformen klein zu halten).
quelle
Wie bei vielen Fragen hängt die Antwort auf diese Frage davon ab . Anstatt zu sagen, was besser ist, habe ich eher Beispiele und Ziele angegeben, bei denen eines besser ist als das andere.
Sowohl der Präprozessor als auch die Konstante haben ihre eigenen Verwendungsorte.
Im Falle eines Vorprozessors wird der Code vor der Kompilierungszeit entfernt. Daher ist es am besten für Situationen geeignet, in denen erwartet wird, dass kein Code kompiliert wird . Dies kann sich auf die Modulstruktur und die Abhängigkeiten auswirken und die Auswahl der besten Codesegmente für Leistungsaspekte ermöglichen. In den folgenden Fällen muss der Code nur mit einem Präprozessor geteilt werden.
Multi-Plattform-Code:
Zum Beispiel, wenn Code unter verschiedenen Plattformen kompiliert wird, wenn Code von bestimmten Versionsnummern des Betriebssystems abhängt (oder sogar von der Compiler-Version - obwohl dies sehr selten ist). Wenn Sie beispielsweise mit Little-Endien-Big-Endien-Gegenstücken von Code arbeiten, müssen diese eher mit Präprozessoren als mit Konstanten getrennt werden. Oder wenn Sie Code für Windows und Linux kompilieren und bestimmte Systemaufrufe sehr unterschiedlich sind.
Experimentelle Patches:
Ein anderer Fall, in dem dies gerechtfertigt ist, ist ein experimenteller Code, der riskant ist, oder bestimmte wichtige Module, die weggelassen werden müssen und signifikante Verknüpfungs- oder Leistungsunterschiede aufweisen. Der Grund, warum man den Code über den Präprozessor deaktivieren möchte, anstatt sich unter if () zu verstecken, liegt darin, dass wir uns möglicherweise nicht sicher sind, welche Fehler durch diesen speziellen Änderungssatz verursacht werden, und dass wir auf experimenteller Basis ausgeführt werden. Wenn dies fehlschlägt, müssen wir nichts anderes tun , als diesen Code in der Produktion zu deaktivieren , als ihn neu zu schreiben. Einige Zeit ist es ideal, um
#if 0
den gesamten Code zu kommentieren.Umgang mit Abhängigkeiten:
Ein weiterer Grund, aus dem Sie möglicherweise generieren möchten Wenn Sie beispielsweise keine JPEG-Bilder unterstützen möchten, können Sie das Kompilieren dieses Moduls / Stubs vermeiden, und die Bibliothek wird möglicherweise nicht (statisch oder dynamisch) mit diesem verknüpft Modul. Manchmal werden Pakete ausgeführt
./configure
, um die Verfügbarkeit einer solchen Abhängigkeit zu ermitteln. Wenn keine Bibliotheken vorhanden sind (oder der Benutzer diese nicht aktivieren möchte), wird diese Funktionalität automatisch deaktiviert, ohne eine Verknüpfung mit dieser Bibliothek herzustellen. Hier ist es immer vorteilhaft, wenn diese Anweisungen automatisch generiert werden.Lizenzierung:
Ein sehr interessantes Beispiel für eine Präprozessor-Direktive ist ffmpeg . Es verfügt über Codecs, die durch ihre Verwendung möglicherweise Patente verletzen können. Wenn Sie die Quelle herunterladen und zur Installation kompilieren, werden Sie gefragt, ob Sie solche Inhalte möchten oder nicht. Halten Sie Codes unter bestimmten Umständen versteckt, wenn die Bedingungen Sie immer noch vor Gericht bringen können!
Code kopieren und einfügen:
Aka-Makros. Dies ist kein Ratschlag für die übermäßige Verwendung von Makros - nur, dass Makros eine viel leistungsfähigere Möglichkeit bieten, das Äquivalent zum Kopieren nach Vergangenheit anzuwenden . Aber verwenden Sie es mit großer Sorgfalt; und verwenden Sie es, wenn Sie wissen, was Sie tun. Konstanten können das natürlich nicht. Man kann aber auch
inline
Funktionen verwenden, wenn das einfach ist.Wann verwenden Sie Konstanten?
Fast überall sonst.
Ordentlicher Codefluss:
Wenn Sie Konstanten verwenden, ist dieser im Allgemeinen kaum von regulären Variablen zu unterscheiden und daher besser lesbarer Code. Wenn Sie eine Routine mit 75 Zeilen schreiben, haben Sie 3 oder 4 Zeilen nach jeweils 10 Zeilen, wobei #ifdef SEHR nicht lesbar ist . Vermutlich eine primäre Konstante, die von #ifdef bestimmt wird, und sie überall in einem natürlichen Fluss verwenden.
Gut eingerückter Code: Alle Präprozessor-Direktiven funktionieren niemals gut mit ansonsten gut eingerücktem Code . Selbst wenn Ihr Compiler das Einrücken von #def zulässt, hat der Pre-ANSI C-Präprozessor kein Leerzeichen zwischen dem Zeilenanfang und dem Zeichen "#" zugelassen. Das führende "#" musste immer in der ersten Spalte stehen.
Konfiguration:
Ein weiterer Grund, warum Konstanten / oder Variablen sinnvoll sind, besteht darin, dass sie sich leicht aus der Verknüpfung mit globalen Elementen entwickeln oder in Zukunft erweitert werden können, um aus Konfigurationsdateien abgeleitet zu werden.
Eine letzte Sache:
Verwenden Sie niemals Präprozessor-Anweisungen,
#ifdef
um#endif
den Geltungsbereich zu überschreiten, oder{ ... }
. dh Beginn#ifdef
oder Ende von#endif
auf verschiedenen Seiten von{ ... }
. Das ist extrem schlecht; es kann verwirrend sein, es kann manchmal gefährlich sein.Dies ist natürlich keine vollständige Liste, zeigt Ihnen jedoch einen deutlichen Unterschied, wo welche Methode besser geeignet ist. Es geht nicht wirklich darum, was besser ist , es geht immer mehr darum, welches in einem bestimmten Kontext natürlicher zu verwenden ist.
quelle
Haben Ihre Kunden Zugriff auf Ihren Code? Wenn ja, könnte der Präprozessor eine bessere Option sein. Wir haben etwas Ähnliches und verwenden Kompilierungszeit-Flags für verschiedene Kunden (oder kundenspezifische Funktionen). Dann könnte ein Skript kundenspezifischen Code extrahieren und wir versenden diesen Code. Den Kunden sind andere Kunden oder andere kundenspezifische Merkmale nicht bekannt.
quelle
Einige weitere erwähnenswerte Punkte:
Der Compiler kann einige Arten von konstanten Ausdrücken verarbeiten, die der Präprozessor nicht kann. Beispielsweise hat der Präprozessor im Allgemeinen keine Möglichkeit,
sizeof()
nicht-primitive Typen zu bewerten . Dies kannif()
in einigen Fällen die Verwendung erzwingen .Der Compiler ignoriert die meisten Syntaxprobleme in Code, der übersprungen wird
#if
(einige Probleme im Zusammenhang mit Präprozessoren können immer noch Probleme verursachen), besteht jedoch auf syntaktischer Korrektheit für Code, mit dem übersprungen wirdif()
. Wenn also Code, der für einige Builds übersprungen wird, für andere jedoch nicht, infolge von Änderungen an anderer Stelle in der Datei ungültig wird (z. B. umbenannte Bezeichner), wird er im Allgemeinen bei allen Builds kreischen, wenn er deaktiviert ist,if()
aber nicht, wenn er von deaktiviert ist#if
. Je nachdem, warum der Code übersprungen wird, kann dies eine gute Sache sein oder auch nicht.Einige Compiler lassen nicht erreichbaren Code weg, andere nicht. Deklarationen der statischen Speicherdauer in nicht erreichbarem Code, einschließlich Zeichenfolgenliteralen, können jedoch Speicherplatz zuweisen, selbst wenn kein Code jemals den so zugewiesenen Speicherplatz verwenden kann.
Makros können
if()
Tests in ihnen verwenden, aber es gibt leider keinen Mechanismus für den Präprozessor, um eine bedingte Logik innerhalb eines Makros auszuführen [Beachten Sie, dass der? :
Operator für die Verwendung in Makros oft besser ist alsif
, aber es gelten dieselben Prinzipien].Mit der
#if
Direktive kann gesteuert werden, welche Makros definiert werden.Ich denke, es
if()
ist in den meisten Fällen oft sauberer, außer in Fällen, in denen Entscheidungen getroffen werden, die darauf basieren, welchen Compiler verwendet wird oder welche Makros definiert werden sollten. Einige der oben genannten Probleme erfordern möglicherweise die Verwendung#if
, selbst in Fällen, in denenif
dies sauberer erscheint.quelle
Lange Rede, kurzer Sinn: Die Befürchtungen bezüglich Präprozessor-Direktiven sind im Grunde genommen 2, mehrere Einschlüsse und möglicherweise weniger lesbarer Code und eine kritischere Logik.
Wenn Ihr Projekt klein ist und fast keinen großen Plan für die Zukunft hat, bieten beide Optionen meines Erachtens das gleiche Verhältnis zwischen Vor- und Nachteilen. Wenn Ihr Projekt jedoch sehr groß sein wird, sollten Sie etwas anderes in Betracht ziehen, z. B. das Schreiben einer separaten Header-Datei, wenn Sie möchten weiterhin Präprozessor-Direktiven verwenden, denken Sie jedoch daran, dass diese Art von Ansatz den Code normalerweise weniger lesbar macht, und stellen Sie sicher, dass der Name dieser Konstanten etwas für die Geschäftslogik des Programms selbst bedeutet.
quelle