2010-04-13 18 views
15

Mögliche Duplizieren:
Should C++ eliminate header files?Warum sind Vorwärtsdeklarationen notwendig?

In Sprachen wie C# und Java gibt es keine Notwendigkeit (zum Beispiel), eine Klasse zu deklarieren, bevor es zu benutzen. Wenn ich es richtig verstehe, liegt das daran, dass der Compiler zwei Durchläufe am Code macht. Im ersten Fall "sammelt er nur die verfügbaren Informationen" und im zweiten prüft er, ob der Code korrekt ist.

In C und C++ macht der Compiler nur einen Durchlauf, also muss alles zu dieser Zeit verfügbar sein.

Also meine Frage im Grunde ist, warum ist es nicht so in C und C++ getan. Würde es nicht die Notwendigkeit für Header-Dateien beseitigen?

+0

Die C++ Compiler sequenziell gelesen wird [das heißt, es wird von oben nach unten gelesen. ...] Genau so funktioniert die Sprache: Ihr Vorschlag, zweimal durchzugehen und die Funktionsprototypen zu betrachten, würde funktionieren, aber leider funktioniert die Sprache nicht so. – Warty

+2

Duplizieren: http://stackoverflow.com/questions/752793 –

+4

C wurde vor mehr als 30 Jahren standardisiert, als die zugrundeliegende Technologie weit weniger leistungsfähig und viel teurer war als heute .. Ein sanfter Vorschlag: Versuchen Sie ein wenig über die Geschichte der Dinge zu lernen und zu verstehen dass sich die Welt seitdem enorm verändert hat Die Entscheidungen wurden getroffen und werden sich auch weiterhin ändern, wenn Sie bei Ihren Entwicklungsbemühungen Entscheidungen treffen. Irgendjemand wird sich eines Tages wundern "WTF ?!" über deine Entscheidungen .... ;-) – DaveE

Antwort

39

Die kurze Antwort ist, dass Rechenleistung und Ressourcen zwischen der Zeit, die C definiert wurde, und der Zeit, die Java 25 Jahre später kam, exponentiell fortgeschritten waren.

Die längere Antwort ...

Die maximale Größe einer Kompilierungseinheit - der Codeblock, der ein Compiler Prozesse in einem einzigen Brocken - durch die Größe des Speichers begrenzt wird, dass der kompilierende Computer hat. Um die Symbole zu verarbeiten, die Sie in Maschinencode eingeben, muss der Compiler alle Symbole in einer Nachschlagetabelle enthalten und auf sie verweisen, wenn sie in Ihrem Code darauf stoßen.

Als C im Jahr 1972 erstellt wurde, waren die Rechenressourcen viel knapper und mit einer hohen Prämie - der Speicher, der erforderlich war, um die gesamte symbolische Tabelle eines komplexen Programms gleichzeitig zu speichern, war in den meisten Systemen einfach nicht verfügbar. Festspeicher war auch teuer und extrem langsam, so dass Ideen wie virtueller Speicher oder das Speichern von Teilen der symbolischen Tabelle auf der Platte einfach keine Kompilierung in einem vernünftigen Zeitrahmen erlaubt hätten.

Die beste Lösung für das Problem war, den Code in kleinere Stücke zu zerstückeln, indem man eine menschliche Sortierung herausstellte, welche Teile der Symboltabelle in welchen Kompilierungseinheiten im Voraus benötigt würden.Eine ziemlich kleine Aufgabe auf dem Programmierer auferlegt, zu erklären, was er verwenden würde, speicherte die enorme Bemühung, den Computer zu haben, das gesamte Programm nach allem zu suchen, das der Programmierer verwenden könnte.

Es speicherte auch den Compiler von zwei Durchgänge auf jeder Quelldatei zu machen: der erste, um alle Symbole im Index zu indizieren, und der zweite, um die Referenzen zu analysieren und nachzuschlagen. Wenn Sie mit Magnetband arbeiten, bei dem die Suchzeiten in Sekunden gemessen wurden und der Lesedurchsatz in Byte pro Sekunde (nicht Kilobyte oder Megabyte) gemessen wurde, war das ziemlich aussagekräftig.

C++, obwohl fast 17 Jahre später erstellt, wurde als eine Obermenge von C definiert und musste daher den gleichen Mechanismus verwenden.

Zu der Zeit, Java im Jahr 1995 herumgerollt, hatten durchschnittliche Computer genug Speicher, dass das Halten einer symbolischen Tabelle, auch für ein komplexes Projekt, nicht mehr eine erhebliche Belastung war. Und Java war nicht so konzipiert, dass es abwärtskompatibel zu C ist, sodass es keinen Legacy-Mechanismus brauchte. C# war ähnlich unbelastet.

Als Konsequenz entschieden sich die Entwickler dafür, die Lasten der symbolischen Deklaration von dem Programmierer zu entfernen und sie wieder auf den Computer zu stellen, da ihre Kosten im Verhältnis zum Gesamtaufwand der Kompilierung minimal waren.

