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

138

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" in C ++ wundert.

Ich verwende die Clang- und GCC-Compiler, Versionen 7.0.1-8 und 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, ändert die Ergebnisse des Valgrind-Benchmarks. Die Werte bleiben jedoch über alle Compiler hinweg konsistent. Die Laufzeitzuordnungen (Peak) für das Programm lauten wie folgt:

  • 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 führe ich valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langjeden Compiler und jede Sprache aus und ms_printzeige dann die Peaks an.

Mache ich hier etwas falsch

Rerumu
quelle
11
Um damit zu beginnen, wie Sie bauen? Welche Optionen verwenden Sie? Und wie messen Sie? Wie läuft Valgrind?
Einige Programmierer Typ
17
Wenn ich mich richtig erinnere, müssen moderne C ++ - Compiler ein Ausnahmemodell verwenden, bei dem die Eingabe eines tryBlocks auf Kosten eines größeren Speicherbedarfs, möglicherweise mit einer Sprungtabelle oder Ähnlichem, nicht beeinträchtigt wird. Versuchen Sie vielleicht ausnahmslos zu kompilieren und sehen Sie, welche Auswirkungen dies hat. Bearbeiten: Versuchen Sie iterativ, verschiedene C ++ - Funktionen zu deaktivieren, um festzustellen, welche Auswirkungen dies auf den Speicherbedarf hat.
François Andrieux
3
Beim Kompilieren mit clang++ -xcstatt clangwar die gleiche Zuordnung vorhanden, was stark darauf hindeutet, dass es sich um verknüpfte Bibliotheken handelt
Justin
14
@bigwillydos Dies ist in der Tat C ++, ich sehe keinen Teil der C ++ - Spezifikationen, gegen die es verstößt ... Außer möglicherweise stdio.h anstelle von cstdio einzuschließen, aber dies ist zumindest in älteren C ++ - Versionen zulässig. Was ist Ihrer Meinung nach in diesem Programm "missgebildet"?
Vality
4
Ich finde es verdächtig, dass diese gcc- und clang-Compiler im CModus genau die gleiche Anzahl von Bytes und im Modus genau die gleiche Anzahl von Bytes erzeugen C++. Haben Sie einen Transkriptionsfehler gemacht?
RonJohn

Antworten:

149

Die Heap-Nutzung stammt aus der C ++ - Standardbibliothek. Es reserviert Speicher für die interne Bibliotheksnutzung beim Start. Wenn Sie nicht dagegen verlinken, sollte zwischen der C- und der C ++ - Version kein Unterschied bestehen. Mit GCC und Clang können Sie die Datei kompilieren mit:

g ++ -Wl, - nach Bedarf main.cpp

Dadurch wird der Linker angewiesen, keine Verknüpfung mit nicht verwendeten Bibliotheken herzustellen. 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 Verwendung des Heapspeichers hängt offensichtlich von der spezifischen Implementierung der C ++ - Bibliothek ab, die Sie verwenden. In Ihrem Fall ist dies die GNU C ++ - Bibliothek libstdc ++ . Andere Implementierungen weisen möglicherweise nicht die gleiche Speichermenge oder überhaupt keinen Speicher zu (zumindest nicht beim Start). Die LLVM C ++ - Bibliothek ( libc ++ ) führt beispielsweise beim Start keine Heap-Zuweisung durch, zumindest unter Linux Maschine:

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

Die Heap-Verwendung ist die gleiche, 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".)

