La fragmentación de la memoria es el mismo concepto que la fragmentación del disco: se refiere al espacio que se desperdicia porque las áreas en uso no están lo suficientemente juntas.
Supongamos, por ejemplo, un juguete simple que tiene diez bytes de memoria:
| | | | | | | | | | |
0 1 2 3 4 5 6 7 8 9
Ahora asignemos tres bloques de tres bytes, nombre A, B y C:
| A | A | A | B | B | B | C | C | C | |
0 1 2 3 4 5 6 7 8 9
Ahora desasigne el bloque B:
| A | A | A | | | | C | C | C | |
0 1 2 3 4 5 6 7 8 9
¿Qué sucede si intentamos asignar un bloque D de cuatro bytes? Bueno, tenemos cuatro bytes de memoria libre, pero no tenemos cuatro bytes contiguos de memoria libre, ¡así que no podemos asignar D! Este es un uso ineficiente de la memoria, porque deberíamos haber podido almacenar D, pero no pudimos. Y no podemos mover C para hacer espacio, porque muy probablemente algunas variables en nuestro programa apuntan a C, y no podemos encontrar y cambiar automáticamente todos estos valores.
¿Cómo sabes que es un problema? Bueno, la señal más importante es que el tamaño de la memoria virtual de su programa es considerablemente mayor que la cantidad de memoria que está utilizando realmente. En un ejemplo del mundo real, tendría muchos más de diez bytes de memoria, por lo que D solo se asignaría comenzando un byte 9, y los bytes 3-5 permanecerían sin usar a menos que luego asigne algo de tres bytes de longitud o menos.
En este ejemplo, 3 bytes no es mucho desperdicio, pero considere un caso más patológico donde dos asignaciones de un par de bytes están, por ejemplo, separadas por diez megabytes en memoria, y necesita asignar un bloque de 10 megabytes de tamaño + 1 byte. Tienes que ir a pedirle al sistema operativo más de diez megabytes de memoria virtual para hacer eso, a pesar de que solo te falta un byte de tener suficiente espacio.
¿Cómo lo evitas? Los peores casos tienden a surgir cuando crea y destruye objetos pequeños con frecuencia, ya que eso tiende a producir un efecto de "queso suizo" con muchos objetos pequeños separados por muchos agujeros pequeños, lo que hace imposible asignar objetos más grandes en esos agujeros. Cuando sepa que va a hacer esto, una estrategia efectiva es preasignar un bloque grande de memoria como un grupo para sus objetos pequeños, y luego administrar manualmente la creación de los objetos pequeños dentro de ese bloque, en lugar de permitir el asignador predeterminado lo maneja.
En general, cuantas menos asignaciones haga, menos probable es que la memoria se fragmente. Sin embargo, STL trata esto con bastante eficacia. Si tiene una cadena que está utilizando la totalidad de su asignación actual y le agrega un carácter, no simplemente se reasigna a su longitud actual más uno, duplica su longitud. Esta es una variación de la estrategia de "grupo de pequeñas asignaciones frecuentes". La cadena está tomando una gran cantidad de memoria para que pueda lidiar eficientemente con pequeños aumentos repetidos de tamaño sin realizar pequeñas reasignaciones repetidas. De hecho, todos los contenedores STL hacen este tipo de cosas, por lo que generalmente no tendrá que preocuparse demasiado por la fragmentación causada por la reasignación automática de los contenedores STL.
Aunque, por supuesto, los contenedores STL no agrupan la memoria entre ellos, por lo que si va a crear muchos contenedores pequeños (en lugar de algunos contenedores que cambian de tamaño con frecuencia), es posible que deba preocuparse por evitar la fragmentación de la misma manera que sería para cualquier objeto pequeño creado con frecuencia, STL o no.