Perché l'allocazione iniziale di C ++ è molto più grande di quella di C?


138

Quando si utilizza lo stesso codice, la semplice modifica del compilatore (da un compilatore C a un compilatore C ++) cambierà la quantità di memoria allocata. Non sono del tutto sicuro del perché questo sia e vorrei capirlo di più. Finora la migliore risposta che ho ricevuto è "probabilmente i flussi I / O", che non è molto descrittivo e mi fa meravigliare dell'aspetto "non paghi per ciò che non usi" del C ++.

Sto usando i compilatori Clang e GCC, rispettivamente versioni 7.0.1-8 e 8.3.0-6. Il mio sistema è in esecuzione su Debian 10 (Buster), più recente. I parametri di riferimento vengono eseguiti tramite il massiccio di Valgrind.

#include <stdio.h>

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

Il codice utilizzato non cambia, ma se compilo come C o come C ++, cambia i risultati del benchmark Valgrind. I valori rimangono coerenti tra i compilatori, tuttavia. Le allocazioni di runtime (picco) per il programma sono le seguenti:

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

Per la compilazione, utilizzo i seguenti comandi:

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

Per Valgrind, corro valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langsu ogni compilatore e lingua, quindi ms_printper visualizzare i picchi.

Sto facendo qualcosa di sbagliato qui?


11
Per cominciare, come stai costruendo? Quali opzioni usi? E come si misura? Come si esegue Valgrind?
Qualche programmatore, amico, il

17
Se ricordo bene, i compilatori C ++ moderni hanno un modello di eccezione in cui non si riscontra alcun tryimpatto sulle prestazioni nell'entrare in un blocco a spese di un ingombro di memoria maggiore, magari con una tabella di salto o qualcosa del genere. Forse prova a compilare senza eccezioni e vedi quale impatto ha. Modifica: in effetti, prova iterativamente a disabilitare varie funzionalità di c ++ per vedere quale impatto ha sul footprint di memoria.
François Andrieux,

3
Durante la compilazione clang++ -xcinvece di clang, c'era la stessa allocazione, il che suggerisce fortemente la sua causa a causa delle biblioteche collegate
Giustino

14
@bigwillydos Questo è davvero C ++, non vedo nessuna parte delle specifiche C ++ che si rompe ... Oltre a includere potenzialmente stdio.h piuttosto che cstdio, ma questo è consentito almeno nella versione C ++ precedente. Cosa pensi che sia "malformato" in questo programma?
Vality,

4
Trovo sospetto che quei compilatori gcc e clang generino lo stesso numero esatto di byte in Cmodalità e lo stesso esatto numero di byte C++modalità. Hai fatto un errore di trascrizione?
RonJohn,

Risposte:


149

L'utilizzo dell'heap proviene dalla libreria standard C ++. Alloca memoria per l'utilizzo interno della libreria all'avvio. Se non ci si collega a questo, non ci dovrebbe essere alcuna differenza tra la versione C e C ++. Con GCC e Clang, puoi compilare il file con:

g ++ -Wl, - secondo necessità main.cpp

Questo indicherà al linker di non collegarsi a librerie inutilizzate. Nel codice di esempio, la libreria C ++ non viene utilizzata, pertanto non deve essere collegata alla libreria standard C ++.

Puoi anche testarlo con il file C. Se compili con:

gcc main.c -lstdc ++

L'utilizzo dell'heap riapparirà, anche se hai creato un programma C.

