c++ verzeichnis - Was ist der Unterschied zwischen#include<filename>und#include "filename"?




path includes (25)

Ein # include mit spitzen Klammern sucht eine "implementierungsabhängige Liste von Orten" (was eine sehr komplizierte Art ist, "System-Header" zu sagen) für die Datei, die enthalten sein soll.

Ein # include mit Anführungszeichen wird nur nach einer Datei suchen (und "in einer implementierungsabhängigen Weise", bleh). Das bedeutet, dass in normalem Englisch versucht wird, den Pfad / Dateiname anzuwenden, den Sie darauf werfen, und nicht einen Systempfad vorwegnimmt oder anderweitig manipuliert.

Wenn #include "" fehlschlägt, wird es vom Standard als #include <> erneut gelesen.

Die g++ hat eine (compilerspezifische) Beschreibung, die, obwohl sie für gcc und nicht für den Standard spezifisch ist, viel einfacher zu verstehen ist als die Anwaltsstil-Diskussion der ISO-Standards.

In den C- und C ++ - Programmiersprachen, was ist der Unterschied zwischen der Verwendung von spitzen Klammern und der Verwendung von Anführungszeichen in einer include Anweisung, wie folgt?

  1. #include <filename>
  2. #include "filename"

Die GCC-Dokumentation sagt Folgendes über den Unterschied zwischen den beiden:

Sowohl die Benutzer- als auch die Systemheaderdateien werden mit der Vorverarbeitungsdirektive '#include' eingebunden. Es hat zwei Varianten:

#include <file>

Diese Variante wird für Systemheaderdateien verwendet. Es sucht nach einer Datei namens Datei in einer Standardliste von Systemverzeichnissen. Sie können Verzeichnisse mit der Option -I (siehe Invocation ) dieser Liste voranstellen.

#include "file"

Diese Variante wird für Header-Dateien Ihres eigenen Programms verwendet. Es sucht zuerst in dem Verzeichnis, das die aktuelle Datei enthält, nach einer Datei mit dem Namen datei, dann in den Zitatverzeichnissen und dann nach den gleichen Verzeichnissen, die für <file> . Mit der Option -iquote können Sie Verzeichnisse der Liste der -iquote voranstellen. Das Argument von '#include' , ob mit Begrenzungszeichen oder spitzen Klammern getrennt, verhält sich wie eine Zeichenkettenkonstante, da Kommentare nicht erkannt werden und Makronamen nicht erweitert werden. Daher gibt #include <x/*y> die Einbindung einer System-Header-Datei mit dem Namen x/*y .

Wenn Backslashes in der Datei auftreten, werden sie jedoch als normale Textzeichen und nicht als Escape-Zeichen betrachtet. Keine der Zeichenfolgenfolgen, die für Zeichenkettenkonstanten in C geeignet sind, wird verarbeitet. Daher gibt #include "x\n\\y" einen Dateinamen mit drei umgekehrten Schrägstrichen an. (Einige Systeme interpretieren '\' als Trennzeichen für Pfadnamen. Alle interpretieren auch '/' dieselbe Weise. Es ist am tragbarsten, nur '/' .)

Es ist ein Fehler, wenn sich in der Zeile nach dem Dateinamen etwas anderes als Kommentare befindet.


Viele der Antworten hier konzentrieren sich auf die Pfade, nach denen der Compiler sucht, um die Datei zu finden. Während dies die meisten Compiler tun, ist es einem konformen Compiler erlaubt, mit den Effekten der Standard-Header vorprogrammiert zu sein und #include <list> als einen Schalter zu behandeln, und er muss überhaupt nicht als eine Datei existieren.

Dies ist nicht rein hypothetisch. Es gibt mindestens einen Compiler, der auf diese Weise funktioniert. Es wird empfohlen, #include <xxx> nur mit Standard-Headern zu verwenden.


The order of search header files is different. <XXX.h> prefer to search the standard headers first while "XXX.h" searches the workspace's header files first.


