cstdio-Streams vs. iostream-Streams?

Lesezeit: 9 Minuten

Benutzer-Avatar
Toni der Löwe

Ich habe gerade von der Existenz von erfahren ios_base::sync_with_stdio Funktion, mit der Sie im Wesentlichen die Synchronisierung zwischen ausschalten (oder einschalten, wenn Sie sie bereits ausgeschaltet haben) können iostream Streams, die in C++ verwendet werden, und die cstdio Streams, die Teil von Standard C sind.

Nun, das dachte ich immer stdout, stderr und stdin in C wurden im Wesentlichen in eine Reihe von Objekten in C++ in den iostreams-Klassen eingeschlossen. Aber wenn es sein muss synchronisiert miteinander, würde dies darauf hindeuten, dass C++’s iostream Klassen sind nicht ein Wrapper um C’s stdin usw.

Ich bin dadurch ziemlich verwirrt? Kann jemand erklären, wie iostream von C++ und stdio von C sind anders Dinge, die genau dasselbe tun, nur auf einer anderen Abstraktionsebene? Ich dachte, sie wären die gleiche Sache!?

Wie kommt es, dass sie synchronisiert werden müssen? Ich dachte immer, sie wären im Wesentlichen dasselbe, einer umhüllt den anderen.

  • +1, woah, ich dachte immer, das C ++ iostream war ein Wrapper der C stdio zu.

    – Hackerlehrling

    11. März 2012 um 9:19 Uhr

Die C- und C++-Standards stellen keine Anforderungen an die Implementierung, sondern nur an die Wirkung bestimmter Operationen. Für die <stdio> vs. <iostream> Funktionalität bedeutet, dass das eine das andere umhüllen könnte, beide im Wesentlichen gleich sein könnten oder dass sie entweder völlig unabhängig voneinander sind. Technisch gesehen wäre die Verwendung einer gemeinsamen Implementierung aus mehreren Gründen ideal (z. B. wäre keine explizite Synchronisierung erforderlich und es gäbe einen definierten Mechanismus zum Erweitern FILE* für benutzerdefinierte Systeme), aber mir ist kein System bekannt, das dies tatsächlich tut. Es ist möglich, dass eine Implementierung ein Wrapper der anderen ist und implementiert wird <iostream>s in Bezug auf <stdio> war eine typische Implementierungswahl, obwohl es den Nachteil hat, dass es zusätzliche Kosten für bestimmte Operationen verursacht und die meisten C++-Standardbibliotheken dazu übergegangen sind, vollständig separate Implementierungen zu verwenden.

Unglücklicherweise haben sowohl die umschlossene als auch die unabhängige Implementierung ein gemeinsames Problem: I/O ist schrecklich ineffizient, wenn sie auf einer Zeichenebene ausgeführt wird. Somit ist es im Wesentlichen obligatorisch, Zeichen zu puffern und aus einem Puffer zu lesen oder in einen Puffer zu schreiben. Dies funktioniert gut für Streams, die unabhängig voneinander sind. Der Haken sind die Standard-C-Streams stdin, stdout, stderr und ihre Gegenstücke mit schmalen C++-Zeichen std::cin, std::cout, std::cerr/std::clog und C++-Breitzeichen-Gegenstücke std::wcin, std::wcout, std::wcerr/std::wclogbzw.: was passiert, wenn ein Benutzer beides ausliest stdin und std::cin? Wenn einer dieser Streams einen Zeichenpuffer aus dem zugrunde liegenden OS-Stream liest, erscheinen die Lesevorgänge in der falschen Reihenfolge. Ebenso, wenn beides stdout und std::cout Used Independent Buffers-Zeichen würden in unerwarteter Reihenfolge erscheinen, wenn ein Benutzer beide in beide Streams schreibt. Daher gibt es spezielle Regeln für die Standard-C++-Stream-Objekte (d. h std::cin, std::cout, std::cerrund std::clog und ihre Gegenstücke mit weitem Charakter), die vorschreiben, dass sie sich mit ihren jeweiligen synchronisieren <stdio> Gegenstück. Effektiv bedeutet dies, dass speziell diese C++-Objekte entweder direkt eine gemeinsame Implementierung verwenden oder dass sie in Bezug auf implementiert werden <stdio> und keine Zeichen puffern.