+7

Ausgezeichnete Zusammenfassung. Erinnert mich an die "guten alten Tage", ein C-Programm auf einem 2-Diskettenlaufwerk 640K PC kompilierend - dauerte etwa 10 Minuten mit einem halben Dutzend oder mehr Floppy-Änderungen. All das für ein Programm, das nicht mehr als ein paar Hundert Aussagen enthält! Und dachte, ich wäre mit all dieser Kraft im Himmel. – NealB

+4

Große Antwort, es ist immer schön, eine historische Perspektive für uns jüngere Menschen zu bekommen! –

5

Fazit: Es gab Fortschritte in der Compiler-Technologie, die Vorwärtsdeklarationen überflüssig machen. Plus-Computer sind tausende Male schneller und können daher die zusätzlichen Berechnungen vornehmen, die erforderlich sind, um das Fehlen von Vorwärtsdeklarationen zu bewältigen.

C und C++ sind älter und wurden zu einem Zeitpunkt standardisiert, als es notwendig war, jeden CPU-Zyklus zu speichern.

+2

:-) Mit anderen Worten - C# ist besser als C++. –

+8

Ihnen fehlen hier die Schlüsselwörter: Rückwärtskompatibilität. Ihre letzte Zeile klingt wie C und C++ haben nur eine Version des Standards aus der Steinzeit. Es sollte lesen "und wurden * zuerst * standardisiert ... und um die Abwärtskompatibilität zu erhalten, bleibt die Methode die gleiche." @Franci: Wenn du mit dem Schreiben eines Betriebssystems in C# fertig bist, hol mich. – GManNickG

+3

@Franci: Nein ... mit anderen Worten, moderne Sprachcompiler haben Vorwärtsdeklarationen überflüssig gemacht, weil sie sich keine Gedanken über die Rückwärtskompatibilität machen müssen. Es könnte * in C++ gemacht werden. Viel Spaß beim Schreiben von Hardwaretreibern in C# bud. –

1

Dies liegt an kleineren Kompilierungsmodulen in C/C++. In C/C++ wird jede .c/.cpp-Datei separat kompiliert, wodurch ein OBJ-Modul erstellt wird. Daher benötigt der Compiler Informationen über Typen und Variablen, die in anderen Kompilierungsmodulen deklariert sind. Diese Information wird in Form von Forward-Deklarationen geliefert, normalerweise in Header-Dateien.

C#, auf der anderen Seite, kompiliert mehrere CS-Dateien gleichzeitig in ein großes Kompilierungsmodul.

In der Tat muss der Compiler, wenn er verschiedene kompilierte Module aus einem C# -Programm referenziert, die Deklarationen (Typnamen usw.) genauso kennen wie der C++ - Compiler. Diese Information wird direkt vom kompilierten Modul erhalten. In C++ werden die gleichen Informationen explizit getrennt (deshalb können Sie die Variablennamen nicht aus C++ - kompilierter DLL herausfinden, sondern können sie aus .NET Assembly ermitteln).

+0

Leider erklärt das nicht, wie C# unsere 100-Assembly-Lösung verwaltet, die Tausende von Quelldateien und Millionen von Codezeilen enthält, die besser sind als C++.h-Datei (die möglicherweise noch eine Vorwärtsdeklaration benötigt, obwohl alle benötigten Informationen in der einen Datei enthalten sind). –

+0

@Jason: Die Vorteile der separaten Kompilierung sind unterschiedlich: Wenn Sie nur die Implementierung ändern, erfolgt die Neukompilierung fast sofort. (Natürlich wird dadurch die erste Übersetzung langsamer.) Ich weiß nicht, warum Ihr C++ keine einzige Header-Datei verwalten kann, ich hatte nie Probleme mit meinen. – Vlad

+0

@Vlad: Sie sagten "... der Compiler benötigt Informationen über Typen ..., die in anderen Kompilierungsmodulen deklariert sind", aber sogar eine einzelne C++ Klasse in einem einzigen Header * kann eine Vorzeichnung erfordern - dh sogar innerhalb eines Kompilierungsmoduls . Das bedeutet, dass C++ linear durch den Code analysiert wird (so dass zukünftige Typen beim Verweis referenziert werden müssen), während C# effektiv eine Datenbank für die Codebasis erstellt, die einen wahlfreien Zugriff auf alle Typen ermöglicht. –

0

Die Vorwärtsdeklarationen in C++ sind eine Möglichkeit, Metadaten zu den anderen Codeteilen bereitzustellen, die von der aktuell kompilierten Quelle für den Compiler verwendet werden können, damit der richtige Code generiert werden kann.

