En C ++, combien de temps le programmeur consacre-t-il à la gestion de la mémoire

39

Les personnes qui sont habituées aux langages mal classés sont souvent effrayées par la gestion de la mémoire en C ++. Il existe des outils, comme auto_ptret shared_ptrqui gèrent pour vous de nombreuses tâches de gestion de la mémoire. De nombreuses bibliothèques C ++ sont antérieures à ces outils et ont leur propre manière de gérer les tâches de gestion de la mémoire.

Combien de temps passez-vous sur les tâches de gestion de la mémoire?

Je soupçonne que cela dépend fortement de l'ensemble des bibliothèques que vous utilisez, alors indiquez-nous les bibliothèques auxquelles votre réponse s'applique et indiquez si elles le rendent meilleur ou pire.

Sean McMillan
la source
1
Pas tant que ça, vraiment ... Surtout avec C ++ 0x, les références et STL. Vous pouvez même écrire du code sans aucune gestion de mémoire.
Codeur
9
En général: Pas tant que cela si vous êtes expérimenté. Beaucoup si vous êtes novice en C ++ (-> généralement à la recherche de fuites de mémoire / ressources).
MaR
1
Je trouve la vraie question, ces jours-ci, davantage sur la recherche de références obsolètes. Et c'est généralement assez évident à chaque fois, juste ennuyeux que cela n'ait pas été capturé auparavant: p
Matthieu M.
Je sais que c'est vieux, mais la gestion de la mémoire IMO fait partie intégrante d'un bon programmeur. Les abstractions comme les conteneurs STL sont bien, mais l'ignorance de la mémoire va à l'encontre de l'idée même du calcul. On pourrait aussi bien se demander comment on peut éliminer la manipulation algébrique, la logique et les boucles de l'arsenal du programmeur.
imallett
Que diriez-vous "combien de temps est utilisé pour déboguer la gestion de la mémoire qui a mal tourné?" En soi, la gestion de la mémoire est possible et pas si difficile en C ++. Le fait est que l'installation est un métier précis, et il est très sujet à la baise. Lorsque vous vous foutez la gueule, vous ne remarquerez peut-être même pas que vous devez avoir peur de revenir à de vieilles erreurs avec des comportements erratiques accumulés au fil du temps. C'est pourquoi les langages modernes non ramassés (je pense à la rouille) ont transféré beaucoup de responsabilités pour la vérification des erreurs typiques vers le compilateur.
ZJR

Réponses:

54

Le C ++ moderne ne vous préoccupe pas de la gestion de la mémoire tant que vous n’avez pas à le faire, c’est-à-dire jusqu’à ce que vous deviez organiser votre mémoire à la main, principalement à des fins d’optimisation, ou si le contexte vous y oblige (pensez au matériel à contraintes élevées). J'ai écrit des jeux entiers sans manipuler la mémoire brute, seulement pour utiliser des conteneurs qui sont le bon outil pour le travail, comme dans n'importe quelle langue.

Cela dépend donc du projet, mais la plupart du temps, ce n’est pas une gestion de mémoire que vous devez gérer, mais seulement la durée de vie d’un objet. Ce problème est résolu à l'aide de pointeurs intelligents , l'un des outils C ++ idiomatiques résultant de RAII .

Une fois que vous aurez compris RAII , la gestion de la mémoire ne sera plus un problème.

Ensuite, lorsque vous aurez besoin d'accéder à la mémoire brute, vous le ferez dans un code très spécifique, localisé et identifiable, comme dans les implémentations d'objet de pool, pas "partout".

En dehors de ce type de code, vous n'aurez pas besoin de manipuler la mémoire, mais uniquement des objets à vie.

La partie "difficile" consiste à comprendre RAII.