Es wurde erkannt, dass die Kosten für diese Synchronisation ziemlich beträchtlich sind, wenn die Implementierungen keine gemeinsame Basis haben, und für einige Benutzer möglicherweise unnötig sind: wenn ein Benutzer nur verwendet <iostream> er möchte nicht für die zusätzliche Umleitung zahlen und, was noch wichtiger ist, er möchte nicht für die zusätzlichen Kosten zahlen, die durch die Nichtverwendung eines Puffers entstehen. Für sorgfältige Implementierungen können die Kosten für die Nichtverwendung eines Puffers ziemlich beträchtlich sein, da dies bedeutet, dass bestimmte Operationen am Ende eine Überprüfung und möglicherweise einen virtuellen Funktionsaufruf in jeder Iteration durchführen müssen, anstatt nur hin und wieder. Daher, std::sync_with_stdio() kann verwendet werden, um diese Synchronisation auszuschalten, was dazu führen kann, dass die Standard-Stream-Objekte ihre interne Implementierung mehr oder weniger vollständig ändern. Da die Stream-Puffer der Standard-Stream-Objekte von einem Benutzer ersetzt werden können, können die Stream-Puffer leider nicht ersetzt werden, aber die interne Implementierung des Stream-Puffers kann geändert werden.

In guten Implementierungen der <iostream> all dies wirkt sich nur auf die Standard-Stream-Objekte aus. Das heißt, Dateistreams sollten davon völlig unberührt bleiben. Wenn Sie jedoch die Standard-Stream-Objekte verwenden und eine gute Leistung erzielen möchten, möchten Sie eindeutig nicht mischen <stdio> und <iostream> und Sie möchten die Synchronisierung deaktivieren. Insbesondere beim Vergleich der I/O-Leistung zwischen <stdio> und <iostream> Sie sollten sich dessen bewusst sein.

  • Konnte keine bessere Erklärung für das Szenario finden. Ich bin einmal in diese Falle getappt und habe 3 Tage damit verbracht zu verstehen, was passiert ist. Super gemacht, Dietmar!

    – Schi B.

    22. Februar 2016 um 4:22 Uhr

Eigentlich die stdout, stderr und stdin sind die Dateihandler des Betriebssystems. Und FILE Struktur von C sowie iostream Klassen von C++ sind beide Wrapper dieser Dateihandler. Sowohl iostream-Klassen als auch die FILE-Struktur können ihre eigenen Puffer oder etwas anderes haben, das untereinander synchronisiert werden muss, um sicherzustellen, dass die Eingabe aus der Datei oder die Ausgabe in die Datei korrekt erfolgt.

  • stdin und Freunde sind Typenstrukturen FILE und ein C Ding. Das Betriebssystem hat einfach Dateideskriptoren 0, 1 und 2wie in definiert unistd.h wie #define STDIN_FILENO 0.

    – hochl

    11. März 2012 um 12:00 Uhr

  • Korrekt. Aber auch stdin, stdout etc. sind Abkürzungen für „Standard Input Stream“, „Standard Output Stream“ etc. Ich habe diese Wörter als Abkürzungen für Systemstreams verwendet.

    – Jurlie

    11. März 2012 um 16:34 Uhr

Benutzer-Avatar
LehrlingHacker

Okay, hier ist, was ich gefunden habe.

Tatsächlich wird die E/A letztendlich von nativen Systemaufrufen und -funktionen ausgeführt.

Nehmen wir zum Beispiel Microsoft Windows. Es gibt tatsächlich verfügbare Griffe für STDIN , STDIO usw (vgl hier). Also im Grunde sowohl das C++ iostream und C stdio native Systemfunktionen aufrufen, die C++ iostream schließt die I/O-Funktionen von C (in modernen Implementierungen) nicht ein. Es ruft die nativen Systemmethoden direkt auf.

Außerdem habe ich das gefunden:

Sobald stdin, stdout und stderr umgeleitet sind, können Standard-C-Funktionen wie printf() und gets() ohne Änderung verwendet werden, um mit der Win32-Konsole zu kommunizieren. Aber was ist mit C++ I/O-Streams? Da cin, cout, cerr und clog eng mit stdin, stdout und stderr von C verbunden sind, würden Sie erwarten, dass sie sich ähnlich verhalten. Das ist halb richtig.

C++-I/O-Streams gibt es eigentlich in zwei Varianten: Vorlage und Nicht-Vorlage. Die ältere Nicht-Template-Version von E/A-Streams wird langsam durch einen neueren Template-Stil von Streams ersetzt, der zuerst von der Standard Template Library (STL) definiert wurde und jetzt in den ANSI C++-Standard aufgenommen wird. Visual C++ v5 stellt beide Typen bereit und ermöglicht Ihnen die Auswahl zwischen den beiden, indem verschiedene Header-Dateien eingeschlossen werden. STL-E/A-Streams funktionieren wie erwartet und verwenden automatisch alle neu umgeleiteten stdio-Handles. I/O-Streams, die keine Vorlagen sind, funktionieren jedoch nicht wie erwartet. Um herauszufinden, warum, habe ich mir den Quellcode angesehen, der praktischerweise auf der Visual C++-CD-ROM bereitgestellt wird.

