Warum ist die anfängliche Zuordnung von C++ so viel größer als die von C?

Lesezeit: 7 Minuten

Benutzeravatar von Rerumu
Rerumu

Wenn Sie denselben Code verwenden, ändert sich durch einfaches Ändern des Compilers (von einem C-Compiler zu einem C++-Compiler), wie viel Speicher zugewiesen wird. Ich bin mir nicht ganz sicher, warum das so ist und würde es gerne besser verstehen. Bisher ist die beste Antwort, die ich erhalten habe, “wahrscheinlich die E/A-Streams”, was nicht sehr beschreibend ist und mich über den Aspekt “Sie zahlen nicht für das, was Sie nicht verwenden” von C++ wundern lässt.

Ich verwende die Clang- und GCC-Compiler, Versionen 7.0.1-8 bzw. 8.3.0-6. Mein System läuft auf Debian 10 (Buster), spätestens. Die Benchmarks werden über das Valgrind-Massiv durchgeführt.

#include <stdio.h>

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

Der verwendete Code ändert sich nicht, aber ob ich als C oder als C++ kompiliere, es ändert die Ergebnisse des Valgrind-Benchmarks. Die Werte bleiben jedoch über Compiler hinweg konsistent. Die Laufzeitbelegungen (Peak) für das Programm sehen wie folgt aus:

  • GCC (C): 1.032 Bytes (1 KB)
  • G++ (C++): 73.744 Bytes, (~74 KB)
  • Clang (C): 1.032 Bytes (1 KB)
  • Clang++ (C++): 73.744 Bytes (~74 KB)

Zum Kompilieren verwende ich folgende Befehle:

clang -O3 -o c-clang ./main.c
gcc -O3 -o c-gcc ./main.c
clang++ -O3 -o cpp-clang ./main.cpp
g++ -O3 -o cpp-gcc ./main.cpp

Für Valgrind laufe ich valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-lang auf jedem Compiler und Sprache, dann ms_print zur Anzeige der Peaks.

Mache ich hier etwas falsch?

  • Zunächst, wie baust du? Welche Möglichkeiten nutzen Sie? Und wie misst du? Wie führt man Valgrind?

    – Irgendein Programmierer-Typ

    20. Juni 2019 um 18:45 Uhr

  • Wenn ich mich richtig erinnere, haben moderne C++-Compiler ein Ausnahmemodell, bei dem es keine Leistungseinbußen gibt, wenn man a eingibt try Block auf Kosten eines größeren Speicherbedarfs, vielleicht mit einer Sprungtabelle oder so. Versuchen Sie vielleicht, ohne Ausnahmen zu kompilieren, und sehen Sie, welche Auswirkungen das hat. Bearbeiten: Versuchen Sie tatsächlich, verschiedene C++-Funktionen iterativ zu deaktivieren, um zu sehen, welche Auswirkungen dies auf den Speicherbedarf hat.

    – Francois Andrieux

    20. Juni 2019 um 18:48 Uhr


  • Beim Kompilieren mit clang++ -xc Anstatt von clangwar die gleiche Zuordnung vorhanden, was stark darauf hindeutet, dass sie auf verknüpfte Bibliotheken zurückzuführen ist

    – Justin

    20. Juni 2019 um 18:51 Uhr

  • @bigwillydos Dies ist in der Tat C ++, ich sehe keinen Teil der C ++ – Spezifikationen, die es bricht … Abgesehen davon, dass möglicherweise stdio.h anstelle von cstdio enthalten ist, ist dies jedoch zumindest in älteren C ++ – Versionen zulässig. Was ist Ihrer Meinung nach in diesem Programm “fehlerhaft”?

    – Wertigkeit

    21. Juni 2019 um 3:34 Uhr

  • Ich finde es verdächtig, dass diese gcc- und clang-Compiler genau die gleiche Anzahl von Bytes erzeugen C Modus und genau die gleiche Anzahl von Bytes C++ Modus. Ist Ihnen ein Übertragungsfehler unterlaufen?

    – RonJohn

    22. Juni 2019 um 0:13 Uhr

