La fragmentation de la mémoire est le même concept que la fragmentation du disque: elle se réfère au gaspillage d'espace car les zones utilisées ne sont pas suffisamment rapprochées.
Supposons pour un exemple de jouet simple que vous disposez de dix octets de mémoire:
| | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
Allouons maintenant trois blocs de trois octets, nommés A, B et C:
| A | A | A | B | B | B | C | C | C | |
0 1 2 3 4 5 6 7 8 9
Désallouez maintenant le bloc B:
| A | A | A | | | | C | C | C | |
0 1 2 3 4 5 6 7 8 9
Maintenant, que se passe-t-il si nous essayons d'allouer un bloc de quatre octets D? Eh bien, nous avons quatre octets de mémoire libres, mais nous n'avons pas quatre octets contigus de mémoire libres, donc nous ne pouvons pas allouer D! Il s'agit d'une utilisation inefficace de la mémoire, car nous aurions dû pouvoir stocker D, mais nous n'avons pas pu. Et nous ne pouvons pas déplacer C pour faire de la place, car très probablement certaines variables de notre programme pointent vers C, et nous ne pouvons pas trouver et modifier automatiquement toutes ces valeurs.
Comment savez-vous que c'est un problème? Eh bien, le plus grand signe est que la taille de la mémoire virtuelle de votre programme est considérablement plus grande que la quantité de mémoire que vous utilisez réellement. Dans un exemple concret, vous auriez beaucoup plus de dix octets de mémoire, donc D serait simplement alloué à partir d'un octet 9, et les octets 3-5 resteraient inutilisés sauf si vous allouiez plus tard quelque chose de trois octets de long ou plus petit.
Dans cet exemple, 3 octets ne sont pas beaucoup à perdre, mais considérons un cas plus pathologique où deux allocations d'un couple d'octets sont, par exemple, dix mégaoctets en mémoire, et vous devez allouer un bloc de taille 10 mégaoctets + 1 octet. Vous devez aller demander au système d'exploitation plus de dix mégaoctets de mémoire virtuelle pour le faire, même si vous n'êtes qu'un octet de moins d'avoir déjà suffisamment d'espace.
Comment l'empêchez-vous? Les pires cas ont tendance à se produire lorsque vous créez et détruisez fréquemment de petits objets, car cela a tendance à produire un effet de "fromage suisse" avec de nombreux petits objets séparés par de nombreux petits trous, ce qui rend impossible d'allouer des objets plus gros dans ces trous. Lorsque vous savez que vous allez le faire, une stratégie efficace consiste à pré-allouer un grand bloc de mémoire en tant que pool pour vos petits objets, puis à gérer manuellement la création des petits objets dans ce bloc, plutôt que de laisser l'allocateur par défaut le gère.
En général, moins vous faites d'allocations, moins la mémoire est susceptible d'être fragmentée. Cependant, STL gère cela de manière assez efficace. Si vous avez une chaîne qui utilise l'intégralité de son allocation actuelle et que vous y ajoutez un caractère, elle ne se réalloue pas simplement à sa longueur actuelle plus un, elle double sa longueur. Il s'agit d'une variante de la stratégie du «pool pour les petites allocations fréquentes». La chaîne saisit une grande partie de la mémoire afin de pouvoir gérer efficacement les petites augmentations de taille répétées sans effectuer de petites réallocations répétées. En fait, tous les conteneurs STL font ce genre de chose, donc généralement vous n'aurez pas à vous soucier trop de la fragmentation causée par la réallocation automatique des conteneurs STL.
Bien que, bien sûr, les conteneurs STL ne regroupent pas la mémoire entre eux, donc si vous allez créer de nombreux petits conteneurs (plutôt que quelques conteneurs qui sont redimensionnés fréquemment), vous devrez peut-être vous préoccuper de prévenir la fragmentation de la même manière que vous serait pour tous les petits objets fréquemment créés, STL ou non.