Nikos C.
quelle
50
Als ich diese Antwort sehe, ist mein erster Gedanke: " Wenn dieses Flag dazu beiträgt, unnötigen Overhead zu reduzieren, warum ist es nicht standardmäßig aktiviert? ". Gibt es eine gute Antwort darauf?
Nat
4
@ Nat Meine Vermutung ist zur Entwicklungszeit, dass das Kompilieren langsamer ist. Wenn Sie bereit sind, einen Release-Build zu erstellen, aktivieren Sie ihn dann. Auch in einer normalen / großen Codebasis kann der Unterschied minimal sein (wenn Sie viele der STD-Bibliotheken usw. verwenden)
DarcyThomas
24
@Nat Das -Wl,--as-neededFlag entfernt Bibliotheken, die Sie in Ihren -lFlags angegeben haben, die Sie jedoch nicht verwenden. Wenn Sie also keine Bibliothek verwenden, verknüpfen Sie sie einfach nicht. Sie brauchen diese Flagge dafür nicht. Wenn Ihr Build-System jedoch zu viele Bibliotheken hinzufügt und es eine Menge Arbeit wäre, 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 mit verknüpft wird. Das ist also ein Eckfall.
Nikos C.
36
@Nat --as-benötigt kann unerwünschte Nebenwirkungen haben. Es überprüft, ob Sie ein Symbol einer Bibliothek verwenden, und wirft diejenigen aus, die den Test nicht bestehen. ABER: Eine Bibliothek kann auch implizit verschiedene Aufgaben ausführen. 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 erforderlich ist, in die Sie nicht explizit aufrufen, die jedoch vorhanden ist.
Norbert Lange
3
@ NikosC. Buildsysteme wissen nicht automatisch, welche Symbole Ihre Anwendung verwendet und welche Bibliotheken sie implementieren (variiert zwischen Compilern, Bögen, Distributionen und c / c ++ - Bibliotheken). Das richtig zu machen ist ziemlich mühsam, zumindest für die Basis-Laufzeitbibliotheken. In den seltenen Fällen, in denen Sie eine Bibliothek benötigen, sollten Sie einfach - nicht nach Bedarf für diese verwenden und - wie überall benötigt - verlassen. Ein Anwendungsfall, den ich gesehen habe, sind Bibliotheken zum Verfolgen / Debuggen (lttng) und Bibliotheken, die etwas von der Art der Authentifizierung / Verbindung ausführen.
Norbert Lange
16

Weder GCC noch Clang sind Compiler - sie sind tatsächlich 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, wird dieselbe Assembly erstellt. Der Assembler erzeugt dieselben Objekte. Der Unterschied besteht darin, dass der Toolchain-Treiber für die beiden verschiedenen Sprachen unterschiedliche Eingaben für den Linker bereitstellt: unterschiedliche Starts (C ++ erfordert Code zum Ausführen von Konstruktoren und Destruktoren für Objekte mit statischer oder threadlokaler Speicherdauer auf Namespace-Ebene und Infrastruktur für den Stapel Frames zur Unterstützung des Abwickelns beispielsweise während der Ausnahmeverarbeitung), die C ++ - Standardbibliothek (die auch Objekte mit statischer Speicherdauer auf Namespace-Ebene enthält) und wahrscheinlich zusätzliche Laufzeitbibliotheken (z. B. libgcc mit seiner Stack-Abwicklungsinfrastruktur).

Kurz gesagt, es ist nicht der Compiler, der den Footprint vergrößert, sondern das Verknüpfen von Dingen, die Sie durch Auswahl der C ++ - Sprache verwendet haben.

Es ist wahr, dass C ++ die Philosophie "Nur für das bezahlen, was Sie verwenden" hat, aber wenn Sie die Sprache verwenden, zahlen Sie dafür. Sie können Teile der Sprache deaktivieren (RTTI, Ausnahmebehandlung), aber dann verwenden Sie C ++ nicht mehr. Wie in einer anderen Antwort erwähnt, können Sie den Treiber anweisen, dies wegzulassen, wenn Sie die Standardbibliothek überhaupt nicht verwenden (--Wl, - nach Bedarf), aber wenn Sie keine der Funktionen verwenden Warum wählen Sie in C ++ oder seiner Bibliothek überhaupt C ++ als Programmiersprache?

Stephen M. Webb
quelle
Die Tatsache, dass das Aktivieren der Ausnahmebehandlung Kosten verursacht, auch wenn Sie sie nicht tatsächlich verwenden, ist ein Problem. Dies ist für C ++ - Funktionen im Allgemeinen nicht normal, und die C ++ - Standardarbeitsgruppen versuchen, Möglichkeiten zur Behebung zu finden. Siehe Herb Sutters Keynote-Vortrag auf der ACCU 2019 De-Fragmenting C ++: Ausnahmen erschwinglicher und benutzerfreundlicher machen . In der aktuellen Version von C ++ ist dies jedoch eine unglückliche Tatsache. Und herkömmliche C ++ - Ausnahmen haben wahrscheinlich immer diese Kosten, selbst wenn / wenn neue Mechanismen für neue Ausnahmen mit einem Schlüsselwort hinzugefügt werden.
Peter Cordes