Klaim
la source
10
Absolument vrai. Au cours des 5 dernières années, je n’ai écrit que des "suppressions" lorsque je travaillais avec du code hérité.
drxzcl
3
Je travaille dans un environnement embarqué très limité en taille de pile. Aussi cool que soit RAII, cela ne fonctionne pas bien si l'espace de pile est limité. Nous sommes donc revenus à la microgestion du pointeur.
Bastibe
1
@nikie J'utilise les pointeurs intelligents des bibliothèques dans le code qui manipule leur API, puis j'utilise des pointeurs intelligents standard ou renforcés dans le code spécifique à mon application (si je suis celui qui décide à ce sujet). Si vous pouvez isoler dans certains modules le code de la bibliothèque qui résume leur utilisation dans votre application, vous évitez la pollution des API par les dépendances.
Klaim
12
@Paperflyer: RAII ne prendra pas plus de place que deletemanuellement, à moins que vous n'ayez une implémentation merdique.
DeadMG
2
@Paperflyer: le pointeur intelligent sur le tas prend le même espace; la différence est que le compilateur insère le code de désallocation de ressource sur toutes les sorties d'une fonction. Et comme cela est si largement utilisé, ceci est généralement bien optimisé (par exemple, le pliage de plusieurs sorties de manière impossible - vous ne pouvez pas mettre de code après a return)
MSalters
32

La gestion de la mémoire est utilisée pour effrayer les enfants, mais ce n'est qu'un type de ressource qu'un programmeur doit gérer. Pensez aux descripteurs de fichier, aux connexions réseau et aux autres ressources que vous obtenez du système d'exploitation.

Les langages qui prennent en charge la récupération de place ne ignorent généralement pas seulement l'existence de ces ressources, mais rendent également plus difficile leur gestion correcte en ne fournissant pas de destructeur.

En bref, je dirais que les développeurs C ++ ne consacrent pas beaucoup de temps à la gestion de la mémoire. Comme l' indique la réponse de klaim , une fois que vous maîtrisez RAII, le reste n'est que réflexe.

Dysaster
la source
3
J'aime particulièrement la façon dont HttpWebRequest.GetResponse fuit les manipulations et commence à planter dans les langues du GC. GC est cool, jusqu’à ce qu’il commence à sucer car les ressources fuient encore. msdn.microsoft.com/en-us/library/… Voir "Attention".
Codeur
6
+1 pour afficher la mémoire en tant que ressource. Code hérité ou non, combien de fois devons-nous crier à haute voix: La gestion de la mémoire est une compétence et non une malédiction .
aquaherd
4
@Coder Je ne suis pas sûr de suivre .. GC est nul parce qu'il est possible d'abuser de toute façon des ressources ..? Je pense que C # fait du bon travail en libérant des ressources déterministes en utilisant IDisposable ...
Max
8
@ Max: Parce que si les ordures sont collectées, je m'attends à ne pas m'inquiéter des ressources stupides via l'utilisation d'IDisposables personnalisés. Les ressources ont quitté la portée, ça y est, elles devraient être nettoyées. En réalité, cependant, je dois encore penser et deviner lesquelles vont fuir et lesquelles ne vont pas. Bat toute raison d'utiliser le langage GC en premier lieu.
Codeur
5
@deadalnix Ils ont la finalizeconstruction. Cependant, vous ne savez pas quand il sera appelé. Sera-ce avant de manquer de sockets ou d'objets WebResponse? Vous trouverez de nombreux articles qui vous disent que vous ne devriez pas vous en fier finalize- avec raison.
Dysaster
13

À peu près aucun. Même avec les anciennes technologies telles que COM, vous pouvez écrire des délimiteurs personnalisés pour les pointeurs standard qui les convertiront très rapidement. Par exemple, std::unique_ptrpeut être converti pour contenir de manière unique une référence COM avec cinq lignes d’un deleter personnalisé. Même si vous devez écrire manuellement votre propre gestionnaire de ressources, la prévalence de connaissances telles que SRP et copie-échange vous permet d'écrire relativement facilement une classe de gestion de ressources à utiliser pour toujours.

En réalité, votre compilateur C ++ 11 est partagé, unique et sans propriété. Il vous suffit d'écrire de petits adaptateurs pour les faire fonctionner même avec du code ancien.