Das Include <file> weist den Präprozessor an, zuerst in -I Verzeichnissen und in vordefinierten Verzeichnissen zu suchen und dann im Verzeichnis der .c-Datei. Das Include "file" weist den Präprozessor an, zuerst das Verzeichnis der Quelldatei zu durchsuchen und dann zu -I und vordefiniert. Alle Ziele werden trotzdem gesucht, nur die Reihenfolge der Suche ist unterschiedlich.

Der 2011-Standard behandelt hauptsächlich die Include-Dateien in "16.2 Quelldateieinschluss".

2 Eine Vorverarbeitungsanweisung des Formulars

# include <h-char-sequence> new-line

Durchsucht eine Folge von implementierungsdefinierten Positionen nach einem Header, der durch die angegebene Sequenz eindeutig zwischen den Begrenzern <und> definiert ist, und bewirkt, dass diese Direktive durch den gesamten Inhalt des Headers ersetzt wird. Wie die Orte angegeben werden oder der Header identifiziert wird, ist implementierungsdefiniert.

3 Eine Vorverarbeitungsanweisung des Formulars

# include "q-char-sequence" new-line

bewirkt die Ersetzung dieser Anweisung durch den gesamten Inhalt der Quelldatei, die durch die angegebene Sequenz zwischen den "Trennzeichen" identifiziert wird. Die benannte Quelldatei wird in einer implementierungsdefinierten Weise gesucht. Wenn diese Suche nicht unterstützt wird oder die Suche fehlschlägt Die Richtlinie wird so aufbereitet, als ob sie gelesen würde

# include <h-char-sequence> new-line

mit der identischen enthaltenen Sequenz (einschließlich> Zeichen, falls vorhanden) von der ursprünglichen Anweisung.

Beachten Sie, dass das Formular "xxx" zu <xxx> reduziert wird, wenn die Datei nicht gefunden wird. Der Rest ist implementierungsdefiniert.


Die einfache allgemeine Regel besteht darin, spitze Klammern zu verwenden, um Header-Dateien einzuschließen, die mit dem Compiler geliefert werden. Verwenden Sie doppelte Anführungszeichen, um andere Headerdateien einzuschließen. Die meisten Compiler machen es so.

1.9 - Header-Dateien erklären ausführlicher die Präprozessor-Anweisungen. Wenn Sie ein Anfänger Programmierer sind, sollte diese Seite Ihnen helfen, all das zu verstehen. Ich habe es von hier gelernt, und ich habe es bei der Arbeit verfolgt.


When you use #include <filename>, the pre-processor looking for the file in directtory of C\C++ header files (stdio.h\cstdio, string, vector, etc.). But, when you use #include "filename": first, the pre-processor looking for the file in the current directory, and if it doesn't here - he looking for it in the directory of C\C++ header files.


Zumindest für die GCC-Version <= 3.0 erzeugt das Winkelklammerformular keine Abhängigkeit zwischen der enthaltenen Datei und der einschließenden Datei.

Wenn Sie also Abhängigkeitsregeln generieren möchten (z. B. mit der Option GCC-M), müssen Sie das in Anführungszeichen gesetzte Formular für die Dateien verwenden, die in der Abhängigkeitsstruktur enthalten sein sollen.

