Frage

Wenn zwei C++-Dateien unterschiedliche Definitionen von Klassen mit demselben Namen haben, wird beim Kompilieren und Verknüpfen etwas ausgegeben, auch ohne Warnung.Zum Beispiel,

// a.cc
class Student {
public:
    std::string foo() { return "A"; }
};
void foo_a()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << std::endl;
}

Beim Kompilieren und Verknüpfen mit g++ geben beide „A“ aus (wenn a.cc in der Befehlszeilenreihenfolge vor b.cc steht).

Ein ähnliches Thema ist Hier.Ich sehe, dass Namespace dieses Problem lösen wird, aber ich weiß nicht, warum der Linker nicht einmal eine Warnung ausgibt.Und wenn eine Definition der Klasse eine zusätzliche Funktion hat, die in einer anderen nicht definiert ist, sagen wir, wenn b.cc wie folgt aktualisiert wird:

// b.cc
class Student {
public:
    std::string foo() { return "B"; }
    std::string bar() { return "K"; }
};
void foo_b()
{
    Student stu;
    std::cout << stu.foo() << stu.bar() << std::endl;
}

Dann funktioniert stu.bar() gut.Vielen Dank an alle, die mir sagen können, wie der Compiler und Linker in einer solchen Situation funktionieren.

Als zusätzliche Frage: Wenn Klassen in Header-Dateien definiert sind, sollten sie dann immer mit einem unbenannten Namespace umschlossen werden, um eine solche Situation zu vermeiden?Gibt es Nebenwirkungen?

War es hilfreich?

Lösung

Dies ist ein Verstoß gegen die One-Definition-Regel (C++03, 3.2/5 „One-Definition-Regel“), die (unter anderem) besagt:

Es kann mehr als eine Definition eines Klassentyps geben (Absatz 9), ...In einem Programm, das vorgesehen ist, dass jede Definition in einer anderen Übersetzungseinheit erscheint, und die Definitionen erfüllen, erfüllen die folgenden Anforderungen.Angesichts einer solchen Entität namens D in mehr als einer Übersetzungseinheit definiert, dann

  • Jede Definition von D muss aus derselben Folge von Token bestehen.

Wenn Sie gegen die One-Definition-Regel verstoßen, ist das Verhalten undefiniert (was bedeutet, dass seltsame Dinge passieren können).

Der Linker sieht mehrere Definitionen von Student::foo() - eine in der Objektdatei von a und eine in der Objektdatei von b.Allerdings beschwert es sich darüber nicht;es wählt einfach eine der beiden aus (zufälligerweise die erste, auf die es stößt).Dieser „sanfte“ Umgang mit doppelten Funktionen geschieht offenbar nur bei Inline-Funktionen.Für Nicht-Inline-Funktionen der Linker Wille beschweren sich über mehrere Definitionen und weigern sich, eine ausführbare Datei zu erstellen (möglicherweise gibt es Optionen, die diese Einschränkung lockern).Beide GNU ld und der Linker von MSVC verhalten sich auf diese Weise.

Das Verhalten macht einen Sinn;Inline-Funktionen müssen in jeder Übersetzungseinheit verfügbar sein, in der sie verwendet werden.Und im Allgemeinen müssen Nicht-Inline-Versionen verfügbar sein (falls der Aufruf nicht inline ist oder wenn die Adresse der Funktion verwendet wird). inline ist eigentlich nur eine Freifahrt um die Ein-Definition-Regel – aber damit es funktioniert, müssen alle Inline-Definitionen gleich sein.

Wenn ich mir Dumps der Objektdateien ansehe, sehe ich nichts Offensichtliches, das mir erklärt, woher der Linker weiß, dass eine Funktion mehrere Definitionen haben darf und andere nicht, aber ich bin sicher, dass es ein Flag oder einen Datensatz gibt was genau das tut.Leider stelle ich fest, dass die Funktionsweise des Linkers und die Details der Objektdatei nicht besonders gut dokumentiert sind, sodass mir der genaue Mechanismus wahrscheinlich ein Rätsel bleiben wird.

Was Ihre zweite Frage betrifft:

Wenn die Klassen in Header -Dateien definiert sind, sollten sie immer mit dem unbenannten Namespace eingewickelt werden, um eine solche Situation zu vermeiden?Gibt es Nebenwirkungen?

Sie möchten dies mit ziemlicher Sicherheit nicht tun. Jede Klasse wäre in jeder Übersetzungseinheit ein eigener Typ, sodass Instanzen der Klasse technisch gesehen nicht von einer Übersetzungseinheit an eine andere übergeben werden könnten (durch Zeiger, Referenz oder Kopieren).Außerdem würden Sie am Ende mehrere Instanzen aller statischen Mitglieder haben.Das würde wahrscheinlich nicht gut funktionieren.

Platzieren Sie sie in verschiedenen, benannten Namespaces.

Andere Tipps

Sie haben die Eindefinitionsregel für Klassendefinitionen verletzt, und die Sprache ist speziell verbietet, dies zu tun.Es ist nicht erforderlich, dass der Compiler / Linker, um zu warnen oder zu diagnostizieren, und ein solches Szenario wird sicherlich nicht garantiert wie erwartet in diesem Fall nicht garantiert.

Aber ich weiß nicht, warum der Linker nicht einmal eine Warnung erschießt.

Verstöße gegen die One-Definition-Regel erfordern keine Diagnose, da der Linker, dass der Linker nachweisen muss, dass die beiden Definitionen nicht äquivalent sind.

Dies ist einfach, sobald die Klassen unterschiedliche Größen, eine andere Anzahl von Mitgliedern oder verschiedenen Basisklassen haben.Sobald alle diese Faktoren gleich sind, könnten Sie immer noch andere Mitgliederdefinitionen haben, zum Beispiel, und der Linker muss ein paar fortschrittliche Introspektion verwenden, um sie zu vergleichen.Auch wenn das immer möglich war (und ich bin mir nicht sicher, ob die Objektdateien mit verschiedenen Optionen zusammengestellt werden könnten, was zu einer anderen Ausgabe führt), ist es sicherlich sehr ineffizient.

Infolgedessen akzeptiert der Linker nur, dass das, was Sie werfen, nicht gegen die ODR verstoßen.Keine perfekte Situation, sondern auch gut.

Ich denke, Ihre "extra" -Ausfrage ist ein Hinweis auf die Hauptfrage.

Wenn ich Ihre zusätzliche Frage verstehe, dann denke ich, dass Sie nicht sie in einem Namespace einwickeln möchten, da, wenn Sie dieselbe Klasse in mehreren .cc-Dateien zusammensetzen, dann möchten Sie wahrscheinlichVerwenden Sie nur eine Kopie jeder Methode, auch wenn sie in der Klasse definiert sind.

Diese (Art von) erläutert, warum Sie nur eine Version jeder Funktion in Ihrem Hauptbeispiel erhalten.Ich erwarte, dass der Linker gerade davon ausgeht, dass die beiden Funktionen identisch sind - erzeugt von derselben #inklusivierten Quelle.Es wäre schön, wenn der Linker erkennen würde, wenn sie anders waren und eine Warnung ausgeben, aber ich denke, das ist schwer.

Wenn Mark B angibt, ist die praktische Antwort "einfach nicht dorthin".

Abgesehen von einem Verstoß gegen eine Definitionsregel sehen Sie keinen Compiler, der sich wegen Name Mangling in C ++

edit: Wie von Konrad Rudolph hingewiesen: Mangled-Namen in diesem Fall wäre derselbe.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top