Il existe deux techniques d'allocation de mémoire largement utilisées: l'allocation automatique et l'allocation dynamique. Généralement, il existe une région de mémoire correspondante pour chacun: la pile et le tas.
Empiler
La pile alloue toujours la mémoire de manière séquentielle. Il peut le faire car il vous oblige à libérer la mémoire dans l'ordre inverse (First-In, Last-Out: FILO). Il s'agit de la technique d'allocation de mémoire pour les variables locales dans de nombreux langages de programmation. Il est très, très rapide car il nécessite une comptabilité minimale et la prochaine adresse à allouer est implicite.
En C ++, cela s'appelle le stockage automatique car le stockage est revendiqué automatiquement à la fin de la portée. Dès que l'exécution du bloc de code actuel (délimité par {}
) est terminée, la mémoire de toutes les variables de ce bloc est automatiquement collectée. C'est également le moment où les destructeurs sont appelés pour nettoyer les ressources.
Tas
Le tas permet un mode d'allocation de mémoire plus flexible. La comptabilité est plus complexe et l'allocation est plus lente. Puisqu'il n'y a pas de point de libération implicite, vous devez libérer la mémoire manuellement, en utilisant delete
ou delete[]
( free
en C). Cependant, l'absence d'un point de sortie implicite est la clé de la flexibilité du tas.
Raisons d'utiliser l'allocation dynamique
Même si l'utilisation du segment de mémoire est plus lente et peut entraîner des fuites de mémoire ou une fragmentation de la mémoire, il existe des cas d'utilisation parfaits pour l'allocation dynamique, car elle est moins limitée.
Deux raisons principales d'utiliser l'allocation dynamique:
Vous ne savez pas de combien de mémoire vous avez besoin au moment de la compilation. Par exemple, lorsque vous lisez un fichier texte dans une chaîne, vous ne savez généralement pas quelle est la taille du fichier, vous ne pouvez donc pas décider de la quantité de mémoire à allouer avant d'exécuter le programme.
Vous souhaitez allouer de la mémoire qui persistera après avoir quitté le bloc actuel. Par exemple, vous souhaiterez peut-être écrire une fonction string readfile(string path)
qui renvoie le contenu d'un fichier. Dans ce cas, même si la pile pouvait contenir tout le contenu du fichier, vous ne pouviez pas revenir d'une fonction et conserver le bloc de mémoire alloué.
Pourquoi l'allocation dynamique est souvent inutile
En C ++, il y a une construction soignée appelée destructeur . Ce mécanisme vous permet de gérer les ressources en alignant la durée de vie de la ressource avec la durée de vie d'une variable. Cette technique est appelée RAII et est le point distinctif du C ++. Il "enveloppe" les ressources en objets. std::string
est un parfait exemple. Cet extrait:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
alloue en fait une quantité variable de mémoire. L' std::string
objet alloue de la mémoire à l'aide du tas et la libère dans son destructeur. Dans ce cas, vous n'avez pas eu besoin de gérer manuellement les ressources et bénéficiez quand même des avantages de l'allocation dynamique de mémoire.
En particulier, cela implique que dans cet extrait:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
il y a une allocation de mémoire dynamique inutile. Le programme nécessite plus de frappe (!) Et présente le risque d'oublier de désallouer la mémoire. Il le fait sans aucun avantage apparent.
Pourquoi utiliser le stockage automatique aussi souvent que possible
Fondamentalement, le dernier paragraphe le résume. Utiliser le stockage automatique aussi souvent que possible rend vos programmes:
- plus rapide à taper;
- plus rapide lors de l'exécution;
- moins sujettes aux fuites de mémoire / ressources.
Points bonus
Dans la question référencée, il existe des préoccupations supplémentaires. En particulier, la classe suivante:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
Est en fait beaucoup plus risqué à utiliser que le suivant:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
La raison en est que std::string
définit correctement un constructeur de copie. Considérez le programme suivant:
int main ()
{
Line l1;
Line l2 = l1;
}
En utilisant la version d'origine, ce programme se bloquera probablement, car il utilise delete
deux fois la même chaîne. En utilisant la version modifiée, chaque Line
instance possédera sa propre instance de chaîne , chacune avec sa propre mémoire et les deux seront publiées à la fin du programme.
Autres notes
L'utilisation intensive de RAII est considérée comme une meilleure pratique en C ++ pour toutes les raisons ci-dessus. Cependant, il existe un avantage supplémentaire qui n'est pas immédiatement évident. Fondamentalement, c'est mieux que la somme de ses parties. L'ensemble du mécanisme compose . Il évolue.
Si vous utilisez la Line
classe comme bloc de construction:
class Table
{
Line borders[4];
};
alors
int main ()
{
Table table;
}
alloue quatre std::string
instances, quatre Line
instances, une Table
instance et tout le contenu de la chaîne et tout est libéré automatiquement .