it-swarm.com.de

Bezahle ich in C ++ für das, was ich nicht esse?

Betrachten wir die folgenden Hello World-Beispiele in C und C++:

main.c

_#include <stdio.h>

int main()
{
    printf("Hello world\n");
    return 0;
}
_

main.cpp

_#include <iostream>

int main()
{
    std::cout<<"Hello world"<<std::endl;
    return 0;
}
_

Wenn ich sie in Godbolt to Assembly kompiliere, beträgt die Größe des C-Codes nur 9 Zeilen (_gcc -O3_):

_.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        xor     eax, eax
        add     rsp, 8
        ret
_

Die Größe des C++ - Codes beträgt jedoch 22 Zeilen (_g++ -O3_):

_.LC0:
        .string "Hello world"
main:
        sub     rsp, 8
        mov     edx, 11
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
        xor     eax, eax
        add     rsp, 8
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit
_

... was viel größer ist.

Es ist berühmt, dass Sie in C++ für das bezahlen, was Sie essen. Wofür bezahle ich in diesem Fall?

167
Saher

Sie zahlen dafür, dass Sie eine umfangreiche Bibliothek aufrufen (nicht so umfangreich wie das Drucken in die Konsole). Sie initialisieren ein ostream Objekt. Es gibt einige versteckte Speicher. Dann rufen Sie std::endl auf, was kein Synonym für \n ist. Mit der Bibliothek iostream können Sie viele Einstellungen anpassen und den Prozessor und nicht den Programmierer entlasten. Dafür zahlen Sie.

Lassen Sie uns den Code überprüfen:

.LC0:
        .string "Hello world"
main:

Ein ostream-Objekt + cout wird initialisiert

    sub     rsp, 8
    mov     edx, 11
    mov     esi, OFFSET FLAT:.LC0
    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long)

Rufen Sie cout erneut auf, um eine neue Zeile zu drucken und zu leeren

    mov     edi, OFFSET FLAT:_ZSt4cout
    call    std::basic_ostream<char, std::char_traits<char> >& std::endl<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&)
    xor     eax, eax
    add     rsp, 8
    ret

Statische Speicherinitialisierung:

_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8
        jmp     __cxa_atexit

Es ist auch wichtig, zwischen der Sprache und der Bibliothek zu unterscheiden.

Übrigens ist dies nur ein Teil der Geschichte. Sie wissen nicht, was in den aufgerufenen Funktionen steht.

59
Arash

Wofür bezahle ich in diesem Fall?

std::cout ist mächtiger und komplizierter als printf. Es unterstützt unter anderem Gebietsschemas, Statusformatierungsflags und vieles mehr.

Wenn Sie diese nicht benötigen, verwenden Sie std::printf oder std::puts - sie sind in <cstdio> verfügbar.


Es ist berühmt, dass Sie in C++ für das bezahlen, was Sie essen.

Ich möchte auch klarstellen, dass C++ ! = Die C++ Standard Library. Die Standardbibliothek soll universell und "schnell genug" sein, ist jedoch häufig langsamer als eine spezielle Implementierung dessen, was Sie benötigen.

Andererseits ist die C++ - Sprache bestrebt, das Schreiben von Code zu ermöglichen, ohne unnötige versteckte Zusatzkosten zu zahlen (z. B. Opt-in virtual, keine Garbage Collection).

209
Vittorio Romeo

Sie vergleichen nicht C und C++. Sie vergleichen printf und std::cout, die in der Lage sind, verschiedene Dinge zu tun (Gebietsschemata, zustandsbehaftete Formatierungen usw.).

Versuchen Sie, den folgenden Code zum Vergleich zu verwenden. Godbolt generiert für beide Dateien dieselbe Assembly (getestet mit gcc 8.2, -O3).

haupt c:

#include <stdio.h>

int main()
{
    int arr[6] = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < 6; ++i)
    {
        printf("%d\n", arr[i]);
    }
    return 0;
}

main.cpp:

#include <array>
#include <cstdio>

int main()
{
    std::array<int, 6> arr {1, 2, 3, 4, 5, 6};
    for (auto x : arr)
    {
        std::printf("%d\n", x);
    }
}
173
pschill

Ihre Angebote vergleichen in der Tat Äpfel und Orangen, aber nicht aus dem Grund, der in den meisten anderen Antworten impliziert wird.

