Was bringt es dem Compiler, die Datei nur einmal einzuschließen? Wäre es nicht standardmäßig sinnvoll? Gibt es überhaupt einen Grund, eine einzelne Datei mehrmals einzuschließen? Warum nicht einfach davon ausgehen? Hat es mit spezifischer Hardware zu tun?
81
#ifdefs
. Also könnte man sagen#define MODE_ONE 1
und dann#include "has-modes.h"
und dann#undef MODE_ONE
mit#define MODE_TWO 1
und#include "has-modes.h"
wieder. Der Präprozessor ist in Bezug auf diese Art von Dingen agnostisch, und vielleicht können sie manchmal Sinn machen.<assert.h>
mehrere Male mit unterschiedlichen DefinitionenNDEBUG
in dieselbe Quelldatei aufnehmen.#pragma once
selbst gibt es Hardware - Umgebungen ( in der Regel mit vernetzten Antrieben und können mehrere Pfade zu demselben Header) , wo es nicht richtig funktioniert.#pragma once
angenommen haben, wie können Sie diesem Standard widersprechen?#pragma many
? Wie viele Compiler haben so etwas implementiert?Antworten:
Hier gibt es mehrere verwandte Fragen:
Warum wird
#pragma once
nicht automatisch erzwungen?Weil es Situationen gibt, in denen Sie Dateien mehrmals einschließen möchten.
Warum sollten Sie eine Datei mehrmals einfügen?
In anderen Antworten wurden mehrere Gründe angegeben (Boost.Preprocessor, X-Macros, einschließlich Datendateien). Ich möchte ein besonderes Beispiel für "Vermeiden von Codeduplizierungen" hinzufügen: OpenFOAM fördert einen Stil, bei dem
#include
es ein gängiges Konzept ist, Teile innerhalb von Funktionen zu verwenden. Siehe zum Beispiel diese Diskussion.Ok, aber warum ist es nicht die Standardeinstellung mit einem Opt-out?
Weil es nicht durch den Standard spezifiziert ist.
#pragma
s sind per Definition implementierungsspezifische Erweiterungen.Warum ist es noch
#pragma once
kein standardisiertes Feature geworden (da es weitgehend unterstützt wird)?Weil es überraschend schwierig ist, die "gleiche Datei" plattformunabhängig festzuhalten. Weitere Informationen finden Sie in dieser Antwort .
quelle
once
pragma once
Fehler aufgetreten sind, aber Wachen eingeschlossen wären. Das Identifizieren von Dateien nach Speicherort funktioniert auch nicht, da dieselbe Datei in einem Projekt manchmal mehrmals vorkommt (z. B. haben Sie zwei Submodule, die beide eine Nur-Header-Bibliothek in ihren Headern enthalten und ihre eigene Kopie davon#pragma STDC
Familie . Sie alle steuern jedoch das implementierungsdefinierte Verhalten.#ifndef XX
, muss er nicht wissen, ob nach dem Entsprechenden etwas folgt,#endif
bis er die gesamte Datei gelesen hat. Ein Compiler, der#ifndef
nachverfolgt, ob der äußerste die gesamte Datei einschließt und feststellt, welches Makro er überprüft, kann möglicherweise ein erneutes Scannen der Datei vermeiden. Eine Anweisung, zu sagen, dass nach dem aktuellen Punkt nichts von Bedeutung ist, scheint jedoch sinnvoller zu sein, als sich auf Compiler zu verlassen erinnere dich an solche Dinge.Sie können
#include
überall in einer Datei verwenden, nicht nur im globalen Bereich - wie innerhalb einer Funktion (und bei Bedarf mehrmals). Sicher, hässlich und nicht gut, aber möglich und gelegentlich sinnvoll (abhängig von der Datei, die Sie einschließen). Wenn#include
es nur eine einmalige Sache wäre, würde das nicht funktionieren.#include
macht doch nur dumme Textersetzung (cut'n'paste), und nicht alles, was Sie einschließen, muss eine Header-Datei sein. Sie können beispielsweise#include
eine Datei mit automatisch generierten Daten enthalten, die die Rohdaten zum Initialisieren von a enthaltenstd::vector
. Mögenstd::vector<int> data = { #include "my_generated_data.txt" }
Und muss "my_generated_data.txt" etwas sein, das vom Build-System während der Kompilierung generiert wird.
Oder vielleicht bin ich faul / albern / dumm und füge dies in eine Datei ein ( sehr erfundenes Beispiel):
const noexcept;
und dann tue ich es
class foo { void f1() #include "stupid.file" int f2(int) #include "stupid.file" };
Ein anderes, etwas weniger ausgeklügeltes Beispiel wäre eine Quelldatei, in der viele Funktionen eine große Anzahl von Typen in einem Namespace verwenden müssen, aber Sie möchten nicht nur
using namespace foo;
global sagen, da dies den globalen Namespace mit vielen anderen Dingen verschmutzen würde du willst nicht Sie erstellen also eine Datei "foo" mitusing std::vector; using std::array; using std::rotate; ... You get the idea ...
Und dann tun Sie dies in Ihrer Quelldatei
void f1() { #include "foo" // needs "stuff" } void f2() { // Doesn't need "stuff" } void f3() { #include "foo" // also needs "stuff" }
Hinweis: Ich befürworte nicht, solche Dinge zu tun. Aber es ist möglich und in einigen Codebasen erledigt und ich verstehe nicht, warum es nicht erlaubt sein sollte. Es hat seine Verwendung.
Es kann auch sein, dass sich die von Ihnen eingeschlossene Datei je nach Wert bestimmter Makros unterschiedlich verhält
#define
. Daher möchten Sie die Datei möglicherweise an mehreren Speicherorten einfügen, nachdem Sie zuerst einen Wert geändert haben, damit Sie in verschiedenen Teilen Ihrer Quelldatei ein unterschiedliches Verhalten erhalten.quelle
#define
s vor jedem Include ändere, der das Verhalten der enthaltenen Datei ändert, muss ich ihn möglicherweise mehrmals einschließen, um diese unterschiedlichen Verhaltensweisen in verschiedenen Teilen meiner Quelldatei zu erhalten.Das mehrfache Einschließen ist zB mit der X-Makro- Technik möglich:
data.inc:
X(ONE) X(TWO) X(THREE)
use_data_inc_twice.c
enum data_e { #define X(V) V, #include "data.inc" #undef X }; char const* data_e__strings[]={ #define X(V) [V]=#V, #include "data.inc" #undef X };
Ich weiß nichts über eine andere Verwendung.
quelle
#pragma once
wäre das Mandatieren von Verhalten eine bahnbrechende Änderung.Sie scheinen unter der Annahme zu arbeiten, dass der Zweck der Funktion "#include", die sogar in der Sprache vorhanden ist, darin besteht, die Zerlegung von Programmen in mehrere Kompilierungseinheiten zu unterstützen. Das ist falsch.
Es kann diese Rolle übernehmen, aber das war nicht der beabsichtigte Zweck. C wurde ursprünglich als etwas höhere Sprache als PDP-11 Macro-11 Assembly für die Neuimplementierung von Unix entwickelt. Es hatte einen Makro-Präprozessor, da dies eine Funktion von Macro-11 war. Es war in der Lage, Makros aus einer anderen Datei in Textform einzuschließen, da dies eine Funktion von Macro-11 war, die das vorhandene Unix, das sie auf ihren neuen C-Compiler portierten, verwendet hatte.
Nun stellt sich heraus, dass "#include" nützlich ist , um Code in Kompilierungseinheiten zu unterteilen, als (wohl) ein bisschen wie ein Hack. Die Tatsache jedoch, dass dieser Hack bestanden bedeutet , dass es wurde der Weg, der in C. Die Tatsache erfolgt , dass ein Weg , kein neues Verfahren jemals existierte gemeint benötigt geschaffen werden , um diese Funktionalität bereitzustellen, so dass nichts sicherer (zB: nicht anfällig für mehr -einschluss) wurde jemals erstellt. Da es bereits in C war, wurde es zusammen mit den meisten anderen Syntaxen und Redewendungen von C in C ++ kopiert.
Es gibt einen Vorschlag, C ++ ein geeignetes Modulsystem zu geben, damit auf diesen 45 Jahre alten Präprozessor-Hack endlich verzichtet werden kann. Ich weiß allerdings nicht, wie unmittelbar dies ist. Ich habe seit mehr als einem Jahrzehnt davon gehört, dass es in Arbeit ist.
quelle
Nein, dies würde die Optionen, die beispielsweise Bibliotheksautoren zur Verfügung stehen, erheblich beeinträchtigen. Mit Boost.Preprocessor können beispielsweise Vorprozessorschleifen verwendet werden. Diese können nur durch mehrere Einschlüsse derselben Datei erreicht werden.
Und Boost.Preprocessor ist ein Baustein für viele sehr nützliche Bibliotheken.
quelle
#pragma reentrant
oder ähnliches bereitzustellen . Rückblick ist 20/20.once
oderreentrant
ist, um dieses oder andere potenzielle Probleme zu verringern.In der Firmware für das Produkt, an dem ich hauptsächlich arbeite, müssen wir angeben können, wo Funktionen und globale / statische Variablen im Speicher zugewiesen werden sollen. Die Echtzeitverarbeitung muss im L1-Speicher auf dem Chip gespeichert sein, damit der Prozessor direkt und schnell darauf zugreifen kann. Eine weniger wichtige Verarbeitung kann im L2-Speicher auf dem Chip erfolgen. Und alles, was nicht besonders schnell erledigt werden muss, kann in der externen DDR leben und das Caching durchlaufen, da es keine Rolle spielt, ob es etwas langsamer ist.
Das # Pragma, um zuzuweisen, wohin die Dinge gehen, ist eine lange, nicht triviale Linie. Es wäre leicht, etwas falsch zu machen. Die Wirkung der es falsch bekommen wäre, dass die Code / Daten würden lautlos in Standard (DDR) Speicher abgelegt werden, und die Wirkung , dass Closed-Loop werden könnten Steuer Anhalten ohne Grund arbeiten , die leicht zu sehen ist.
Also benutze ich Include-Dateien, die genau dieses Pragma enthalten. Mein Code sieht jetzt so aus.
Header-Datei...
#ifndef HEADERFILE_H #define HEADERFILE_H #include "set_fast_storage.h" /* Declare variables */ #include "set_slow_storage.h" /* Declare functions for initialisation on startup */ #include "set_fast_storage.h" /* Declare functions for real-time processing */ #include "set_storage_default.h" #endif
Und Quelle ...
#include "headerfile.h" #include "set_fast_storage.h" /* Define variables */ #include "set_slow_storage.h" /* Define functions for initialisation on startup */ #include "set_fast_storage.h" /* Define functions for real-time processing */
Sie werden dort mehrere Einschlüsse derselben Datei bemerken, sogar nur in der Kopfzeile. Wenn ich jetzt etwas falsch schreibe, teilt mir der Compiler mit, dass die Include-Datei "set_fat_storage.h" nicht gefunden werden kann, und ich kann das Problem leicht beheben.
Als Antwort auf Ihre Frage ist dies ein reales, praktisches Beispiel dafür, wo eine mehrfache Einbeziehung erforderlich ist.
quelle
_Pragma
Richtlinie. Dieselben Pragmas können jetzt aus regulären Makros erweitert werden. Es muss also nicht mehr als einmal eingeschlossen werden.