DeadMG
la source
1
Quelle compétence en C ++ faut-il avoir pour a) écrire un suppresseur personnalisé b) savoir qu'un suppresseur personnalisé est ce dont vous avez besoin? Je pose la question parce qu'il semble facile de choisir un nouveau langage GC et de se rapprocher du correct sans connaître le tout - est-il facile de réussir également en C ++?
Sean McMillan
1
@SeanMcMillan: Les suppressions personnalisées sont faciles à écrire et à déployer, la ligne COM que j'ai mentionnée compte cinq lignes pour tous les types de COM, et quiconque ayant une formation de base en C ++ moderne devrait les connaître. Vous ne pouvez pas choisir un langage GCed, car surprise, le GC ne collectera pas d'objets COM. Ou des poignées de fichier. Ou mémoire obtenue à partir d'autres systèmes. Ou connexions de base de données. RAII fera toutes ces choses.
DeadMG
2
Par "Choisir un langage GC'd", je veux dire que j’ai sauté entre Java / C # / Ruby / Perl / Javascript / Python et qu’ils ont tous le même style de gestion des ressources - la mémoire est généralement automatique, et tout le reste , vous devez gérer. Il me semble que vous dites que les outils de gestion de C ++ vous permettent de gérer les descripteurs de fichier / les connexions à la base de données / etc. de la même manière que la mémoire, et que c'est relativement simple une fois que vous l'avez appris. Pas une opération du cerveau. Est-ce que je comprends bien?
Sean McMillan
3
@SeanMcMillan: Oui, c'est tout à fait exact, et ce n'est pas complexe.
DeadMG
11

Quand j'étais programmeur C ++ (il y a longtemps), je m'inquiétais longuement du bogue de gestion de la mémoire lorsque j'essayais de le réparer pour en reproduire les bogues .

Avec le modem C ++, la gestion de la mémoire est beaucoup moins un problème, mais pouvez-vous faire confiance à tous les membres d'une grande équipe pour bien faire les choses. Quel est le coût / temps de:

  • Formation (peu de programmeurs maîtrisent bien les problèmes)
  • Revues de code pour résoudre les problèmes de gestion de la mémoire
  • Débogage des problèmes de gestion de la mémoire
  • Gardez toujours à l'esprit qu'un bogue dans une partie de l'application peut être dû à un problème de gestion de la mémoire dans une partie non liée de l'application .

Donc, ce n’est pas seulement le temps passé à « faire », c’est davantage un problème pour les grands projets.

Ian
la source
2
Je pense que certains projets C ++ ont désespéré de réparer certaines de leurs fuites de mémoire en raison d'un code mal écrit. Un mauvais code va arriver, et quand il le fait, cela peut aussi prendre beaucoup de temps à d'autres personnes.
Jeremy
@ Jeremy, j'ai constaté que lorsque je passais du C ++ au C #, il y avait toujours autant de code mal écrit (sinon plus), mais au moins, il était très facile de trouver la partie du programme qui avait un bogue donné.
Ian
1
oui, cela explique en grande partie la plupart des magasins qui ont migré vers Java ou .NET. Le ramassage des ordures atténue les dommages inévitables causés par un code défectueux.
Jeremy
1
Curieusement, nous n'avons pas ces problèmes.
David Thornley
1
@ DavidThornley, je pense que le problème provient en grande partie de l'écriture de code d'interface utilisateur en C ++. Ces jours-ci, la plupart du code C ++ que je vois n'est pas une interface utilisateur
Ian, du
2

J'utilise beaucoup les bibliothèques boost et TR1, et elles rendent la gestion de la mémoire au sens strict (nouveau / supprimer) non problématique. Par ailleurs, l’allocation de mémoire en C ++ n’est pas bon marché et vous devez faire attention au lieu de création de ces pointeurs partagés de fantaisie. Vous finissez souvent par utiliser des espaces de travail ou à travailler avec de la mémoire en pile. En général, je dirais que c'est principalement un problème de conception, pas un problème de mise en œuvre.

quant_dev
la source
2

combien de temps cela prend-il en tant que client? très peu, une fois que vous avez compris. quand un conteneur gère la durée de vie et les références, c'est vraiment très simple. imo, c’est beaucoup plus simple que le comptage manuel des références, et il est pratiquement transparent si vous considérez le conteneur que vous utilisez comme une documentation que le compilateur vous empêche commodément d’effectuer des transferts de propriété non valides dans un système typafe bien conçu.

La plupart du temps que je passe (en tant que client) consiste à contenir des types d'autres API, de sorte qu'ils fonctionnent bien dans le contexte de vos programmes. exemple: ceci est mon conteneur ThirdPartyFont, et il prend en charge ces fonctionnalités, et met en œuvre la destruction de cette façon, et la référence à compter de cette façon, et la copie de cette façon, et ... . Bon nombre de ces concepts doivent être en place et c'est souvent l'endroit logique pour les mettre en place. que vous souhaitiez ou non inclure cela en fonction du temps dépend de votre définition (l'implémentation doit exister lors de l'interfaçage avec ces API, de toute façon, non?).

Après cela, vous devrez prendre en compte la mémoire et la propriété. dans un système de niveau inférieur, c'est bien et nécessaire, mais cela peut prendre du temps et un échafaudage pour mettre en œuvre la façon dont vous devriez déplacer les choses. Je ne vois pas cela comme une douleur puisqu'il s'agit d'une exigence d'un système de niveau inférieur. la propriété, le contrôle et la responsabilité sont évidents.

nous pouvons donc orienter cela vers des apis basés sur c qui utilisent des types opaques: nos conteneurs nous permettent d’abstraire tous les détails d’implémentation de la gestion de la durée de vie et de la copie de ces types opaques, ce qui rend finalement la gestion des ressources très simple et permet de gagner du temps, et réduit les implémentations.

leur utilisation est vraiment très simple - le problème (venant de GC) est que vous devez maintenant prendre en compte la durée de vie de vos ressources. Si vous vous trompez, cela peut prendre beaucoup de temps à résoudre. apprendre et intégrer la gestion explicite de la durée de vie est naturellement complexe en comparaison (pas pour tout le monde) - c'est le véritable obstacle. Une fois que vous maîtrisez les durées de vie et que vous utilisez de bonnes solutions, il est très facile de gérer les durées de vie des ressources. ce n'est pas une partie importante de ma journée (à moins qu'un bug difficile ne se soit introduit).

si vous n'utilisez pas de conteneur (pointeur auto / partagé), vous plaidez pour la douleur.

J'ai implémenté mes propres bibliothèques. il me faut du temps pour mettre en œuvre ces choses, mais la plupart des gens les réutilisent (ce qui est généralement une bonne idée).

Justin
la source
1

Vous voulez dire, par exemple, devoir libérer manuellement de la mémoire, fermer des fichiers, etc.? Si tel est le cas, je dirais le minimum et généralement moins que la plupart des autres langues que j'ai utilisées, en particulier si nous généralisons cela non seulement à la "gestion de la mémoire" mais également à la "gestion des ressources". En ce sens, je pense réellement que le C ++ nécessite moins de gestion manuelle des ressources que, disons, Java ou C #.

Cela est principalement dû aux destructeurs qui automatisent la destruction de la ressource (mémoire ou autre). Généralement, le seul moment où je dois libérer / détruire manuellement une ressource en C ++ est si j'implémente une structure de données au niveau vlow (ce que la plupart des gens n'ont pas besoin de faire) ou à l'aide d'une API C où je passe juste un peu de temps encapsulant la ressource C devant être libérée / détruite / fermée manuellement dans un wrapper C ++ conforme à RAII.

Bien sûr, si un utilisateur demande à fermer une image dans un logiciel de retouche d'image, je dois supprimer l'image d'une collection ou quelque chose du genre. Mais espérons que cela ne compte pas comme gestion de "mémoire" ou de "ressources" d'un type qui importe dans ce contexte, car requis dans toutes les langues si vous souhaitez libérer la mémoire associée à cette image à ce moment-là. Mais encore une fois, tout ce que vous avez à faire est de supprimer l’image de la collection et le destructeur d’image s’occupe du reste.

Entre-temps, si je compare à, par exemple, Java ou C #, les utilisateurs sont souvent obligés de fermer des fichiers manuellement, de déconnecter manuellement des sockets, de définir des références d'objet sur null afin de permettre leur ramassage illégal, etc. gestion des ressources dans ces langues si vous me demandez. En C ++, vous n'avez souvent même pas besoin d' unlockun mutex manuellement, car le casier à mutex le fait automatiquement pour vous lorsque le mutex sort de son périmètre. Par exemple, vous ne devriez jamais avoir à faire des choses comme celle-ci en C ++:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

Il n'est pas nécessaire de faire des choses comme fermer des fichiers manuellement en C ++. Ils se ferment automatiquement dès l'instant où ils sortent de la portée, qu'ils le soient par la suite ou par des chemins d'exécution normaux ou exceptionnels. Une chose similaire pour les ressources liées à la mémoire comme std::vector. Un tel code, comme file.Close()ci-dessus, serait souvent mal vu car, en particulier dans le contexte d'un finallybloc, cela suggère que la ressource locale doit être libérée manuellement, alors que tout l'état d'esprit autour de C ++ est de l'automatiser.

En termes de gestion manuelle de la mémoire, je dirais que le C nécessite le maximum, Java / C # un montant moyen et le C ++ le minimum. Il y a beaucoup de raisons d'être un peu timide en utilisant C ++ car c'est un langage très difficile à maîtriser, mais la gestion de la mémoire ne devrait pas en faire partie. Au contraire, je pense en fait que c'est l'un des langages les plus faciles à utiliser dans cet aspect.

Bien sûr, C ++ vous permet de commencer à allouer manuellement de la mémoire et à appeler operator delete/delete[]pour libérer manuellement de la mémoire. Il vous permet également d’utiliser des fonctions C comme mallocetfree. Mais ce sont des pratiques de codage à l’ancienne qui, à mon avis, sont devenues obsolètes bien avant que les gens n’en donnent le crédit, puisque Stroustrup prônait RAII avant même d’avoir utilisé ce terme très tôt. Donc, je ne pense même pas qu'il soit juste de dire que "le C ++ moderne" automatise la gestion des ressources, car c'était censé être le but recherché depuis le début. Vous ne pouvez pratiquement pas obtenir une sécurité d'exception autrement. Au début des années 90, de nombreux développeurs malavisés ont essayé d'utiliser le C ++ comme un C avec des objets, ignorant souvent complètement la gestion des exceptions, et il n'a jamais été supposé être utilisé de cette façon. Si vous utilisez le C ++ de la manière dont il était pratiquement destiné à être utilisé, la gestion de la mémoire est totalement automatisée et ne nécessite généralement pas une gestion manuelle (ou devrait être traitée) du tout.


la source
1
Java moderne a "essayer avec des ressources" qui supprime tout ce code en désordre dans le bloc finally. Il est rarement nécessaire d'avoir un blocage final. On dirait que les concepteurs ont copié le concept RAII.
kiwiron
0

Dépend des principaux responsables techniques de l’équipe. Dans certaines entreprises (y compris la mienne), il n’existe pas de concept appelé smart poiner. C'est considéré comme compliqué. Donc, les gens mettent simplement des suppressions partout et il y a un lecteur pour la réparation des fuites de mémoire tous les 2 mois. La nouvelle vague de déclarations de suppression arrive partout. Cela dépend donc de l’entreprise et du type de personnes qui y travaillent.

Jagannath
la source
1
Y a-t-il quelque chose dans votre environnement qui vous empêche d'utiliser auto_ptret d'amis?
Sean McMillan
2
on dirait que votre entreprise n’écrit pas de code C ++, vous écrivez C.
gbjbaanb