(Siehe http://gcc.gnu.org/onlinedocs/cpp/Invocation.html )


#include "filename" // User defined header
#include <filename> // Standard library header.

Beispiel:

Der Dateiname hier ist Seller.h :

#ifndef SELLER_H     // Header guard
#define SELLER_H     // Header guard

#include <string>
#include <iostream>
#include <iomanip>

class Seller
{
    private:
        char name[31];
        double sales_total;

    public:
        Seller();
        Seller(char[], double);
        char*getName();

#endif

In der Klassenimplementierung (beispielsweise Seller.cpp und in anderen Dateien, die die Datei Seller.h ) sollte der vom Benutzer definierte Header jetzt wie folgt Seller.h werden:

#include "Seller.h"

  • #include <> ist für vordefinierte Header-Dateien

Wenn die Header-Datei vordefiniert ist, würden Sie einfach den Namen der Header-Datei in eckige Klammern schreiben und es würde so aussehen (vorausgesetzt, wir haben einen vordefinierten Header-Dateinamen namens iostream):

#include <iostream>
  • #include " " ist für Header-Dateien, die der Programmierer definiert

Wenn Sie (der Programmierer) Ihre eigene Header-Datei geschrieben haben, würden Sie den Header-Dateinamen in Anführungszeichen schreiben. Angenommen, Sie haben eine Header-Datei mit dem Namen myfile.h , dann ist dies ein Beispiel dafür, wie Sie die include-Anweisung verwenden würden, um diese Datei myfile.h :

#include "myfile.h"

To include a predefined library header file , #include<filename> is used whereas to include user defined header file, #include "filename" is relevant.


#include <filename>

will find the corresponding file from the C++ library. it means if you have a file called hello.h in the C++ library folder, #include <hello.h> will load it.

Aber,

#include "filename"

will find the file in the same directory where your source file is.

In addition,

#include "path_to_file/filename"

will find the file in the directory which you typed in path_to_file .


#include <file> tells the compiler to search for the header in its includes directory, eg for MinGW the compiler would search for file in C:\MinGW\include\ or wherever your compiler is installed.

#include "file" tells the compiler to search the current directory (ie the directory in which the source file resides) for file .

You can use the -I flag for GCC to tell it that, when it encounters an include with angled brackets, it should also search for headers in the directory after -I . For instance, if you have a file called myheader.h in your own directory, you could say #include <myheader.h> if you called GCC with -I . (indicating that it should search for includes in the current directory.)


Fügen Sie in C ++ eine Datei auf zwei Arten ein:

Die erste ist #include, die den Präprozessor anweist, nach der Datei im vordefinierten Standardspeicherort zu suchen. Dieser Speicherort ist häufig eine INCLUDE-Umgebungsvariable, die den Pfad zum Einschließen von Dateien angibt.

Und der zweite Typ ist #include "filename", der den Präprozessor anweist, zuerst nach der Datei im aktuellen Verzeichnis zu suchen und dann nach den vordefinierten Orten zu suchen, die der Benutzer eingerichtet hat.


In der Praxis liegt der Unterschied an der Stelle, an der der Präprozessor nach der enthaltenen Datei sucht.

Für #include <filename> sucht der Präprozessor in einer implementierungsabhängigen Weise, normalerweise in Suchverzeichnissen, die vom Compiler / IDE vorher festgelegt wurden. Diese Methode wird normalerweise verwendet, um Header-Dateien der Standardbibliothek einzuschließen.

Für #include "filename" sucht der Präprozessor zuerst im selben Verzeichnis wie die Datei, die die Direktive enthält, und folgt dann dem Suchpfad, der für das Formular #include <filename> wird. Diese Methode wird normalerweise verwendet, um vom Programmierer definierte Header-Dateien einzuschließen.

Eine ausführlichere Beschreibung finden Sie in der GCC- Dokumentation zu Suchpfaden .


Für #include "" durchsucht ein Compiler normalerweise den Ordner der Datei, die diesen Include und dann die anderen Ordner enthält. Für #include <> durchsucht der Compiler den Ordner der aktuellen Datei nicht.


Für #include "Dateiname" sucht der Präprozessor im selben Verzeichnis wie die Datei, die die Direktive enthält. Diese Methode wird normalerweise verwendet, um vom Programmierer definierte Header-Dateien einzuschließen.

Für # enthält der Präprozessor Suchen in einer implementierungsabhängigen Weise, normalerweise in Suchverzeichnissen, die vom Compiler / IDE vorher festgelegt wurden. This method is normally used to include standard library header files.


Es tut:

"mypath/myfile" is short for ./mypath/myfile

mit . Dies ist entweder das Verzeichnis der Datei, in der das #include enthalten ist, und / oder das aktuelle Arbeitsverzeichnis des Compilers und / oder die default_include_paths

und

<mypath/myfile> is short for <defaultincludepaths>/mypath/myfile

Wenn ./ in <default_include_paths> , macht das keinen Unterschied.

Wenn sich mypath/myfile in einem anderen Include-Verzeichnis befindet, ist das Verhalten nicht definiert.


#include <filename>

wird verwendet, wenn Sie die Header-Datei des C / C ++ - Systems oder der Compiler-Bibliotheken verwenden möchten. Diese Bibliotheken können stdio.h, string.h, math.h usw. sein.

#include "path-to-file/filename"

wird verwendet, wenn Sie Ihre eigene benutzerdefinierte Header-Datei verwenden möchten, die sich in Ihrem Projektordner oder an einem anderen Ort befindet.

Weitere Informationen zu Präprozessoren und Header. Lesen Sie C - Präprozessoren .


Die Zeichenfolge zwischen <und> bezieht sich eindeutig auf eine Kopfzeile, bei der es sich nicht notwendigerweise um eine Datei handelt. Implementierungen sind ziemlich frei, die Zeichenfolge zu verwenden, wie sie möchten. (Behandeln Sie es jedoch meistens als Dateinamen und führen Sie eine Suche im Include-Pfad durch , wie in den anderen Posts angegeben.)

Wenn das Formular #include "file" verwendet wird, sucht die Implementierung zuerst nach einer Datei mit dem angegebenen Namen, sofern diese unterstützt wird. Wenn nicht (unterstützt) oder wenn die Suche fehlschlägt, verhält sich die Implementierung so, als ob das andere Formular ( #include <file> ) verwendet wurde.

Außerdem existiert ein drittes Formular und wird verwendet, wenn die #include Direktive nicht mit einem der obigen Formulare übereinstimmt. In dieser Form wird einige grundlegende Vorverarbeitung (wie Makroexpansion) für die "Operanden" der #include Direktive durchgeführt, und das Ergebnis wird voraussichtlich mit einem der beiden anderen Formen übereinstimmen.


Einige gute Antworten verweisen hier auf den C-Standard, haben aber den POSIX-Standard vergessen, insbesondere das spezifische Verhalten des c99-Befehls (zB C-Compiler) .

Laut der Open Group Base Spezifikationen Ausgabe 7 ,

-I Verzeichnis

Ändern Sie den Algorithmus für die Suche nach Headern, deren Namen keine absoluten Pfadnamen sind, in das Verzeichnis, das durch den Verzeichnispfadnamen benannt wird, bevor Sie an den üblichen Stellen suchen. Daher müssen Header, deren Namen in Anführungszeichen ("") eingeschlossen sind, zuerst im Verzeichnis der Datei mit der Zeile #include gesucht werden, dann in Verzeichnissen, die in -I Optionen benannt sind, und zuletzt an den üblichen Stellen. Bei Kopfzeilen, deren Namen in spitze Klammern eingeschlossen sind ("<>"), darf die Kopfzeile nur in Verzeichnissen gesucht werden, die in -I Optionen und dann an den üblichen Stellen benannt sind. In -I- Optionen benannte Verzeichnisse müssen in der angegebenen Reihenfolge durchsucht werden. Implementierungen müssen mindestens zehn Instanzen dieser Option in einem einzelnen c99- Befehlsaufruf unterstützen.

In einer POSIX-konformen Umgebung wird #include "file.h" mit einem POSIX-kompatiblen C-Compiler wahrscheinlich zuerst nach ./file.h suchen . ist das Verzeichnis, in dem sich die Datei mit der #include Anweisung befindet, während #include <file.h> wahrscheinlich zuerst nach /usr/include/file.h , wobei /usr/include für das System übliche Orte sind Header (es scheint nicht von POSIX definiert).


the " < filename > " searches in standard C library locations

whereas "filename" searches in the current directory as well.

Ideally, you would use <...> for standard C libraries and "..." for libraries that you write and are present in the current directory.


Danke für die tollen Antworten, insb. Adam Stelmaszczyk und piCookie und aib.

Wie bei vielen Programmierern habe ich die informelle Konvention verwendet, das "myApp.hpp" für anwendungsspezifische Dateien und das <libHeader.hpp> für Bibliotheks- und Compiler-Systemdateien zu verwenden, dh Dateien, die in /I und der Umgebungsvariablen INCLUDE sind Seit Jahren denke ich, dass das der Standard war.

Der C-Standard gibt jedoch an, dass die Suchreihenfolge implementierungsspezifisch ist, was die Portabilität erschweren kann. Um das Ganze noch schlimmer zu machen, benutzen wir Marmelade, die automatisch herausfindet, wo die Include-Dateien sind. Sie können relative oder absolute Pfade für Ihre Include-Dateien verwenden. dh

#include "../../MyProgDir/SourceDir1/someFile.hpp"

Ältere Versionen von MSVS benötigten doppelte umgekehrte Schrägstriche (\\), aber das ist jetzt nicht erforderlich. Ich weiß nicht, wann es sich geändert hat. Verwenden Sie einfach Schrägstriche für Kompatibilität mit 'nix (Windows wird das akzeptieren).

Wenn Sie sich darüber Sorgen machen, verwenden Sie "./myHeader.h" für eine Include-Datei im selben Verzeichnis wie der Quellcode (mein aktuelles, sehr großes Projekt hat einige doppelte Namen von Include-Dateien verstreut - wirklich ein Problem beim Konfigurationsmanagement) ).

Hier ist die MSDN-Erklärung, die hier für Ihre Bequemlichkeit kopiert wurde.

Zitatformular

Der Präprozessor sucht nach Include-Dateien in dieser Reihenfolge:

  1. In demselben Verzeichnis wie die Datei, die die # include-Anweisung enthält.
  2. In den Verzeichnissen der aktuell geöffneten Include-Dateien, in umgekehrter Reihenfolge in welcher
    Sie wurden geöffnet. Die Suche beginnt im Verzeichnis der übergeordneten Include-Datei und
    weiter nach oben durch die Verzeichnisse aller Großeltern Include-Dateien.
  3. Entlang dem Pfad, der von jeder /I Compiler-Option angegeben wird.
  4. Entlang der Pfade, die von der Umgebungsvariablen INCLUDE angegeben werden.

Winkelbügelform

Der Präprozessor sucht nach Include-Dateien in dieser Reihenfolge:

  1. Entlang dem Pfad, der von jeder /I Compiler-Option angegeben wird.
  2. Wenn das Kompilieren in der Befehlszeile erfolgt, entlang der Pfade, die von der Umgebungsvariablen INCLUDE angegeben werden.

#include <abc.h>

wird verwendet, um Standardbibliotheksdateien einzuschließen. Der Compiler wird also die Speicherorte der Header der Standardbibliothek einchecken.

#include "xyz.h"

sagt dem Compiler, dass er benutzerdefinierte Header-Dateien enthalten soll. Der Compiler sucht also nach diesen Header-Dateien im aktuellen Ordner oder den angegebenen Ordnern.


Ich werde nur die Analogie geben, mit der ich Speicherkonsistenzmodelle (oder kurz Speichermodelle) verstehe. Es ist inspiriert von Leslie Lamports bahnbrechendem Artikel "Zeit, Uhren und die Reihenfolge der Ereignisse in einem verteilten System" . Die Analogie ist zutreffend und hat grundlegende Bedeutung, aber kann für viele Leute übertrieben sein. Ich hoffe jedoch, dass es ein mentales Bild (eine bildliche Darstellung) liefert, das das Nachdenken über Gedächtniskonsistenzmodelle erleichtert.

Betrachten wir die Historien aller Speicherorte in einem Raum-Zeit-Diagramm, in dem die horizontale Achse den Adressraum repräsentiert (dh jeder Speicherplatz wird durch einen Punkt auf dieser Achse repräsentiert) und die vertikale Achse die Zeit darstellt (wir werden sehen, im Allgemeinen gibt es keine universelle Vorstellung von Zeit). Die Historie der Werte, die von jeder Speicherstelle gehalten werden, wird daher durch eine vertikale Spalte bei dieser Speicheradresse dargestellt. Jede Wertänderung ist darauf zurückzuführen, dass einer der Threads einen neuen Wert an diesen Ort schreibt. Mit einem Speicherbild meinen wir die Gesamtheit / Kombination von Werten aller Speicherstellen, die zu einer bestimmten Zeit durch einen bestimmten Thread beobachtbar sind .

Zitat aus "A Primer auf Speicherkonsistenz und Cache-Kohärenz"

Das intuitive (und restriktivste) Speichermodell ist die sequentielle Konsistenz (SC), in der eine Multithread-Ausführung wie eine Verschachtelung der sequentiellen Ausführungen jedes konstituierenden Threads aussehen soll, als ob die Threads auf einem Single-Core-Prozessor zeitgemultiplext wären.

Diese globale Speicherordnung kann von einem Programmlauf zum anderen variieren und ist möglicherweise nicht vorher bekannt. Das charakteristische Merkmal von SC ist der Satz horizontaler Schichten in dem Adressraum-Zeit-Diagramm, die Gleichzeitigkeitsebenen (dh Speicherbilder) darstellen. Auf einer gegebenen Ebene sind alle seine Ereignisse (oder Speicherwerte) gleichzeitig. Es gibt eine Vorstellung von absoluter Zeit , in der alle Threads übereinstimmen, welche Speicherwerte gleichzeitig sind. In SC gibt es zu jedem Zeitpunkt nur ein Speicherbild, das von allen Threads geteilt wird. Das heißt, zu jedem Zeitpunkt stimmen alle Prozessoren auf das Speicherbild (dh den Gesamtspeicherinhalt) zu. Dies bedeutet nicht nur, dass alle Threads für alle Speicherbereiche die gleiche Wertefolge anzeigen, sondern dass alle Prozessoren die gleichen Wertekombinationen aller Variablen beobachten. Dies ist gleichbedeutend damit, dass alle Speicheroperationen (an allen Speicherorten) in der gleichen Gesamtfolge von allen Threads beobachtet werden.

In entspannten Speichermodellen schneidet jeder Thread die Adressraum-Zeit auf seine eigene Weise auf. Die einzige Einschränkung besteht darin, dass sich die Segmente eines Threads nicht kreuzen dürfen, da alle Threads sich auf die Historie jedes einzelnen Speicherplatzes einigen müssen (natürlich , Scheiben verschiedener Fäden können und werden einander kreuzen). Es gibt keine universelle Möglichkeit, sie aufzuteilen (keine privilegierte Foliation von Adresse-Raum-Zeit). Slices müssen nicht planar (oder linear) sein. Sie können gekrümmt sein, und dies kann dazu führen, dass ein Thread die von einem anderen Thread geschriebenen Werte aus der Reihenfolge liest, in der sie geschrieben wurden. Die Histogramme unterschiedlicher Speicherpositionen können bei Betrachtung durch einen bestimmten Thread beliebig verschoben (oder gestreckt) werden . Jeder Thread hat einen unterschiedlichen Sinn dafür, welche Ereignisse (oder äquivalent Speicherwerte) gleichzeitig sind. Die Gruppe von Ereignissen (oder Speicherwerten), die gleichzeitig mit einem Thread ausgeführt werden, ist nicht gleichzeitig mit einem anderen. In einem entspannten Speichermodell beobachten daher alle Threads immer noch die gleiche Historie (dh die Folge von Werten) für jeden Speicherort. Sie können jedoch unterschiedliche Speicherbilder (dh Kombinationen von Werten aller Speicherstellen) beobachten. Selbst wenn zwei verschiedene Speicherplätze nacheinander von demselben Thread geschrieben werden, können die zwei neu geschriebenen Werte in anderer Reihenfolge von anderen Threads beobachtet werden.

[Bild aus Wikipedia]

Leser, die Einsteins Spezielle Relativitätstheorie kennen, werden bemerken, worauf ich anspiele. Die Wörter von Minkowski in das Reich der Speichermodelle übersetzen: Adressraum und Zeit sind Schatten von Adresse-Raum-Zeit. In diesem Fall projiziert jeder Beobachter (dh ein Faden) Schatten von Ereignissen (dh Speicher speichert / lädt) auf seine eigene Weltlinie (dh seine Zeitachse) und seine eigene Ebene der Gleichzeitigkeit (seine Adresse-Raum-Achse). . Threads im C ++ 11-Speichermodell entsprechen Beobachtern , die sich in der speziellen Relativität relativ zueinander bewegen. Sequenzielle Konsistenz entspricht der galiläischen Raumzeit (dh alle Beobachter sind sich einig über eine absolute Reihenfolge von Ereignissen und ein globales Gleichzeitigkeitsgefühl).

Die Ähnlichkeit zwischen Speichermodellen und spezieller Relativitätstheorie rührt von der Tatsache her, dass beide eine teilweise geordnete Menge von Ereignissen definieren, die oft als kausale Menge bezeichnet werden. Einige Ereignisse (z. B. Speicher) können andere Ereignisse beeinflussen (aber nicht davon betroffen sein). Ein C ++ 11-Thread (oder ein Beobachter in der Physik) ist nicht mehr als eine Kette (dh eine vollständig geordnete Menge) von Ereignissen (z. B. lädt und speichert Speicher auf möglicherweise unterschiedliche Adressen).

In der Relativitätstheorie wird eine Ordnung in das scheinbar chaotische Bild von teilweise geordneten Ereignissen wiederhergestellt, da die einzige zeitliche Ordnung, auf die sich alle Beobachter geeinigt haben, die Ordnung unter "zeitartigen" Ereignissen ist (dh jene Ereignisse, die im Prinzip durch jedes langsamere Teilchen verbindbar sind) als die Lichtgeschwindigkeit im Vakuum). Nur die zeitähnlichen Ereignisse sind invariant geordnet. Zeit in der Physik, Craig Callender .