Das Problem besteht darin, dass die älteren E/A-Streams so konzipiert wurden, dass sie “Dateideskriptoren” im UNIX-Stil verwenden, bei denen Ganzzahlen anstelle von Handles verwendet werden (0 für stdin, 1 für stdout usw.). Das ist praktisch für UNIX-Implementierungen, aber Win32-C-Compiler müssen noch eine weitere E/A-Schicht bereitstellen, um diesen E/A-Stil darzustellen, da Win32 keinen kompatiblen Satz von Funktionen bereitstellt. Wenn Sie _open_osfhandle() aufrufen, um (beispielsweise) stdout ein neues Win32-Handle zuzuordnen, hat dies in jedem Fall keine Auswirkung auf die andere Ebene des E/A-Codes. Daher verwendet der Dateideskriptor 1 weiterhin dasselbe zugrunde liegende Win32-Handle wie zuvor, und das Senden der Ausgabe an cout wird nicht den gewünschten Effekt erzielen.

Glücklicherweise haben die Designer des ursprünglichen I/O-Stream-Pakets dieses Problem vorhergesehen und eine saubere und nützliche Lösung bereitgestellt. Die Basisklasse ios bietet eine statische Funktion, sync_with_stdio(), die bewirkt, dass die Bibliothek ihre zugrunde liegenden Dateideskriptoren ändert, um alle Änderungen in der Standard-E/A-Schicht widerzuspiegeln. Obwohl dies für STL-E/A-Streams nicht unbedingt erforderlich ist, schadet es nicht und lässt mich Code schreiben, der sowohl mit der neuen als auch mit der alten Form von E/A-Streams korrekt funktioniert.

(Quelle)

Daher Anruf sync_with_stdio() ändert tatsächlich die zugrunde liegenden Dateideskriptoren. Es wurde tatsächlich von den Designern hinzugefügt, um die Kompatibilität der älteren C++-E/A mit Systemen wie Windows-32 sicherzustellen, die Handles anstelle von Ganzzahlen verwendeten.

Beachten Sie, dass Using sync_with_stdio() ist bei modernen C++-Vorlagen-basierten STL-E/A nicht erforderlich.

  • Es könnte erwähnenswert sein, dass Ihre Quelle von „Visual C++ 5“ und „CD-ROMs“ spricht, nur um das in einen Kontext zu stellen.

    – Kerrek SB

    11. März 2012 um 9:56 Uhr

  • @KerrekSB das liegt daran, dass es in den 90ern geschrieben wurde. 🙂 . Als die STL eine ‘neue Sache’ war.

    – Hackerlehrling

    11. März 2012 um 10:02 Uhr


Einer kann ein Wrapper um den anderen sein (und das funktioniert in beide Richtungen. Du könnte implementieren stdio Funktionen durch Verwendung iostream und umgekehrt. Oder Sie können sie völlig unabhängig schreiben.

Und sync_with_stdio garantiert, dass die beiden Streams synchronisiert werden, wenn es aktiviert ist. Aber sie können immer noch synchronisieren, wenn es auch deaktiviert ist, wenn sie es wirklich wollen.

Aber selbst wenn einer den anderen umschließt, hat der eine möglicherweise immer noch einen Puffer, den der andere beispielsweise nicht teilt, sodass eine Synchronisierung immer noch erforderlich ist.

Sie sind dasselbe, aber sie könnten auch separat gepuffert werden. Dies könnte sich auf Code auswirken, der die Verwendung von C- und C++-I/O mischt, wie hier

std::cout << "Hello ";
printf("%s", "world");
std::cout << "!\n";

Damit dies funktioniert, müssen die zugrunde liegenden Streams irgendwie synchronisiert werden. Auf einigen Systemen ist dies könnte bedeutet, dass die Leistung leiden könnte.

Der Standard erlaubt Ihnen also, anzurufen std::sync_with_stdio(false) zu sagen, dass Ihnen Code wie dieser egal ist, Sie aber lieber die Standard-Streams so schnell wie möglich arbeiten lassen möchten wenn es einen unterschied macht. Auf vielen Systemen macht es keinen Unterschied.

1373960cookie-checkcstdio-Streams vs. iostream-Streams?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy