Pourquoi l'allocation initiale de C ++ est-elle tellement plus grande que celle de C?

138

Lorsque vous utilisez le même code, changer simplement le compilateur (d'un compilateur C à un compilateur C ++) changera la quantité de mémoire allouée. Je ne sais pas trop pourquoi et j'aimerais mieux comprendre. Jusqu'à présent, la meilleure réponse que j'ai obtenue est "probablement les flux d'E / S", ce qui n'est pas très descriptif et me fait m'interroger sur l'aspect "vous ne payez pas pour ce que vous n'utilisez pas" du C ++.

J'utilise les compilateurs Clang et GCC, versions 7.0.1-8 et 8.3.0-6 respectivement. Mon système fonctionne sur Debian 10 (Buster), le dernier. Les benchmarks se font via Valgrind Massif.

#include <stdio.h>

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

Le code utilisé ne change pas, mais que je compile en C ou en C ++, cela change les résultats du benchmark Valgrind. Les valeurs restent cependant cohérentes entre les compilateurs. Les allocations d'exécution (pic) pour le programme se présentent comme suit:

  • GCC (C): 1 032 octets (1 Ko)
  • G ++ (C ++): 73 744 octets, (~ 74 Ko)
  • Clang (C): 1 032 octets (1 Ko)
  • Clang ++ (C ++): 73 744 octets (~ 74 Ko)

Pour la compilation, j'utilise les commandes suivantes:

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

Pour Valgrind, je tourne valgrind --tool=massif --massif-out-file=m_compiler_lang ./compiler-langsur chaque compilateur et langage, puis ms_printpour afficher les pics.

Est-ce que je fais quelque chose de mal ici?

Rerumu
la source
11
Pour commencer, comment construisez-vous? Quelles options utilisez-vous? Et comment mesurez-vous? Comment dirigez-vous Valgrind?
Un mec programmeur le
17
Si je me souviens bien, les compilateurs C ++ modernes doivent utiliser un modèle d'exception où les performances ne sont pas affectées pour entrer dans un trybloc au détriment d'une plus grande empreinte mémoire, peut-être avec une table de saut ou quelque chose. Essayez peut-être de compiler sans exceptions et voyez quel impact cela a. Edit: En fait, essayez de désactiver de manière itérative diverses fonctionnalités c ++ pour voir quel impact cela a sur l'empreinte mémoire.
François Andrieux
3
Lors de la compilation avec clang++ -xcau lieu de clang, la même allocation était là, ce qui suggère fortement que cela est dû aux bibliothèques liées
Justin
14
@bigwillydos C'est en effet du C ++, je ne vois aucune partie des spécifications C ++ qu'il casse ... Autre que potentiellement inclure stdio.h plutôt que cstdio mais cela est autorisé au moins dans les anciennes versions de C ++. Selon vous, qu'est-ce qui est «mal formé» dans ce programme?
Vality
4
Je trouve suspect que ces compilateurs gcc et clang génèrent exactement le même nombre d'octets en Cmode et le même nombre exact d'octets en C++mode. Avez-vous fait une erreur de transcription?
RonJohn

Réponses:

149

L'utilisation du tas provient de la bibliothèque standard C ++. Il alloue de la mémoire pour une utilisation interne de la bibliothèque au démarrage. Si vous ne le liez pas, il ne devrait y avoir aucune différence entre la version C et C ++. Avec GCC et Clang, vous pouvez compiler le fichier avec:

g ++ -Wl, - au besoin main.cpp

Cela demandera à l'éditeur de liens de ne pas établir de lien avec des bibliothèques inutilisées. Dans votre exemple de code, la bibliothèque C ++ n'est pas utilisée, elle ne doit donc pas être liée à la bibliothèque standard C ++.

Vous pouvez également tester cela avec le fichier C. Si vous compilez avec:

gcc main.c -lstdc ++

L'utilisation du tas réapparaîtra, même si vous avez construit un programme C.

L'utilisation du tas dépend évidemment de l'implémentation de bibliothèque C ++ spécifique que vous utilisez. Dans votre cas, c'est la bibliothèque GNU C ++, libstdc ++ . D'autres implémentations peuvent ne pas allouer la même quantité de mémoire, ou ne pas allouer de mémoire du tout (du moins pas au démarrage.) La bibliothèque LLVM C ++ ( libc ++ ) par exemple ne fait pas d'allocation de tas au démarrage, du moins sur mon Linux machine:

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

L'utilisation du tas est la même que de ne pas du tout lier contre lui.

(Si la compilation échoue, alors libc ++ n'est probablement pas installée. Le nom du paquet contient généralement "libc ++" ou "libcxx".)

Nikos C.
la source
50
En voyant cette réponse, ma première pensée est: " Si cet indicateur permet de réduire les frais généraux inutiles, pourquoi n'est-il pas activé par défaut? ". Y a-t-il une bonne réponse à cela?
Nat
4
@Nat Je suppose qu'au moment du développement, il est plus lent à compiler. Lorsque vous êtes prêt à créer une version de version, vous l'activez alors. Également dans une base de code normale / large, la différence peut être minime (si vous utilisez beaucoup de bibliothèque STD, etc.)
DarcyThomas
24
@Nat L' -Wl,--as-neededindicateur supprime les bibliothèques que vous spécifiez dans vos -lindicateurs mais que vous n'utilisez pas réellement. Donc, si vous n'utilisez pas de bibliothèque, alors ne liez pas contre elle. Vous n'avez pas besoin de ce drapeau pour cela. Cependant, si votre système de construction ajoute trop de bibliothèques et qu'il faudrait beaucoup de travail pour toutes les nettoyer et ne lier que celles nécessaires, vous pouvez utiliser cet indicateur à la place. La bibliothèque standard est cependant une exception, car elle est automatiquement liée à. C'est donc une affaire de coin.
Nikos C.
36
@Nat - au besoin peut avoir des effets secondaires indésirables, il fonctionne en vérifiant si vous utilisez un symbole d'une bibliothèque et expulse ceux qui échouent au test. MAIS: une bibliothèque peut également faire diverses choses implicitement, par exemple, si vous avez une instance C ++ statique dans la bibliothèque, son constructeur sera automatiquement appelé. Il existe de rares cas où une bibliothèque que vous n'appelez pas explicitement est nécessaire, mais elle existe.
Norbert Lange
3
@NikosC. Les buildsystems ne savent pas automatiquement quels symboles votre application utilise et quelles bibliothèques les implémentent (varie entre les compilateurs, les archs, les distributions et les bibliothèques c / c ++). Obtenir ce droit est plutôt gênant, au moins pour les bibliothèques d'exécution de base. Mais pour les rares cas où vous avez besoin d'une bibliothèque, vous devez simplement utiliser --no-as-needed pour celle-ci, et laisser - comme nécessaire partout ailleurs. Un cas d'utilisation que j'ai vu est celui des bibliothèques de traçage / débogage (lttng) et des bibliothèques qui font quelque chose du genre authentification / connexion.
Norbert Lange
16

Ni GCC ni Clang ne sont des compilateurs - ce sont en fait des programmes de pilote de chaîne d'outils. Cela signifie qu'ils invoquent le compilateur, l'assembleur et l'éditeur de liens.

Si vous compilez votre code avec un compilateur C ou C ++, vous obtiendrez le même assembly produit. L'assembleur produira les mêmes objets. La différence est que le pilote de la chaîne d'outils fournira une entrée différente à l'éditeur de liens pour les deux langues différentes: des démarrages différents (C ++ nécessite du code pour exécuter les constructeurs et les destructeurs pour les objets avec une durée de stockage statique ou locale au niveau de l'espace de noms, et nécessite une infrastructure pour la pile frames pour prendre en charge le déroulement pendant le traitement des exceptions, par exemple), la bibliothèque standard C ++ (qui a également des objets de durée de stockage statique au niveau de l'espace de noms), et probablement des bibliothèques d'exécution supplémentaires (par exemple, libgcc avec son infrastructure de déroulement de pile).

En bref, ce n'est pas le compilateur qui cause l'augmentation de l'encombrement, c'est la liaison des éléments que vous avez choisi d'utiliser en choisissant le langage C ++.

Il est vrai que C ++ a la philosophie «ne payez que pour ce que vous utilisez», mais en utilisant le langage, vous le payez. Vous pouvez désactiver certaines parties du langage (RTTI, gestion des exceptions) mais vous n'utilisez plus C ++. Comme mentionné dans une autre réponse, si vous n'utilisez pas du tout la bibliothèque standard, vous pouvez demander au pilote de la laisser de côté (--Wl, - selon les besoins) mais si vous n'utilisez aucune des fonctionnalités de C ++ ou de sa bibliothèque, pourquoi choisissez-vous même C ++ comme langage de programmation?

Stephen M. Webb
la source
Le fait que l'activation de la gestion des exceptions a un coût même si vous ne l'utilisez pas est un problème. Ce n'est pas normal pour les fonctionnalités C ++ en général, et c'est quelque chose que les groupes de travail sur les normes C ++ essaient de trouver des moyens de corriger. Voir le discours d'ouverture de Herb Sutter à ACCU 2019 De-fragmenting C ++: Rendre les exceptions plus abordables et utilisables . C'est un fait malheureux, cependant, dans le C ++ actuel. Et les exceptions C ++ traditionnelles auront probablement toujours ce coût, même si / quand de nouveaux mécanismes pour de nouvelles exceptions sont ajoutés avec un mot clé.
Peter Cordes