Im C ++ 11-Speichermodell wird ein ähnlicher Mechanismus (das Acquire-Release-Konsistenzmodell) verwendet, um diese lokalen Kausalitätsbeziehungen zu ermitteln .

Um eine Definition der Speicherkonsistenz und eine Motivation für den Abbruch von SC zu geben, werde ich aus "Eine Grundlegendes zur Speicherkonsistenz und Cache-Kohärenz" zitieren.

Bei einer Maschine mit gemeinsam genutztem Speicher definiert das Speicherkonsistenzmodell das architektonisch sichtbare Verhalten seines Speichersystems. Das Korrektheitskriterium für einen einzelnen Prozessorkern teilt das Verhalten zwischen " einem korrekten Ergebnis " und " vielen falschen Alternativen " auf. Dies liegt daran, dass die Architektur des Prozessors vorschreibt, dass die Ausführung eines Threads einen gegebenen Eingabezustand in einen einzigen wohldefinierten Ausgabezustand umwandelt, selbst bei einem Out-of-Order-Core. Shared-Memory-Konsistenzmodelle betreffen jedoch die Lade- und Speichervorgänge mehrerer Threads und ermöglichen normalerweise viele korrekte Ausführungen, während viele (mehr) falsche Ausführungen nicht zulässig sind. Die Möglichkeit mehrerer korrekter Ausführungen ist auf die ISA zurückzuführen, die es erlaubt, mehrere Threads gleichzeitig auszuführen, oft mit vielen möglichen legalen Interleavings von Anweisungen von verschiedenen Threads.

