Ist #pragma einmal ein sicherer Wächter?

310

Ich habe gelesen, dass es bei der Verwendung einige Compiler-Optimierungen gibt, #pragma oncedie zu einer schnelleren Kompilierung führen können. Ich erkenne, dass dies nicht dem Standard entspricht und daher ein plattformübergreifendes Kompatibilitätsproblem darstellen kann.

Wird dies von den meisten modernen Compilern auf Nicht-Windows-Plattformen (gcc) unterstützt?

Ich möchte Probleme bei der Plattformkompilierung vermeiden, aber auch die zusätzliche Arbeit von Fallback-Wachen vermeiden:

#pragma once
#ifndef HEADER_H
#define HEADER_H

...

#endif // HEADER_H

Sollte ich besorgt sein? Sollte ich dafür weitere geistige Energie aufwenden?

Ryan Emerle
quelle
3
Nachdem ich eine ähnliche Frage gestellt hatte , stellte ich fest, dass #pragma onceeinige Probleme mit der Klassenansicht in VS 2008 vermieden werden. Ich bin gerade dabei, Include-Wachen loszuwerden und sie #pragma onceaus diesem Grund durch alle zu ersetzen .
SmacL

Antworten:

188

Die Verwendung #pragma oncesollte auf jedem modernen Compiler funktionieren, aber ich sehe keinen Grund, keinen Standard- #ifndefInclude-Guard zu verwenden. Es funktioniert gut. Die einzige Einschränkung ist, dass GCC #pragma oncevor Version 3.4 nicht unterstützt hat .

Ich habe auch festgestellt, dass es zumindest bei GCC den Standard- #ifndefInclude-Guard erkennt und optimiert , sodass er nicht viel langsamer sein sollte als #pragma once.

Zifre
quelle
12
Es sollte überhaupt nicht langsamer sein (mit GCC sowieso).
Jason Coco
54
Es ist nicht so implementiert. Wenn die Datei zum ersten Mal mit einem #ifndef beginnt und mit einem #endif endet, merkt sich gcc dies und überspringt immer die zukünftigen Dateien, ohne sich die Mühe zu machen, die Datei zu öffnen.
Jason Coco
10
#pragma onceist im Allgemeinen schneller, da die Datei nicht vorverarbeitet wird. ifndef/define/endiferfordert sowieso eine Vorverarbeitung, denn nach diesem Block kann man (theoretisch) etwas Kompilierbares haben
Andrey
13
GCC-Dokumente zur Optimierung des Guard-Makros: gcc.gnu.org/onlinedocs/cppinternals/Guard-Macros.html
Adrian
38
Um Include Guards zu verwenden, müssen Sie zusätzlich ein neues Symbol definieren, z. B. #ifndef FOO_BAR_Hnormalerweise für eine Datei wie "foo_bar.h". Wenn Sie diese Datei später umbenennen, sollten Sie die Include-Schutzvorrichtungen entsprechend anpassen, um mit dieser Konvention übereinzustimmen? Wenn Sie zwei unterschiedliche foo_bar.h an zwei verschiedenen Stellen in Ihrem Codebaum haben, müssen Sie sich jeweils zwei verschiedene Symbole vorstellen. Die kurze Antwort lautet: #pragma onceWenn Sie wirklich in einer Umgebung kompilieren müssen, die dies nicht unterstützt, fügen Sie Include-Schutzvorrichtungen für diese Umgebung hinzu.
Brandin
329

#pragma once hat einen Nachteil (außer nicht standardmäßig zu sein): Wenn Sie dieselbe Datei an verschiedenen Speicherorten haben (wir haben dies, weil unser Build-System Dateien kopiert), wird der Compiler denken, dass dies unterschiedliche Dateien sind.