L'uso dell'heap dipende ovviamente dalla specifica implementazione della libreria C ++ che stai utilizzando. Nel tuo caso, questa è la libreria GNU C ++, libstdc ++ . Altre implementazioni potrebbero non allocare la stessa quantità di memoria o potrebbero non allocare alcuna memoria (almeno non all'avvio.) La libreria LLVM C ++ ( libc ++ ), ad esempio, non esegue l'allocazione dell'heap all'avvio, almeno sul mio Linux macchina:

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

L'uso dell'heap è lo stesso di non collegarsi affatto contro di esso.

(Se la compilazione fallisce, probabilmente libc ++ non è installato. Il nome del pacchetto di solito contiene "libc ++" o "libcxx".)


50
Nel vedere questa risposta, il mio primo pensiero è " Se questo flag aiuta a ridurre le spese generali non necessarie, perché non è attivo per impostazione predefinita? ". C'è una buona risposta a questo?
Nat

4
@Nat La mia ipotesi è al momento dello sviluppo è più lenta da compilare. Quando sei pronto per creare una build di rilascio, la accendi. Anche in una base di codice normale / grande la differenza può essere minima (se si utilizza molta libreria STD, ecc.)
DarcyThomas

24
@Nat Il -Wl,--as-neededflag rimuove le librerie specificate nei -lflag ma non si stanno effettivamente utilizzando. Quindi, se non usi una libreria, non limitarti a collegarti. Non hai bisogno di questa bandiera per questo. Tuttavia, se il tuo sistema di compilazione aggiunge troppe librerie e sarebbe molto difficile ripulirle tutte e collegare solo quelle necessarie, puoi invece usare questo flag. La libreria standard è un'eccezione, poiché è automaticamente collegata. Quindi questo è un caso angolare.
Nikos C.,

36
@Nat - come necessario può avere effetti collaterali indesiderati, funziona controllando se usi un simbolo di una libreria e butta fuori quelli che non superano il test. MA: una libreria potrebbe anche fare varie cose in modo implicito, ad esempio, se hai un'istanza C ++ statica nella libreria, il suo costruttore verrà automaticamente chiamato. Ci sono rari casi in cui è necessaria una libreria in cui non si chiama esplicitamente, ma esistono.
Norbert Lange,

3
@NikosC. Buildsystems non conosce automaticamente quali simboli utilizza l'applicazione e quali librerie li implementano (varia tra compilatori, archi, distribuzioni e librerie c / c ++). Ottenere ciò nel modo giusto è piuttosto problematico, almeno per le librerie di runtime di base. Ma per i rari casi in cui hai bisogno di una biblioteca, dovresti semplicemente usare - non necessario per quello, e lasciare - come necessario ovunque. Un caso d'uso che ho visto sono librerie per traccia / debugging (lttng) e librerie che fanno qualcosa del tipo di autenticazione / connessione.
Norbert Lange,

16

Né GCC né Clang sono compilatori: in realtà sono programmi driver per toolchain. Ciò significa che invocano il compilatore, l'assemblatore e il linker.

Se si compila il codice con un compilatore C o C ++, verrà prodotto lo stesso assembly. L'assemblatore produrrà gli stessi oggetti. La differenza è che il driver della toolchain fornirà input diversi al linker per le due diverse lingue: diversi startup (C ++ richiede codice per eseguire costruttori e distruttori per oggetti con durata di archiviazione statica o thread-locale a livello di spazio dei nomi e richiede l'infrastruttura per lo stack frame per supportare lo svolgimento durante l'elaborazione delle eccezioni, ad esempio), la libreria standard C ++ (che ha anche oggetti di durata dell'archiviazione statica a livello di spazio dei nomi) e probabilmente librerie di runtime aggiuntive (ad esempio libgcc con la sua infrastruttura di svolgimento dello stack).

In breve, non è il compilatore che causa l'aumento dell'ingombro, è il collegamento di cose che hai scelto di usare scegliendo il linguaggio C ++.

È vero che il C ++ ha la filosofia "paga solo per ciò che usi", ma usando il linguaggio lo paghi. È possibile disabilitare parti del linguaggio (RTTI, gestione delle eccezioni) ma non si utilizza più C ++. Come menzionato in un'altra risposta, se non si utilizza affatto la libreria standard è possibile indicare al driver di lasciarlo fuori (--Wl, - secondo necessità) ma se non si intende utilizzare nessuna delle funzionalità del C ++ o della sua libreria, perché scegli il C ++ come linguaggio di programmazione?


Il fatto che abilitare la gestione delle eccezioni abbia un costo anche se non lo si utilizza effettivamente è un problema. Questo non è normale per le funzionalità C ++ in generale, ed è qualcosa che i gruppi di lavoro sugli standard C ++ stanno cercando di pensare a come risolvere. Guarda il discorso di base di Herb Sutter all'ACCU 2019 De-frammenting C ++: Rendere le eccezioni più convenienti e utilizzabili . È un fatto sfortunato, tuttavia, nell'attuale C ++. E le eccezioni C ++ tradizionali avranno probabilmente sempre quel costo, anche se / quando nuovi meccanismi per nuove eccezioni vengono aggiunti con una parola chiave.
Peter Cordes,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.