Überprüfen wir, was Ihr Code tatsächlich tut:

C:

  • gib einen einzelnen String aus, "Hello world\n"

C++:

  • streame den String "Hello world" in std::cout
  • streame den std::endl Manipulator in std::cout

Anscheinend macht Ihr C++ - Code doppelt so viel Arbeit. Für einen fairen Vergleich sollten wir dies kombinieren:

#include <iostream>

int main()
{
    std::cout<<"Hello world\n";
    return 0;
}

... und plötzlich sieht Ihr Assembly-Code für main dem von C sehr ähnlich:

main:
        sub     rsp, 8
        mov     esi, OFFSET FLAT:.LC0
        mov     edi, OFFSET FLAT:_ZSt4cout
        call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
        xor     eax, eax
        add     rsp, 8
        ret

Tatsächlich können wir den C- und C++ - Code zeilenweise vergleichen, und es gibt sehr wenige Unterschiede:

sub     rsp, 8                      sub     rsp, 8
mov     edi, OFFSET FLAT:.LC0   |   mov     esi, OFFSET FLAT:.LC0
                                >   mov     edi, OFFSET FLAT:_ZSt4cout
call    puts                    |   call    std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
xor     eax, eax                    xor     eax, eax
add     rsp, 8                      add     rsp, 8
ret                                 ret

Der einzige wirkliche Unterschied besteht darin, dass wir in C++ operator << mit zwei Argumenten (std::cout und der Zeichenfolge) aufrufen. Wir könnten sogar diesen kleinen Unterschied beseitigen, indem wir ein näheres C-Äquivalent verwenden: fprintf, das auch ein erstes Argument enthält, das den Stream spezifiziert.

Dadurch verbleibt der Assembly-Code für _GLOBAL__sub_I_main, der für C++, jedoch nicht für C generiert wird. Dies ist der einzige echte Overhead, der in dieser Assembly-Auflistung sichtbar ist (für beide Sprachen gibt es mehr, unsichtbaren Overhead. Na sicher). Dieser Code führt zu Beginn des C++ - Programms eine einmalige Einrichtung einiger C++ - Standardbibliotheksfunktionen durch.

Wie in anderen Antworten erläutert, wird der relevante Unterschied zwischen diesen beiden Programmen jedoch nicht in der Assembly-Ausgabe der main -Funktion gefunden, da all das schwere Heben hinter den Kulissen stattfindet.

133
Konrad Rudolph

Es ist berühmt, dass Sie in C++ für das bezahlen, was Sie essen. Wofür bezahle ich in diesem Fall?

Das ist einfach. Du bezahlst für std::cout. "Sie bezahlen nur für das, was Sie essen" heißt nicht "Sie bekommen immer die besten Preise". Sicher, printf ist billiger. Man kann argumentieren, dass std::cout sicherer und vielseitiger ist, daher sind die höheren Kosten gerechtfertigt (es kostet mehr, bietet aber mehr Wert), aber das geht am Ende vorbei. Sie verwenden printf nicht, Sie verwenden std::cout, also zahlen Sie für die Verwendung von std::cout. Sie zahlen nicht für die Verwendung von printf.

Ein gutes Beispiel sind virtuelle Funktionen. Virtuelle Funktionen haben einige Laufzeitkosten und Platzanforderungen - aber nur, wenn Sie sie tatsächlich verwenden. Wenn Sie keine virtuellen Funktionen nutzen, zahlen Sie nichts.

Ein paar Bemerkungen

  1. Selbst wenn C++ - Code zu mehr Assemblyanweisungen ausgewertet wird, sind es immer noch eine Handvoll Anweisungen, und der Performance-Overhead wird wahrscheinlich durch tatsächliche E/A-Vorgänge in den Schatten gestellt.

  2. Eigentlich ist es manchmal sogar besser als "in C++ bezahlen Sie für das, was Sie essen". Beispielsweise kann der Compiler ableiten, dass ein virtueller Funktionsaufruf unter bestimmten Umständen nicht erforderlich ist, und diesen in einen nicht virtuellen Aufruf umwandeln. Das heißt, Sie können virtuelle Funktionen für kostenlos erhalten. Ist das nicht toll

53
el.pescado