Motti
quelle
36
Sie können aber auch zwei Dateien mit demselben Namen an unterschiedlichen Speicherorten haben, ohne sich die Mühe machen zu müssen, unterschiedliche #define NAMES zu erstellen. Dies geschieht in Form von HEADERFILENAME_H
Vargas
69
Sie können auch zwei oder mehr Dateien mit der gleichen #define WHATEVER haben, was kein Ende des Spaßes verursacht. Aus diesem Grund würde ich die einmalige Verwendung von Pragma bevorzugen.
Chris Huang-Leaver
107
Nicht überzeugend ... Ändern Sie das Build-System in ein System, das keine Dateien kopiert, sondern stattdessen Symlinks verwendet, oder fügen Sie dieselbe Datei nur von einem Speicherort in jeder Übersetzungseinheit ein. Klingt eher so, als wäre Ihre Infrastruktur ein Chaos, das neu organisiert werden muss.
Yakov Galka
3
Und wenn Sie verschiedene Dateien mit demselben Namen in verschiedenen Verzeichnissen haben, glaubt der # ifdef-Ansatz, dass es sich um dieselbe Datei handelt. Es gibt also einen Nachteil für den einen und einen Nachteil für den anderen.
Rxantos
3
@rxantos, wenn sich die Dateien unterscheiden #ifdef, kann sich auch der Makrowert unterscheiden.
Motti
63

Ich wünschte #pragma once(oder so ähnlich) wäre im Standard gewesen. Include-Wachen sind keine große Sache (aber es scheint ein wenig schwierig zu sein, sie den Leuten zu erklären, die die Sprache lernen), aber es scheint ein kleiner Ärger zu sein, der hätte vermieden werden können.

In der Tat, da das #pragma onceVerhalten in 99,98% der Fälle das gewünschte Verhalten ist, wäre es schön gewesen, wenn das Verhindern der mehrfachen Einbeziehung eines Headers automatisch vom Compiler behandelt worden wäre, mit einem #pragmaoder etwas, das eine doppelte Einbeziehung ermöglicht.