Benutzeravatar von Nikos C
Nikos C.

Die Heap-Nutzung stammt aus der C++-Standardbibliothek. Es weist beim Start Speicher für die Verwendung der internen Bibliothek zu. Wenn Sie nicht dagegen verlinken, sollte es keinen Unterschied zwischen der C- und der C++-Version geben. Mit GCC und Clang können Sie die Datei kompilieren mit:

g++ -Wl,--as-needed main.cpp

Dadurch wird der Linker angewiesen, nicht mit unbenutzten Bibliotheken zu verknüpfen. In Ihrem Beispielcode wird die C++-Bibliothek nicht verwendet, daher sollte sie nicht mit der C++-Standardbibliothek verknüpft werden.

Sie können dies auch mit der C-Datei testen. Wenn Sie kompilieren mit:

gcc main.c -lstdc++

Die Heap-Nutzung wird wieder angezeigt, obwohl Sie ein C-Programm erstellt haben.

Die Heap-Nutzung hängt offensichtlich von der spezifischen C++-Bibliotheksimplementierung ab, die Sie verwenden. In Ihrem Fall ist das die GNU C++-Bibliothek, libstdc++. Andere Implementierungen weisen möglicherweise nicht die gleiche Speichermenge oder gar keinen Speicher zu (zumindest nicht beim Start). Die LLVM-C++-Bibliothek (libc++) führt zum Beispiel beim Start keine Heap-Zuweisung durch, zumindest nicht auf meinem Linux-Rechner:

clang++ -stdlib=libc++ main.cpp

Die Heap-Nutzung ist dasselbe, als würde man überhaupt nicht dagegen verlinken.

(Wenn die Kompilierung fehlschlägt, ist libc++ wahrscheinlich nicht installiert. Der Paketname enthält normalerweise „libc++“ oder „libcxx“.)

  • Als ich diese Antwort sehe, ist mein erster Gedanke: “Wenn dieses Flag dazu beiträgt, unnötigen Overhead zu reduzieren, warum ist es dann nicht standardmäßig aktiviert?“. Gibt es darauf eine gute Antwort?

    – Nat

    21. Juni 2019 um 3:48 Uhr

  • @Nat Meine Vermutung ist, dass es zur Entwicklungszeit langsamer zu kompilieren ist. Wenn Sie bereit sind, einen Release-Build zu erstellen, würden Sie ihn dann aktivieren. Auch in einer normalen/großen Codebasis kann der Unterschied minimal sein (wenn Sie viele STD-Bibliotheken usw. verwenden).

    – DarcyThomas

    21. Juni 2019 um 5:04 Uhr

  • @Nat Die -Wl,--as-needed Flag entfernt Bibliotheken, die Sie in Ihrer angeben -l Flags, aber Sie verwenden sie nicht wirklich. Wenn Sie also keine Bibliothek verwenden, dann verlinken Sie einfach nicht dagegen. Sie brauchen dieses Flag dafür nicht. Wenn Ihr Build-System jedoch zu viele Bibliotheken hinzufügt und es eine Menge Arbeit wäre, sie alle zu bereinigen und nur die benötigten zu verknüpfen, können Sie stattdessen dieses Flag verwenden. Die Standardbibliothek ist jedoch eine Ausnahme, da sie automatisch gelinkt wird. Das ist also ein Eckfall.

    – Nikos C.

    21. Juni 2019 um 7:03 Uhr

  • @Nat –as-needed kann unerwünschte Nebeneffekte haben, es überprüft, ob Sie ein Symbol einer Bibliothek verwenden, und wirft diejenigen aus, die den Test nicht bestehen. ABER: Eine Bibliothek könnte auch implizit verschiedene Dinge tun, wenn Sie beispielsweise eine statische C++-Instanz in der Bibliothek haben, wird ihr Konstruktor automatisch aufgerufen. Es gibt seltene Fälle, in denen eine Bibliothek, die Sie nicht explizit aufrufen, erforderlich ist, aber sie existiert.

    – Norbert Lange

    21. Juni 2019 um 8:16 Uhr

  • @NikosC. Buildsysteme wissen nicht automatisch, welche Symbole Ihre Anwendung verwendet und welche Bibliotheken sie implementieren (variiert zwischen Compilern, Archs, Distributionen und c/c++-Bibliotheken). Das richtig zu machen, ist ziemlich mühsam, zumindest für die Basis-Laufzeitbibliotheken. Aber für die seltenen Fälle, in denen Sie eine Bibliothek benötigen, sollten Sie einfach –no-as-needed für diese verwenden und –as-needed überall sonst lassen. Ein Anwendungsfall, den ich gesehen habe, sind Bibliotheken zum Verfolgen/Debuggen (lttng) und Bibliotheken, die so etwas wie Authentifizieren/Verbinden tun.

    – Norbert Lange

    21. Juni 2019 um 9:04 Uhr