Die "Assembly-Auflistung für printf" ist NICHT für printf, sondern für Puts (Art der Compiler-Optimierung?); printf ist prety viel komplexer als setzt ... nicht vergessen!

Ich sehe hier einige gültige Antworten, aber ich werde ein bisschen mehr ins Detail gehen.

In der folgenden Zusammenfassung finden Sie die Antwort auf Ihre Hauptfrage, wenn Sie nicht die gesamte Textwand durchgehen möchten.


Abstraktion

Wofür bezahle ich in diesem Fall?

Sie bezahlen für Abstraktion . Einfacher und menschlicher Code zu schreiben, ist mit Kosten verbunden. In C++, einer objektorientierten Sprache, ist fast alles ein Objekt. Wenn Sie einen Gegenstand benutzen, passieren immer drei Dinge unter der Haube:

  1. Objekterstellung, im Grunde Speicherzuordnung für das Objekt selbst und seine Daten.
  2. Objektinitialisierung (normalerweise über eine init() Methode). Normalerweise geschieht die Speicherzuweisung als erstes in diesem Schritt unter der Haube.
  3. Objektzerstörung (nicht immer).

Sie sehen es nicht im Code, aber jedes Mal, wenn Sie ein Objekt verwenden, müssen alle drei oben genannten Dinge irgendwie passieren. Wenn Sie alles manuell machen würden, wäre der Code offensichtlich viel länger.

Jetzt kann die Abstraktion effizient durchgeführt werden, ohne dass Zusatzaufwand entsteht: Methoden-Inlining und andere Techniken können sowohl von Compilern als auch von Programmierern verwendet werden, um Zusatzaufwand für die Abstraktion zu beseitigen. Dies ist jedoch nicht Ihr Fall.

Was ist wirklich los in C++?

Hier ist es, aufgeschlüsselt:

  1. Die Klasse std::ios_base wird initialisiert. Dies ist die Basisklasse für alle E/A-Vorgänge.
  2. Das Objekt std::cout wird initialisiert.
  3. Ihre Zeichenfolge wird geladen und an std::__ostream_insert übergeben. Wie Sie bereits anhand des Namens herausgefunden haben, handelt es sich hierbei um eine Methode des Operators std::cout (im Grunde der Operator <<), mit der dem Stream eine Zeichenfolge hinzugefügt wird .
  4. cout::endl wird auch an std::__ostream_insert übergeben.
  5. __std_dso_handle wird an __cxa_atexit übergeben. Hierbei handelt es sich um eine globale Funktion, die für die "Bereinigung" vor dem Beenden des Programms verantwortlich ist. __std_dso_handle selbst wird von dieser Funktion aufgerufen, um verbleibende globale Objekte freizugeben und zu zerstören.

Also mit C == nichts bezahlen?

Im C-Code passieren nur sehr wenige Schritte:

  1. Ihr String wird geladen und über das Register puts an edi übergeben.
  2. puts wird aufgerufen.

Keine Objekte, daher muss nichts initialisiert/zerstört werden.

Dies bedeutet jedoch nicht, dass Sie für nichts in C "bezahlen" . Sie bezahlen immer noch für die Abstraktion, die Initialisierung der C-Standardbibliothek und die dynamische Auflösung der Funktion printf (oder tatsächlich puts), die vom Compiler optimiert wird, da Sie keine Formatzeichenfolge benötigen ) passieren immer noch unter der Haube.

Wenn Sie dieses Programm in reiner Assembly schreiben würden, würde es ungefähr so ​​aussehen:

jmp start

msg db "Hello world\n"

start:
    mov rdi, 1
    mov rsi, offset msg
    mov rdx, 11
    mov rax, 1          ; write
    syscall
    xor rdi, rdi
    mov rax, 60         ; exit
    syscall

Was im Grunde nur dazu führt, dass writesyscall gefolgt von exit syscall aufgerufen wird. Nun wäre dies das absolute Minimum, um dasselbe zu erreichen.


Zusammenfassen