Aber wir haben was wir haben (außer dass Sie es vielleicht nicht haben #pragma once).

Michael Burr
quelle
48
Was ich wirklich will, ist eine Standardrichtlinie #import.
John
10
Eine Standard- Importrichtlinie kommt: isocpp.org/blog/2012/11/… Aber noch nicht hier. Ich bin stark dafür.
AHelps
6
@ Hilft Vaporware. Ist es jetzt fast fünf Jahre her? Vielleicht kommen Sie 2023 auf diesen Kommentar zurück und sagen "Ich habe es Ihnen gesagt".
Doug65536
Es handelt sich nicht um Vaporware, sondern nur in der Phase der technischen Spezifikation. Module werden in Visual Studio 2015 ( blogs.msdn.microsoft.com/vcblog/2015/12/03/… ) und in clang ( clang.llvm.org/docs/Modules.html ) implementiert . Und es ist Import, nicht #import.
AHelps
Sollte es in C ++ 20 schaffen.
Ionoclast Brigham
36

Ich weiß nichts über Leistungsvorteile, aber es funktioniert auf jeden Fall. Ich verwende es in allen meinen C ++ - Projekten (vorausgesetzt, ich verwende den MS-Compiler). Ich finde es effektiver als zu verwenden

#ifndef HEADERNAME_H
#define HEADERNAME_H
...
#endif

Es erledigt den gleichen Job und füllt den Präprozessor nicht mit zusätzlichen Makros.

GCC unterstützt #pragma onceoffiziell ab Version 3.4 .

JaredPar
quelle
25

GCC unterstützt #pragma onceseit 3.4, siehe http://en.wikipedia.org/wiki/Pragma_once für weitere Compiler-Unterstützung.

Der große Vorteil, den ich bei der Verwendung #pragma oncevon Wachen sehe , besteht darin, Fehler beim Kopieren / Einfügen zu vermeiden.

Seien wir ehrlich: Die meisten von uns starten kaum eine neue Header-Datei von Grund auf neu, sondern kopieren einfach eine vorhandene und passen sie an unsere Bedürfnisse an. Es ist viel einfacher, eine Arbeitsvorlage zu erstellen, #pragma onceanstatt Schutzvorrichtungen einzuschließen. Je weniger ich die Vorlage ändern muss, desto weniger Fehler treten auf. Dasselbe Include Guard in verschiedenen Dateien zu haben, führt zu seltsamen Compilerfehlern und es dauert einige Zeit, um herauszufinden, was schief gelaufen ist.

TL; DR: #pragma onceist einfacher zu bedienen.

uceumern
quelle
11

Ich benutze es und bin damit zufrieden, da ich viel weniger eingeben muss, um einen neuen Header zu erstellen. Es hat auf drei Plattformen gut funktioniert: Windows, Mac und Linux.

Ich habe keine Leistungsinformationen, aber ich glaube, dass der Unterschied zwischen #pragma und dem Include Guard nichts im Vergleich zu der Langsamkeit beim Parsen der C ++ - Grammatik ist. Das ist das eigentliche Problem. Versuchen Sie beispielsweise, die gleiche Anzahl von Dateien und Zeilen mit einem C # -Compiler zu kompilieren, um den Unterschied festzustellen.

Am Ende spielt es keine Rolle, die Wache oder das Pragma zu benutzen.

Edwin Jarvis
quelle
Ich mag #pragma einmal nicht, aber ich schätze es, dass Sie auf die relativen Vorteile hinweisen ... C ++ - Parsing ist in einer "normalen" Betriebsumgebung viel teurer als alles andere. Niemand kompiliert aus einem Remote-Dateisystem, wenn Kompilierungszeiten ein Problem darstellen.
Tom
1
Re C ++ Parsing Langsamkeit vs. C #. In C # müssen Sie nicht (buchstäblich) Tausende von LOC-Header-Dateien (iostream, irgendjemand?) Für jede winzige C ++ - Datei analysieren. Verwenden Sie vorkompilierte Header, um dieses Problem zu verkleinern
Eli Bendersky
11

Die Verwendung von ' #pragma once' hat möglicherweise keine Auswirkung (sie wird nicht überall unterstützt - obwohl sie zunehmend unterstützt wird). Sie müssen also trotzdem den bedingten Kompilierungscode verwenden. In diesem Fall sollten Sie sich mit ' #pragma once' beschäftigen. Der Compiler optimiert es wahrscheinlich trotzdem. Dies hängt jedoch von Ihren Zielplattformen ab. Wenn alle Ihre Ziele es unterstützen, verwenden Sie es - aber es sollte eine bewusste Entscheidung sein, denn die Hölle bricht los, wenn Sie nur das Pragma verwenden und dann auf einen Compiler portieren, der es nicht unterstützt.

Jonathan Leffler
quelle
1
Ich bin nicht der Meinung, dass Sie sowieso Wachen unterstützen müssen. Wenn Sie #pragma einmal verwenden (oder Wachen), liegt dies daran, dass einige Konflikte ohne sie ausgelöst werden. Wenn es also nicht von Ihrem Chain-Tool unterstützt wird, wird das Projekt einfach nicht kompiliert und Sie befinden sich genau in der gleichen Situation wie beim Kompilieren von Ansi C auf einem alten K & R-Compiler. Sie müssen nur ein aktuelleres Chaintool besorgen oder den Code ändern, um einige Wachen hinzuzufügen. Die Hölle brach, wenn das Programm kompiliert wurde, aber nicht funktionierte.
Kriss
5

Der Leistungsvorteil besteht darin, dass die Datei nach dem Lesen des #pragma nicht erneut geöffnet werden muss. Bei Wachen muss der Compiler die Datei öffnen (was zeitaufwändig sein kann), um die Informationen zu erhalten, die den Inhalt nicht wieder enthalten sollten.

Dies ist nur deshalb theoretisch, weil einige Compiler nicht automatisch Dateien öffnen, in denen kein Lesecode für jede Kompilierungseinheit enthalten war.

Wie auch immer, es ist nicht bei allen Compilern der Fall, daher muss #pragma idealerweise einmal für plattformübergreifenden Code vermieden werden, da es überhaupt nicht Standard ist / keine standardisierte Definition und Wirkung hat. Praktisch ist es jedoch wirklich besser als Wachen.

Am Ende ist der bessere Vorschlag , um sicherzustellen, dass Ihr Compiler die beste Geschwindigkeit bietet, ohne in diesem Fall das Verhalten jedes Compilers überprüfen zu müssen, die Verwendung von Pragma einmal und Guards.

#ifndef NR_TEST_H
#define NR_TEST_H
#pragma once

#include "Thing.h"

namespace MyApp
{
 // ...
}

#endif

Auf diese Weise erhalten Sie das Beste aus beiden (plattformübergreifend und bei der Kompilierungsgeschwindigkeit).

Da das Tippen länger dauert, verwende ich persönlich ein Tool, um all das auf sehr schnelle Weise zu generieren (Visual Assist X).

Klaim
quelle
Optimiert Visual Studio die # include-Wachen nicht wie sie sind? Andere (bessere?) Compiler tun dies, und ich stelle mir vor, dass es ziemlich einfach ist.
Tom
1
Warum setzen Sie das pragmanach dem ifndef? Gibt es einen Vorteil?
user1095108
1
@ user1095108 Einige Compiler verwenden die Header-Schutzvorrichtungen als Trennzeichen, um festzustellen, ob die Datei nur Code enthält, der einmal instanziiert werden muss. Wenn sich ein Code außerhalb der Header-Schutzvorrichtungen befindet, wird die gesamte Datei möglicherweise mehr als einmal als instanziierbar angesehen. Wenn derselbe Compiler Pragma nicht einmal unterstützt, wird diese Anweisung ignoriert. Daher ist das Einfügen des Pragmas in die Header-Schutzvorrichtungen die allgemeinste Methode, um sicherzustellen, dass zumindest die Header-Schutzvorrichtungen "optimiert" werden können.
Klaim
4

Nicht immer.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52566 enthält ein schönes Beispiel für zwei Dateien, die beide enthalten sein sollen, aber aufgrund identischer Zeitstempel und Inhalte fälschlicherweise als identisch angesehen werden (nicht identischer Dateiname). .

Omer
quelle
10
Das wäre ein Fehler im Compiler. (Der Versuch, eine Abkürzung zu nehmen, sollte nicht dauern).
Rxantos
4
#pragma onceist nicht Standard, also ist alles, was ein Compiler entscheidet, "richtig". Dann können wir natürlich darüber sprechen, was "erwartet" und was "nützlich" ist.
user7610
2

Verwenden von gcc 3.4 und 4.1 bei sehr großen Bäumen (manchmal unter Verwendung von distcc ) habe ich noch keine Beschleunigung festgestellt, wenn #pragma einmal anstelle oder in Kombination mit Standard-Include-Wachen verwendet wird.

Ich sehe wirklich nicht ein, wie es sich lohnt, ältere Versionen von gcc oder sogar andere Compiler zu verwirren, da es keine wirklichen Einsparungen gibt. Ich habe nicht alle verschiedenen Entlinter ausprobiert, aber ich bin bereit zu wetten, dass dies viele von ihnen verwirren wird.

Ich wünschte auch, es wäre früh angenommen worden, aber ich kann das Argument "Warum brauchen wir das, wenn ifndef einwandfrei funktioniert?" Sehen. Angesichts der vielen dunklen Ecken und Komplexitäten von C sind Wachen eines der einfachsten und selbsterklärendsten Dinge. Wenn Sie nur ein kleines Wissen über die Funktionsweise des Präprozessors haben, sollten diese selbsterklärend sein.

Wenn Sie jedoch eine erhebliche Beschleunigung feststellen, aktualisieren Sie bitte Ihre Frage.

Tim Post
quelle
2

Heutzutage sind Wachen der alten Schule so schnell wie ein #Pragma. Selbst wenn der Compiler sie nicht speziell behandelt, stoppt er dennoch, wenn #ifndef WHATEVER und WHATEVER definiert sind. Das Öffnen einer Datei ist heute spottbillig. Selbst wenn es eine Verbesserung geben würde, würde dies in der Größenordnung von Millisekunden liegen.

Ich benutze #pragma einfach nicht einmal, da es keinen Nutzen hat. Um Konflikte mit anderen Include-Wachen zu vermeiden, verwende ich Folgendes: CI_APP_MODULE_FILE_H -> CI = Company Initials; APP = Anwendungsname; der Rest ist selbsterklärend.

CMircea
quelle
19
Ist der Vorteil nicht, dass es viel weniger tippt?
Andrey
1
Beachten Sie jedoch, dass einige Millisekunden hunderttausend Mal einige Minuten sind. Große Projekte bestehen aus zehntausenden Dateien mit jeweils zehn Headern. Angesichts der heutigen Mehrkern-CPUs ist die Eingabe / Ausgabe, insbesondere das Öffnen vieler kleiner Dateien , einer der größten Engpässe.
Damon
1
"Heute sind Wachen der alten Schule so schnell wie ein #Pragma." Heute und auch vor vielen, vielen Jahren. Die ältesten Dokumente auf der GCC-Website sind für 2,95 ab 2001 und die Optimierung der Include-Wachen war damals nicht neu. Es ist keine aktuelle Optimierung.
Jonathan Wakely
4
Der Hauptvorteil besteht darin, dass Wachen fehleranfällig und wortreich sind. Es ist zu einfach, zwei verschiedene Dateien mit identischen Namen in verschiedenen Verzeichnissen zu haben (und die Include-Guards können dasselbe Symbol sein) oder beim Kopieren von Include-Guards Fehler beim Kopieren und Einfügen zu machen. Pragma ist früher weniger fehleranfällig und funktioniert auf allen wichtigen PC-Plattformen. Wenn Sie es verwenden können, ist es besser Stil.
AHelps
2

Der Hauptunterschied besteht darin, dass der Compiler die Header-Datei öffnen musste, um den Include Guard zu lesen. Im Vergleich dazu bewirkt Pragma einmal, dass der Compiler die Datei verfolgt und keine Datei-E / A ausführt, wenn er auf ein anderes Include für dieselbe Datei stößt. Das mag vernachlässigbar klingen, kann aber leicht mit großen Projekten skaliert werden, insbesondere mit Projekten ohne guten Header, die Disziplinen enthalten.

Allerdings sind Compiler (einschließlich GCC) heutzutage klug genug, um Wachen einmal wie Pragma zu behandeln. dh sie öffnen die Datei nicht und vermeiden die Strafe für Datei-E / A.

In Compilern, die Pragma nicht unterstützen, habe ich manuelle Implementierungen gesehen, die etwas umständlich sind.

#ifdef FOO_H
#include "foo.h"
#endif

Ich persönlich mag #pragma einmal Ansatz, da es den Aufwand von Namenskollisionen und möglichen Tippfehlern vermeidet. Im Vergleich dazu ist es auch eleganter. Für tragbaren Code sollte es jedoch nicht schaden, beides zu haben, es sei denn, der Compiler beschwert sich darüber.

Shammi
quelle
1
"Allerdings sind Compiler (einschließlich GCC) heutzutage klug genug, um Wachen einmal wie Pragma zu behandeln." Sie machen das schon seit Jahrzehnten, vielleicht länger als je #pragma oncezuvor!
Jonathan Wakely
Denke, du hast mich missverstanden. Ich wollte vor Pragma einmal sagen, dass alle Compiler mehrere E / A-Strafen für dieselbe h-Datei erleiden würden, die während der Präprozessorphase mehrmals enthalten waren. Moderne Implementierungen verwenden eine bessere Zwischenspeicherung von Dateien in der Präprozessorphase. Unabhängig davon, ohne Pragmas, enthält die Präprozessor-Phase immer noch alles außerhalb der Include-Wachen. Bei einmaligem Pragma wird die gesamte Datei weggelassen. Von diesem Standpunkt aus ist Pragma immer noch vorteilhaft.
Shammi
1
Nein, das ist falsch. Anständige Compiler lassen die gesamte Datei auch ohne #pragma einmal aus. Sie öffnen die Datei nicht ein zweites Mal und sehen sie sich nicht einmal ein zweites Mal an, siehe gcc.gnu.org/onlinedocs/. cpp / Once-Only-Headers.html (dies hat nichts mit Caching zu tun).
Jonathan Wakely
1
Aus Ihrem Link geht hervor, dass die Optimierung nur in cpp erfolgt. Unabhängig davon kommt das Caching ins Spiel. Woher weiß der Präprozessor, dass er Code außerhalb der Wachen einfügt? Beispiel ... extern int foo; #ifndef INC_GUARD #define INC_GUARD Klasse ClassInHeader {}; #endif In diesem Fall muss der Präprozessor extern int foo enthalten. mehrmals, wenn Sie dieselbe Datei mehrmals einfügen. Ende des Tages, es macht nicht viel Sinn, darüber zu streiten, solange wir den Unterschied zwischen #pragma einmal und Include-Wachen verstehen und wie sich verschiedene Compiler mit beiden verhalten :)
Shammi
1
Die Optimierung wird dabei offensichtlich nicht angewendet.
Jonathan Wakely
1

Wenn wir msvc oder Qt (bis zu Qt 4.5) verwenden, da GCC (bis zu 3.4), msvc beide unterstützen #pragma once, kann ich keinen Grund sehen, nicht zu verwenden#pragma once .

Der Name der Quelldatei ist normalerweise der gleiche wie der Klassenname, und wir wissen, dass wir manchmal einen Refactor benötigen , um den Klassennamen umzubenennen. Dann mussten wir auch den Namen ändern #include XXXX, daher denke ich, dass die manuelle Pflege #include xxxxxkeine kluge Arbeit ist. Selbst mit der Visual Assist X-Erweiterung ist die Pflege von "xxxx" keine notwendige Arbeit.

Raidsan
quelle
1

Zusätzlicher Hinweis für die Leute, die denken, dass eine automatische einmalige Einbeziehung von Header-Dateien immer erwünscht ist: Ich erstelle seit Jahrzehnten Codegeneratoren mit doppelter oder mehrfacher Einbeziehung von Header-Dateien. Insbesondere für die Generierung von Protokollbibliotheks-Stubs finde ich es sehr komfortabel, einen extrem portablen und leistungsstarken Codegenerator ohne zusätzliche Tools und Sprachen zu haben. Ich bin nicht der einzige Entwickler, der dieses Schema verwendet, wie die X-Macros in diesem Blog zeigen. Dies wäre ohne die fehlende automatische Bewachung nicht möglich.

Marcel
quelle
Könnten C ++ - Vorlagen das Problem lösen? Aufgrund der C ++ - Vorlagen finde ich selten einen gültigen Bedarf an Makros.
Klarer
1
Unsere langjährige Berufserfahrung zeigt, dass wir als Dienstleister (Embedded Systems) durch die ständige Nutzung einer ausgereiften Sprach-, Software- und Tool-Infrastruktur einen enormen Vorteil in Bezug auf Produktivität und Flexibilität erzielen. Konkurrenten, die stattdessen C ++ - basierte Software und Stacks für eingebettete Systeme entwickeln, finden einige ihrer Entwickler möglicherweise zufriedener bei der Arbeit. In der Regel übertreffen wir sie jedoch mehrmals in Bezug auf Markteinführungszeit, Funktionalität und Flexibilität. Nether unterschätzen Produktivitätsgewinne, wenn immer wieder ein und dasselbe Tool verwendet wird. Web-Entwickler leiden stattdessen unter Wegen zu vielen Frameworks.
Marcel
Ein Hinweis: Enthält nicht einmal Wachen / # Pragma in jeder Header-Datei, was dem DRY-Prinzip selbst widerspricht. Ich kann Ihren Standpunkt in der X-MacroFunktion sehen, aber es ist nicht die Hauptverwendung von include. Sollte es nicht umgekehrt sein wie Header unguard / # pragma multi, wenn wir bei DRY bleiben?
Caiohamamura
DRY steht für "Wiederhole dich nicht". Es geht um den Menschen. Was die Maschine tut, hat nichts mit diesem Paradigma zu tun. C ++ - Vorlagen wiederholen sich häufig, C-Compiler tun dies ebenfalls (z. B. Abrollen von Schleifen), und jeder Computer wiederholt fast alles, was unglaublich oft und schnell ist.
Marcel