Entspannte oder schwache Speicherkonsistenzmodelle werden durch die Tatsache motiviert, dass die meisten Speicherordnungen in starken Modellen unnötig sind. Wenn ein Thread zehn Datenelemente und dann ein Synchronisierungskennzeichen aktualisiert, ist es den Programmierern normalerweise egal, ob die Datenelemente in der Reihenfolge zueinander aktualisiert werden, sondern nur, dass alle Datenelemente aktualisiert werden, bevor das Flag aktualisiert wird (normalerweise mithilfe von FENCE-Befehlen) ). Entspannte Modelle versuchen, diese erhöhte Bestellflexibilität zu erfassen und nur die Befehle beizubehalten, die Programmierer " benötigen ", um sowohl eine höhere Leistung als auch eine höhere Korrektheit von SC zu erreichen. Zum Beispiel werden in bestimmten Architekturen FIFO-Schreibpuffer von jedem Kern verwendet, um die Ergebnisse von festgeschriebenen (zurückgezogenen) Speichern zu halten, bevor die Ergebnisse in die Caches geschrieben werden. Diese Optimierung verbessert die Leistung, verletzt jedoch SC. Der Schreibpuffer verbirgt die Latenz des Wartens eines Speicherfehlers. Da Geschäfte üblich sind, ist es ein wichtiger Vorteil, bei den meisten von ihnen das Abwürgen zu vermeiden. Bei einem Single-Core-Prozessor kann ein Schreibpuffer architektonisch unsichtbar gemacht werden, indem sichergestellt wird, dass ein Ladevorgang an Adresse A den Wert des letzten Speichers an A zurückgibt, auch wenn sich ein oder mehrere Speicher für A im Schreibpuffer befinden. Dies geschieht typischerweise, indem entweder der Wert des letzten Speichers an A zu dem Ladevorgang von A umgangen wird, wobei "zuletzt" durch die Programmreihenfolge bestimmt wird, oder indem ein Ladevorgang von A angehalten wird, wenn ein Speicher zu A im Schreibpuffer ist . Wenn mehrere Kerne verwendet werden, hat jeder seinen eigenen umgehenden Schreibpuffer. Ohne Schreibpuffer ist die Hardware SC, aber mit Schreibpuffern ist dies nicht der Fall, wodurch Schreibpuffer in einem Multicore-Prozessor architektonisch sichtbar gemacht werden.