C ist weitaus nackter und überlässt dem Benutzer die volle Kontrolle, der in der Lage ist, und zu optimieren, nur das Nötigste Fertigen Sie im Allgemeinen alles besonders an, das sie wollen. Sie weisen den Prozessor an, eine Zeichenfolge in ein Register zu laden und dann eine Bibliotheksfunktion aufzurufen, um diese Zeichenfolge zu verwenden. C++ ist dagegen weitaus komplexer und abstrakter . Dies hat enorme Vorteile beim Schreiben von kompliziertem Code und ermöglicht ein einfacheres Schreiben und einen menschlicheren Code, ist aber natürlich mit Kosten verbunden. In C++ wird die Leistung im Vergleich zu C in solchen Fällen immer einen Nachteil haben, da C++ mehr als das bietet, was zur Erledigung derartiger grundlegender Aufgaben erforderlich ist, und daher mehr Overhead verursacht .

Beantwortung Ihrer Hauptfrage :

Bezahle ich für das, was ich nicht esse?

In diesem speziellen Fall ja . Sie nutzen nichts, was C++ mehr zu bieten hat als C, aber das liegt nur daran, dass dieses einfache Stück Code nichts enthält, womit C++ Ihnen helfen könnte: Es ist so einfach, dass Sie C++ überhaupt nicht brauchen.


Oh, und noch eine Sache!

Die Vorteile von C++ sind möglicherweise auf den ersten Blick nicht ersichtlich, da Sie ein sehr einfaches und kleines Programm geschrieben haben. Schauen Sie sich jedoch ein etwas komplexeres Beispiel an und sehen Sie den Unterschied (beide Programme tun genau dasselbe):

C:

#include <stdio.h>
#include <stdlib.h>

int cmp(const void *a, const void *b) {
    return *(int*)a - *(int*)b;
}

int main(void) {
    int i, n, *arr;

    printf("How many integers do you want to input? ");
    scanf("%d", &n);

    arr = malloc(sizeof(int) * n);

    for (i = 0; i < n; i++) {
        printf("Index %d: ", i);
        scanf("%d", &arr[i]);
    }

    qsort(arr, n, sizeof(int), cmp)

    puts("Here are your numbers, ordered:");

    for (i = 0; i < n; i++)
        printf("%d\n", arr[i]);

    free(arr);

    return 0;
}

C++ :

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main(void) {
    int n;

    cout << "How many integers do you want to input? ";
    cin >> n;

    vector<int> vec(n);

    for (int i = 0; i < vec.size(); i++) {
        cout << "Index " << i << ": ";
        cin >> vec[i];
    }

    sort(vec.begin(), vec.end());

    cout << "Here are your numbers:" << endl;

    for (int item : vec)
        cout << item << endl;

    return 0;
}

Hoffentlich können Sie klar sehen, was ich hier meine. Beachten Sie auch, wie Sie in C den Speicher mit malloc und free auf einer niedrigeren Ebene verwalten müssen und wie Sie beim Aufnehmen von Eingaben und Daten sehr genau vorgehen müssen Drucken.

44
Marco Bonelli

Zunächst gibt es ein paar Missverständnisse. Erstens führt das C++ - Programmnicht zu22 Anweisungen, es sind eher 22.000 von ihnen (ich habe diese Nummer von meinem Hut gezogen, aber es ist ungefähr im Baseballstadion). Außerdem führt der C-Codenicht zu9 Anweisungen. Das sind nur die, die du siehst.

Was der C-Code tut, ist, nachdem er eine Menge Dinge getan hat, die Sie nicht sehen, eine Funktion vom CRT aufzurufen (die normalerweise, aber nicht unbedingt, als gemeinsam genutzte Bibliothek vorhanden ist), dannprüft nicht auf den Rückgabewert oder behandelt Fehler und kommt heraus. Abhängig von den Einstellungen des Compilers und der Optimierung wird printf nicht wirklich aufgerufen, sondern puts oder etwas noch primitiveres.
Sie hätten auch mehr oder weniger dasselbe Programm (mit Ausnahme einiger unsichtbarer Init-Funktionen) in C++ schreiben können, wenn Sie dieselbe Funktion nur auf dieselbe Weise aufgerufen hätten. Oder, wenn Sie superkorrekt sein möchten, dieselbe Funktion mit dem Präfix std::.