Diese Metadaten können vom Autor der verknüpften Bibliothek/Komponente stammen. Es kann jedoch auch automatisch generiert werden (zum Beispiel gibt es Tools, die C++ - Header-Dateien für COM-Objekte generieren). In jedem Fall besteht die C++ - Methode zum Ausdrücken dieser Metadaten aus den Header-Dateien, die Sie in Ihren Quellcode aufnehmen müssen.

Die C# /. Net verbrauchen auch ähnliche Metadaten zur Kompilierzeit. Diese Metadaten werden jedoch automatisch generiert, wenn die Assembly, auf die sie angewendet wird, erstellt wird und normalerweise in sie eingebettet wird. Wenn Sie also in Ihrem C# -Projekt eine Assembly referenzieren, sagen Sie dem Compiler im Wesentlichen, dass Sie auch in dieser Assembly nach den Metadaten suchen müssen, die Sie benötigen. Die Metadatengenerierung und -nutzung in C# ist für die Entwickler also transparenter, sodass sie sich auf das konzentrieren können, was wirklich zählt - ihren eigenen Code schreiben.

Es gibt auch andere Vorteile, die Metadaten über den mit der Baugruppe gebündelten Code zu haben. Reflection, Code-Emittierung, On-the-Fly-Serialisierung - sie alle hängen von den Metadaten ab, um zur Laufzeit den richtigen Code generieren zu können.

Das C++ Analog zu diesem wäre RTTI, obwohl es wegen inkompatibler Implementierungen nicht weit verbreitet ist.

0

Von Eric Lippert, Blogger aller Dinge intern zu C#: http://blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx:

Die Sprache C# erfordert nicht, dass Erklärungen vor Verwendungen auftreten, die zwei Auswirkungen hat, wieder auf dem Benutzer und auf dem Compiler-Schreiber. [...]

Die Auswirkung auf den Compiler-Schreiber ist , dass wir einen "zwei Durchlauf" Compiler haben müssen. Im ersten Durchgang suchen wir nach für Deklarationen und ignorieren Körper. Sobald wir alle Informationen aus den Erklärungen aufgelesen haben, dass wir aus den Headern in C++ bekommen haben würde, nehmen wir einen zweiten Durchgang über den Code und die IL für die Körper erzeugen.

Zusammengefasst erfordert die Verwendung von etwas nicht deklarieren in C#, während es in C++ tut. Das bedeutet, dass Sie in C++ explizit Dinge deklarieren müssen, und es ist bequemer und sicherer, dies mit Header-Dateien zu tun, so dass Sie nicht gegen die One Definition Rule verstoßen.

3

Nein, es würde Headerdateien nicht umgehen. Es würde die Notwendigkeit beseitigen, einen Header zu verwenden, um Klassen/Funktionen in derselben Datei zu deklarieren. Der Hauptgrund für Header ist nicht, um Dinge in der gleichen Datei zu deklarieren. Der Hauptgrund für Header ist, Dinge zu deklarieren, die in anderen Dateien definiert sind.

Ob gut oder schlecht, die Regeln für die Semantik von C (und C++) schreiben das "single pass" -Stil-Verhalten vor. Nur zum Beispiel Code wie folgt betrachten:

int i; 

int f() { 
    i = 1; 
    int i = 2; 
} 

Die i=1 Abtretungsempfänger zum globalen, nicht derjenige innerhalb von f() definiert. Dies liegt daran, dass zum Zeitpunkt der Zuweisung die lokale Definition i noch nicht gesehen wurde, so dass sie nicht berücksichtigt wird. Sie könnten diesen Regeln immer noch mit einem Zwei-Durchlauf-Compiler folgen, aber das könnte nicht-trivial sein. Ich habe ihre Spezifikationen nicht überprüft, um es mit Sicherheit zu wissen, aber meine unmittelbare Vermutung wäre, dass Java und C# in dieser Hinsicht von C und C++ abweichen.

Edit: Da ein Kommentar sagte meine Vermutung war falsch, habe ich ein wenig überprüft. Gemäß der Java-Sprachreferenz, §14.4.2 scheint Java ziemlich nahe den gleichen Regeln wie C++ (ein wenig anders, aber nicht viel.

Mindestens zu folgen, wie ich die C# language specification lesen, (Warnung: Word-Datei) jedoch ist anders.Es (§3.7.1) sagt: "Der Bereich einer lokalen Variablen deklariert in einer lokalen Variablen-Deklaration (§8.5.1) ist der Block, in dem die Erklärung tritt auf. "

Dies scheint zu sagen, dass in C# die lokale Variable im gesamten gesamten Block, in dem es deklariert werden sollte, so mit Code ähnlich dem Beispiel, das ich gab, die Assignme Das wäre die lokale Variable, nicht die globale Variable.

So, das war meine Vermutung halb rechts: Java folgt (ziemlich much0 die gleiche Regel wie C++ in dieser Hinsicht, aber C# nicht

+0

Ihre Schätzung wäre falsch. –

Verwandte Themen