Store-Store-Neuordnung kann passieren, wenn ein Kern einen Nicht-FIFO-Schreibpuffer hat, der Speicher in einer anderen Reihenfolge ablaufen lässt als in der Reihenfolge, in der sie eingegeben wurden. Dies kann auftreten, wenn der erste Speicher im Cache fehlschlägt, während der zweite auftritt, oder wenn der zweite Speicher mit einem früheren Speicher (dh vor dem ersten Speicher) koaleszieren kann. Die Neuordnung von Ladeanforderungen kann auch bei dynamisch geplanten Kernen auftreten, die Anweisungen außerhalb der Programmreihenfolge ausführen. Das kann sich genauso verhalten wie das Umsortieren von Speichern auf einem anderen Kern (Können Sie eine Beispielverschachtelung zwischen zwei Threads finden?). Das Umordnen einer früheren Ladung mit einem späteren Speicher (eine Neuordnung des Lade-Speichers) kann viele falsche Verhaltensweisen verursachen, z. B. das Laden eines Werts nach dem Aufheben der Sperre, die ihn schützt (wenn der Speicher die Entsperr-Operation ist). Es ist zu beachten, dass Speicherlade-Neuordnungen auch aufgrund einer lokalen Umgehung in dem gemeinsam implementierten FIFO-Schreibpuffer auftreten können, selbst mit einem Kern, der alle Befehle in der Programmreihenfolge ausführt.