Der entsprechende C++ - Code ist in Wirklichkeit überhaupt nicht dasselbe. Während die Gesamtheit von <iostream> dafür bekannt ist, ein fettes hässliches Schwein zu sein, das kleinen Programmen einen immensen Overhead hinzufügt (in einem "echten" Programm merkt man nicht wirklich so viel), ist es eine etwas gerechtere Interpretation dass es eine Menge Dinge tut, die man nicht sieht und diegerade funktioniert. Einschließlich, aber nicht beschränkt auf die magische Formatierung von so ziemlich jedem beliebigen Material, einschließlich verschiedener Zahlenformate und Gebietsschemas und so weiter, sowie Pufferung und ordnungsgemäße Fehlerbehandlung. Fehlerbehandlung? Nun ja, raten Sie mal, die Ausgabe eines Strings kann tatsächlich fehlschlagen, und im Gegensatz zum C-Programm würde das C++ - Programmnotdies unbemerkt ignorieren. Wenn man bedenkt, was std::ostream unter der Haube tut, und ohne dass jemand etwas davon merkt, ist es eigentlich ziemlich leicht. Nicht wie ich es benutze, weil ich die Stream-Syntax leidenschaftlich hasse. Aber trotzdem ist es ziemlich großartig, wenn man bedenkt, was es tut.

Aber sicher ist C++ insgesamtnichtso effizient wie C sein kann. Es kann nicht so effizient sein, da es nicht dasselbe ist und es nichtdasselbe tut. Wenn nicht anders angegeben, generiert C++ Ausnahmen (und Code zum Generieren, Behandeln oder Fehlschlagen), und es gibt einige Garantien, die C nicht gibt. Ein C++ - Programm muss also notwendigerweise ein bisschen größer sein. Im großen und ganzen spielt dies jedoch keine Rolle. Im Gegenteil, fürrealProgramme habe ich nicht selten festgestellt, dass C++ eine bessere Leistung erbringt, da es aus dem einen oder anderen Grund günstigere Optimierungen zu bieten scheint. Fragen Sie mich nicht, warum, ich würde es nicht wissen.

Wenn Sie anstelle von Feuer-und-Vergessen-Hoffnung-für-das-Beste C-Code schreiben möchten, derkorrektist (dh Sie prüfen tatsächlich auf Fehler, und die Programm verhält sich bei Fehlern korrekt), dann ist der Unterschied geringfügig, falls vorhanden.

27
Damon

Sie bezahlen für einen Fehler. In den 80er Jahren, als Compiler nicht gut genug waren, um Formatzeichenfolgen zu überprüfen, wurde das Überladen von Operatoren als ein guter Weg angesehen, um einen Anschein von Typensicherheit während io zu erzwingen. Jedes seiner Banner-Features ist jedoch von Anfang an entweder schlecht oder konzeptionell bankrott implementiert:

<iomanip>

Der widerlichste Teil des C++ - Streams io api ist das Vorhandensein dieser Formatierungsheaderbibliothek. Neben dem Status, der Hässlichkeit und der Fehleranfälligkeit wird auch die Formatierung an den Stream gekoppelt.

Angenommen, Sie möchten eine Zeile mit einem 8-stelligen hexadezimalen Int, gefolgt von einem Leerzeichen, gefolgt von einem Doppel mit 3 Dezimalstellen, ausgeben. Mit <cstdio> können Sie eine kurze Formatzeichenfolge lesen. Mit <ostream> müssen Sie den alten Zustand speichern, die Ausrichtung nach rechts einstellen, das Füllzeichen einstellen, die Füllbreite einstellen, die Basis auf hex setzen, die Ganzzahl ausgeben und den gespeicherten Zustand wiederherstellen (andernfalls verschmutzt Ihre Ganzzahl-Formatierung die Float-Formatierung ), geben Sie das Leerzeichen aus, setzen Sie die Notation auf fest, setzen Sie die Genauigkeit, geben Sie das Double und die Newline aus und stellen Sie dann die alte Formatierung wieder her.

// <cstdio>
std::printf( "%08x %.3lf\n", ival, fval );

// <ostream> & <iomanip>
std::ios old_fmt {nullptr};
old_fmt.copyfmt (std::cout);
std::cout << std::right << std::setfill('0') << std::setw(8) << std::hex << ival;
std::cout.copyfmt (old_fmt);
std::cout << " " << std::fixed << std::setprecision(3) << fval << "\n";
std::cout.copyfmt (old_fmt);

Bedienerüberladung

<iostream> ist das Aushängeschild für die Verwendung der Operatorüberladung:

