Hay dos técnicas de asignación de memoria ampliamente utilizadas: asignación automática y asignación dinámica. Comúnmente, hay una región de memoria correspondiente para cada uno: la pila y el montón.
Apilar
La pila siempre asigna memoria de forma secuencial. Puede hacerlo porque requiere que libere la memoria en el orden inverso (Primero en entrar, Último en salir: FILO). Esta es la técnica de asignación de memoria para variables locales en muchos lenguajes de programación. Es muy, muy rápido porque requiere una contabilidad mínima y la siguiente dirección para asignar está implícita.
En C ++, esto se llama almacenamiento automático porque el almacenamiento se reclama automáticamente al final del alcance. Tan pronto como se completa la ejecución del bloque de código actual (delimitado usando {}
), la memoria para todas las variables en ese bloque se recopila automáticamente. Este es también el momento en que se invocan los destructores para limpiar los recursos.
Montón
El montón permite un modo de asignación de memoria más flexible. La contabilidad es más compleja y la asignación es más lenta. Como no hay un punto de liberación implícito, debe liberar la memoria manualmente, usando delete
o delete[]
( free
en C). Sin embargo, la ausencia de un punto de liberación implícito es la clave para la flexibilidad del montón.
Razones para usar la asignación dinámica
Incluso si el uso del montón es más lento y potencialmente conduce a pérdidas de memoria o fragmentación de la memoria, existen casos de uso perfectamente buenos para la asignación dinámica, ya que es menos limitado.
Dos razones clave para usar la asignación dinámica:
No sabe cuánta memoria necesita en tiempo de compilación. Por ejemplo, cuando lee un archivo de texto en una cadena, generalmente no sabe qué tamaño tiene el archivo, por lo que no puede decidir cuánta memoria asignar hasta que ejecute el programa.
Desea asignar memoria que persistirá después de abandonar el bloque actual. Por ejemplo, es posible que desee escribir una función string readfile(string path)
que devuelva el contenido de un archivo. En este caso, incluso si la pila pudiera contener todo el contenido del archivo, no podría regresar de una función y mantener el bloque de memoria asignado.
Por qué la asignación dinámica a menudo es innecesaria
En C ++ hay una construcción ordenada llamada destructor . Este mecanismo le permite administrar recursos alineando la vida útil del recurso con la vida útil de una variable. Esta técnica se llama RAII y es el punto distintivo de C ++. "Envuelve" recursos en objetos. std::string
Es un ejemplo perfecto. Este fragmento:
int main ( int argc, char* argv[] )
{
std::string program(argv[0]);
}
en realidad asigna una cantidad variable de memoria. El std::string
objeto asigna memoria usando el montón y lo libera en su destructor. En este caso, no necesitaba administrar manualmente ningún recurso y aún así obtuvo los beneficios de la asignación dinámica de memoria.
En particular, implica que en este fragmento:
int main ( int argc, char* argv[] )
{
std::string * program = new std::string(argv[0]); // Bad!
delete program;
}
Hay asignación de memoria dinámica innecesaria. El programa requiere más tipeo (!) E introduce el riesgo de olvidarse de desasignar la memoria. Lo hace sin ningún beneficio aparente.
¿Por qué debería usar el almacenamiento automático tan a menudo como sea posible?
Básicamente, el último párrafo lo resume. El uso del almacenamiento automático con la mayor frecuencia posible hace que sus programas:
- más rápido de escribir;
- más rápido cuando corres;
- menos propenso a pérdidas de memoria / recursos.
Puntos extra
En la pregunta referenciada, hay preocupaciones adicionales. En particular, la siguiente clase:
class Line {
public:
Line();
~Line();
std::string* mString;
};
Line::Line() {
mString = new std::string("foo_bar");
}
Line::~Line() {
delete mString;
}
En realidad, es mucho más riesgoso de usar que el siguiente:
class Line {
public:
Line();
std::string mString;
};
Line::Line() {
mString = "foo_bar";
// note: there is a cleaner way to write this.
}
La razón es que std::string
define correctamente un constructor de copia. Considere el siguiente programa:
int main ()
{
Line l1;
Line l2 = l1;
}
Al usar la versión original, este programa probablemente se bloqueará, ya que se usa delete
en la misma cadena dos veces. Usando la versión modificada, cada Line
instancia será propietaria de su propia instancia de cadena , cada una con su propia memoria y ambas serán lanzadas al final del programa.
Otras notas
El uso extensivo de RAII se considera una mejor práctica en C ++ debido a todas las razones anteriores. Sin embargo, hay un beneficio adicional que no es inmediatamente obvio. Básicamente, es mejor que la suma de sus partes. Todo el mecanismo compone . Se escala.
Si usa la Line
clase como un bloque de construcción:
class Table
{
Line borders[4];
};
Entonces
int main ()
{
Table table;
}
asigna cuatro std::string
instancias, cuatro Line
instancias, una Table
instancia y todos los contenidos de la cadena y todo se libera automáticamente .