Weil Cache-Kohärenz und Speicherkonsistenz manchmal verwirrt sind, ist es lehrreich, auch dieses Zitat zu haben:

Im Gegensatz zur Konsistenz ist Cache-Kohärenz weder für Software sichtbar noch erforderlich. Coherence versucht, die Caches eines Shared-Memory-Systems so funktionell unsichtbar zu machen wie die Caches eines Single-Core-Systems. Korrekte Kohärenz stellt sicher, dass ein Programmierer nicht feststellen kann, ob und wo ein System Caches hat, indem er die Ergebnisse von Ladevorgängen und Speichern analysiert. Dies liegt daran, dass eine korrekte Kohärenz sicherstellt, dass die Caches niemals ein neues oder anderes funktionales Verhalten ermöglichen (Programmierer können unter Verwendung von Timing- Informationen immer noch in der Lage sein, eine wahrscheinliche Cachestruktur abzuleiten ). Der Hauptzweck von Cache-Kohärenzprotokollen besteht darin, die Single-Writer-Multiple-Reader (SWMR) für jeden Speicherplatz invariant zu halten. Ein wichtiger Unterschied zwischen Kohärenz und Konsistenz besteht darin, dass Kohärenz auf einer Speicherbasis angegeben wird, während Konsistenz in Bezug auf alle Speicherstellen spezifiziert wird.

In Fortsetzung unseres mentalen Bildes entspricht die SWMR-Invariante der physikalischen Anforderung, dass es höchstens ein Teilchen an einem Ort gibt, aber es kann eine unbegrenzte Anzahl von Beobachtern von jedem Ort geben.





c++ c include header-files c-preprocessor