std::cout << 2 << 3 && 0 << 5;

Performance

std::cout ist um ein Vielfaches langsamer printf(). Die grassierende Featuritis und der virtuelle Versand fordern ihren Tribut.

Thread-Sicherheit

Sowohl <cstdio> als auch <iostream> sind threadsicher, da jeder Funktionsaufruf atomar ist. Aber mit printf() wird pro Anruf noch viel mehr erledigt. Wenn Sie das folgende Programm mit der Option <cstdio> ausführen, wird nur eine Reihe von f angezeigt. Wenn Sie <iostream> auf einem Multicore-Computer verwenden, sehen Sie wahrscheinlich etwas anderes.

// g++ -Wall -Wextra -Wpedantic -pthread -std=c++17 cout.test.cpp

#define USE_STREAM 1
#define REPS 50
#define THREADS 10

#include <thread>
#include <vector>

#if USE_STREAM
    #include <iostream>
#else
    #include <cstdio>
#endif

void task()
{
    for ( int i = 0; i < REPS; ++i )
#if USE_STREAM
        std::cout << std::hex << 15 << std::dec;
#else
        std::printf ( "%x", 15);
#endif

}

int main()
{
    auto threads = std::vector<std::thread> {};
    for ( int i = 0; i < THREADS; ++i )
        threads.emplace_back(task);

    for ( auto & t : threads )
        t.join();

#if USE_STREAM
        std::cout << "\n<iostream>\n";
#else
        std::printf ( "\n<cstdio>\n" );
#endif
}

Die Erwiderung auf dieses Beispiel ist, dass die meisten Leute Disziplin ausüben, um niemals von mehreren Threads aus in einen einzelnen Dateideskriptor zu schreiben. Nun, in diesem Fall müssen Sie beachten, dass <iostream> bei jedem << und jedem >> hilfreich ist. Während Sie in <cstdio> nicht so oft sperren, haben Sie sogar die Möglichkeit, nicht zu sperren.

<iostream> verbraucht mehr Sperren, um ein weniger konsistentes Ergebnis zu erzielen.

22
KevinZ

Zusätzlich zu dem, was alle anderen Antworten gesagt haben,
Es gibt auch die Tatsache, dass std::endlnicht dasselbe wie '\n' ist.

Dies ist ein leider weit verbreitetes Missverständnis. std::endl bedeutet nicht "neue Zeile",
bedeutet "Neue Zeile drucken nd dann den Stream leeren". Spülen ist nicht billig!

Wenn Sie die Unterschiede zwischen printf und std::cout für einen Moment vollständig ignorieren, sollte Ihr C++ - Beispiel so aussehen, damit es funktional mit Ihrem C-Beispiel übereinstimmt:

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    return 0;
}

Und hier ist ein Beispiel dafür, wie Ihre Beispiele aussehen sollten, wenn Sie das Spülen einschließen.

C

#include <stdio.h>

int main()
{
    printf("Hello world\n");
    fflush(stdout);
    return 0;
}

C++

#include <iostream>

int main()
{
    std::cout << "Hello world\n";
    std::cout << std::flush;
    return 0;
}

Wenn Sie Code vergleichen , sollten Sie immer darauf achten, dass Sie Gleiches für Gleiches vergleichen und die Auswirkungen Ihrer Code-Aktivitäten verstehen. Manchmal sind sogar die einfachsten Beispiele komplizierter, als manche Leute glauben.

18
Pharap

Während die vorhandenen technischen Antworten korrekt sind, denke ich, dass die Frage letztendlich aus diesem Missverständnis resultiert:

Es ist berühmt, dass Sie in C++ für das bezahlen, was Sie essen.

Dies ist nur ein Marketinggespräch der C++ Community. (Fairerweise gibt es in jeder Sprachgemeinschaft Marketinggespräche.) Das bedeutet nichts Konkretes, worauf Sie sich wirklich verlassen können.

"Sie zahlen für das, was Sie verwenden" soll bedeuten, dass eine C++ - Funktion nur dann einen Overhead hat, wenn Sie diese Funktion verwenden. Aber die Definition von "einem Feature" ist nicht unendlich detailliert. Oft werden Sie Features mit mehreren Aspekten aktivieren, und obwohl Sie nur eine Teilmenge dieser Aspekte benötigen, ist dies oft nicht praktikabel oder möglich damit die Implementierung das Feature teilweise einbringt.