Weder GCC noch Clang sind Compiler – sie sind eigentlich Toolchain-Treiberprogramme. Das heißt, sie rufen den Compiler, den Assembler und den Linker auf.

Wenn Sie Ihren Code mit einem C- oder C++-Compiler kompilieren, erhalten Sie dieselbe Assembly. Der Assembler erzeugt die gleichen Objekte. Der Unterschied besteht darin, dass der Toolchain-Treiber dem Linker unterschiedliche Eingaben für die beiden unterschiedlichen Sprachen bereitstellt: unterschiedliche Startups (C++ erfordert Code zum Ausführen von Konstruktoren und Destruktoren für Objekte mit statischer oder Thread-lokaler Speicherdauer auf Namespace-Ebene und erfordert Infrastruktur für Stack Rahmen, um zum Beispiel das Entladen während der Ausnahmeverarbeitung zu unterstützen), die C++-Standardbibliothek (die auch Objekte mit statischer Speicherdauer auf Namespace-Ebene hat) und wahrscheinlich zusätzliche Laufzeitbibliotheken (zum Beispiel libgcc mit seiner Infrastruktur zum Entladen des Stapels).

Kurz gesagt, es ist nicht der Compiler, der die Zunahme des Platzbedarfs verursacht, sondern das Einbinden von Dingen, die Sie verwenden möchten, indem Sie die Sprache C++ auswählen.

Es ist wahr, dass C++ die Philosophie „zahle nur für das, was du verwendest“ verfolgt, aber wenn du die Sprache verwendest, bezahlst du dafür. Sie können Teile der Sprache (RTTI, Ausnahmebehandlung) deaktivieren, aber dann verwenden Sie C++ nicht mehr. Wie in einer anderen Antwort erwähnt, können Sie, wenn Sie die Standardbibliothek überhaupt nicht verwenden, den Treiber anweisen, dies wegzulassen (–Wl,–as-needed), aber wenn Sie keine der Funktionen verwenden werden von C++ oder seiner Bibliothek, warum wählen Sie überhaupt C++ als Programmiersprache?

  • Die Tatsache, dass die Aktivierung der Ausnahmebehandlung Kosten verursacht, selbst wenn Sie sie nicht wirklich verwenden, ist ein Problem. Das ist für C++-Features im Allgemeinen nicht normal, und es ist etwas, das die C++-Standardarbeitsgruppen versuchen, Wege zu finden, es zu beheben. Siehe den Keynote-Vortrag von Herb Sutter auf der ACCU 2019 C++ defragmentieren: Ausnahmen erschwinglicher und benutzerfreundlicher machen. In aktuellem C++ ist dies jedoch eine unglückliche Tatsache. Und traditionelle C++-Ausnahmen werden wahrscheinlich immer diese Kosten haben, selbst wenn/wenn neue Mechanismen für neue Ausnahmen mit einem Schlüsselwort hinzugefügt werden.

    – Peter Cordes

    26. Juni 2019 um 3:52 Uhr


1423680cookie-checkWarum ist die anfängliche Zuordnung von C++ so viel größer als die von C?

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

Privacy policy