Im Allgemeinen streben viele (wenn auch nicht alle) Sprachen nach Effizienz und unterschiedlichem Erfolg. C++ ist irgendwo auf der Skala, aber es gibt nichts Besonderes oder Magisches an seinem Design, das es ihm ermöglichen würde, dieses Ziel perfekt zu erreichen.

Die Eingabe-/Ausgabefunktionen in C++ sind elegant geschrieben und benutzerfreundlich gestaltet. In vielerlei Hinsicht sind sie ein Vorzeigeobjekt für die objektorientierten Funktionen in C++.

Im Gegenzug geben Sie zwar ein bisschen Leistung auf, aber das ist vernachlässigbar, verglichen mit der Zeit, die Ihr Betriebssystem benötigt, um die Funktionen auf einer niedrigeren Ebene auszuführen.

Sie können jederzeit auf die C-Style-Funktionen zurückgreifen, da diese Teil des C++ - Standards sind, oder die Portabilität ganz aufgeben und direkte Aufrufe an Ihr Betriebssystem verwenden.

12
Bathsheba

Wie Sie in anderen Antworten gesehen haben, zahlen Sie, wenn Sie allgemeine Bibliotheken verlinken und komplexe Konstruktoren aufrufen. Es gibt hier keine besondere Frage, eher eine Beschwerde. Ich werde auf einige Aspekte der realen Welt hinweisen:

  1. Barne hatte ein zentrales Konstruktionsprinzip, nach dem Effizienz niemals ein Grund dafür sein sollte, in C statt in C++ zu bleiben. Das heißt, man muss vorsichtig sein, um diese Wirkungsgrade zu erzielen, und es gibt gelegentliche Wirkungsgrade, die immer funktionierten, aber nicht „technisch“ innerhalb der C-Spezifikation lagen. Beispielsweise wurde das Layout von Bitfeldern nicht wirklich spezifiziert.

  2. Versuchen Sie, durch ostream zu schauen. Oh mein Gott, es ist aufgebläht! Es würde mich nicht überraschen, dort einen Flugsimulator zu finden. Sogar stdlibs printf () läuft normalerweise über 50K. Dies sind keine faulen Programmierer: Die Hälfte der printf-Größe war auf indirekte Präzisionsargumente zurückzuführen, die die meisten Leute niemals verwenden. Fast jede wirklich eingeschränkte Prozessorbibliothek erstellt einen eigenen Ausgabecode anstelle von printf.

  3. Die Zunahme der Größe sorgt normalerweise für ein zurückhaltenderes und flexibleres Erlebnis. Analog dazu verkauft ein Verkaufsautomat eine Tasse Kaffee für ein paar Münzen, und die gesamte Transaktion dauert weniger als eine Minute. Das Betreten eines guten Restaurants setzt voraus, dass Sie einen Tisch gedeckt haben, Platz genommen haben, bestellt haben, gewartet haben, eine schöne Tasse bekommen haben, eine Rechnung erhalten haben, nach Ihren Wünschen zahlen, ein Trinkgeld hinzufügen und sich auf dem Weg nach draußen einen guten Tag wünschen. Es ist eine andere Erfahrung und praktischer, wenn Sie mit Freunden eine komplexe Mahlzeit zu sich nehmen.

  4. Die Leute schreiben immer noch ANSI C, aber selten K & R C. Ich habe die Erfahrung gemacht, dass wir es immer mit einem C++ - Compiler kompilieren, indem wir ein paar Konfigurationsänderungen vornehmen, um das Einziehen zu begrenzen. Es gibt gute Argumente für andere Sprachen: Go entfernt den polymorphen Overhead und den verrückten Präprozessor ; Es gab einige gute Argumente für eine intelligentere Feldverpackung und ein besseres Speicherlayout. Meiner Meinung nach sollte jedes Sprachdesign mit einer Liste von Zielen beginnen, ähnlich wie Zen of Python .

Es war eine lustige Diskussion. Sie fragen sich, warum Sie keine magisch kleinen, einfachen, eleganten, vollständigen und flexiblen Bibliotheken haben können?

Es gibt keine Antwort. Es wird keine Antwort geben. Das ist die Antwort.

1
Charles Merriam