2011-12-14 14 views
6

No entiendo el objetivo de la memoria dinámicamente asignada y espero que ustedes puedan aclarar las cosas.C++ memoria asignada dinámicamente

En primer lugar, cada vez que asignamos memoria, simplemente obtenemos un puntero a esa memoria.

int * dynInt = new int; 

Entonces, ¿cuál es la diferencia entre hacer lo que hice arriba y:

int someInt; 
int* dynInt = &someInt; 

Según entiendo, en ambos casos se asigna memoria para un int, y obtenemos un puntero a la memoria.

¿Cuál es la diferencia entre los dos? ¿Cuándo se prefiere un método al otro?

Más aún ¿por qué necesito para liberar memoria con

delete dynInt; 

en el primer caso, pero no en el segundo caso.

Mis conjeturas son:

  1. Cuando la asignación dinámica de memoria para un objeto, el objeto no quede inicializado mientras que si haces algo como en el segundo caso, el objeto Get de inicializado. Si esta es la única diferencia, ¿hay alguna motivación detrás de esto aparte del hecho de que la asignación dinámica de memoria es más rápida.

  2. La razón por la que no necesitamos usar eliminar para el segundo caso se debe a que el hecho de que el objeto se haya inicializado crea algún tipo de rutina de destrucción automática.

Esas son solo conjeturas me encantaría que alguien me corrigiera y me aclarara las cosas.

+3

Si no está seguro, siga la regla simple: "Nunca use punteros, nunca use' new'. " Una vez que comprenda la necesidad de la vida útil de los objetos administrados manualmente, sabrá cuándo romper esta regla. –

+1

Uhm, le recomiendo encarecidamente que abra un libro sobre C++. El alcance variable, la gestión dinámica de la memoria se suelen analizar en el capítulo 4 o 5 de cualquier libro para principiantes en C++. @KerrekSB No diría eso. Especialmente en este caso, la incertidumbre no proviene de la ambigüedad, sino de la falta de conocimiento. Si no está seguro, asegúrese de leer lo suficiente para estar seguro. – paul23

+0

La asignación dinámica de memoria es usualmente _slower_, y la inicialización no tiene nada que ver con nada de esto. –

Respuesta

15

La diferencia está en duración de almacenamiento.

  • objetos con duración del almacenamiento automático son sus objetos "normales" que van automáticamente fuera de alcance al final del bloque en el que están definidas.

    Creen como int someInt;

    Puede haber oído hablar de ellos como "objetos de pila", aunque I objeto a esta terminología.

  • Objetos con duración de almacenamiento dinámico tienen algo así como una vida útil "manual"; tiene que destruirlos usted mismo con delete, y crearlos con la palabra clave new.

    Es posible que haya oído hablar de ellos como "objetos de montón", aunque me opongo a esto también.

El uso de punteros no es estrictamente relevante para ninguno de ellos. Puede tener un puntero a un objeto de duración de almacenamiento automático (su segundo ejemplo) y puede tener un puntero a un objeto de duración de almacenamiento dinámico (su primer ejemplo).

Pero es raro que usted desee un puntero a un objeto automático, debido a que:

  1. que no tiene uno "por defecto";
  2. el objeto no va a durar mucho tiempo, por lo que no hay mucho que pueda hacer con dicho puntero.

Por el contrario, a los objetos dinámicos a menudo se accede a través de punteros, simplemente porque la sintaxis se acerca a forzarlo. new devuelve un puntero para que lo use, debe pasar un puntero a delete y (aparte de usar referencias) no hay otra manera de acceder al objeto. Vive "afuera" en una nube de dinamismo que es no sentado en el ámbito local.

Debido a esto, el uso de punteros a veces se confunde con el uso de almacenamiento dinámico, pero de hecho el primero no está relacionado causalmente con este último.

+0

Los punteros a las variables automáticas pueden ser útiles cuando se invoca una función que se supone que debe mutar sus argumentos (y quizás use su valor de retorno para indicar el éxito). –

+3

@ TamásSzelei: En este caso, debe usar una referencia. –

+0

@ TamásSzelei: Bueno, cierto. Sin embargo, ese es un uso muy localizado. Supongo que estoy hablando de obtener un puntero y almacenarlo, no usar un temporal en una sola expresión –

13

un objeto creado de esta manera:

int foo; 

tiene duración del almacenamiento automático - el objeto vive hasta que la variable foo sale del ámbito. Esto significa que en su primer ejemplo, dynInt será un puntero no válido una vez que someInt quede fuera del alcance (por ejemplo, al final de una función).

un objeto creado de esta manera:

int foo* = new int; 

Ha duración de almacenamiento dinámico - el objeto vive hasta que llame explícitamente delete en él.

La inicialización de los objetos es un concepto ortogonal; no está directamente relacionado con el tipo de almacenamiento-duración que usa. Consulte here para obtener más información sobre la inicialización.

+0

Gracias por una respuesta tan buena y clara. Gracias de nuevo. – vinay

+2

Para ser pedante, realmente no sabe nada sobre la * memoria *, porque eso depende de la implementación de su función de asignación ':: operator new()'. Es el * tiempo de vida * del objeto que es dinámico, es decir, manual: el * objeto * vive hasta que 'lo elimine '. C++ separa la asignación de memoria y la construcción de objetos. –

+1

@KerrekSB: Siempre me da un poco de miedo cuando escribo esa respuesta, porque estoy seguro de que obtendré algo mal :) –

1

¿Qué sucede si se supone que su programa permite al usuario almacenar cualquier cantidad de números enteros? A continuación, deberá decidir durante el tiempo de ejecución, en función de la entrada del usuario, cuántos Ints asignar, por lo que esto debe hacerse de forma dinámica.

+0

A menos que use [alloca] (http://stackoverflow.com/q/1018853/533120);) –

4

Para un entero simple, solo tiene sentido si necesita mantener el valor después de, por ejemplo, regresar de una función. Si declaraste someInt como dijiste, se habría invalidado tan pronto como saliera del alcance.

Sin embargo, en general hay un mayor uso para la asignación dinámica. Hay muchas cosas que tu programa no sabe antes de la asignación y depende de la entrada. Por ejemplo, su programa necesita leer un archivo de imagen. ¿Qué tan grande es ese archivo de imagen? Podríamos decir que la almacenamos en una serie como esta:

unsigned char data[1000000]; 

Pero eso sólo funcionaría si el tamaño de la imagen era menor o igual a 1000000 bytes, y también sería un desperdicio de imágenes más pequeñas.En cambio, podemos asignar dinámicamente la memoria:

unsigned char* data = new unsigned char[file_size]; 

Aquí, file_size se determina en tiempo de ejecución. No podría decir este valor al momento de la compilación.

+1

¿No podría simplemente hacer: "unsigned char data [file_size];"? – user1066113

+0

No, no podrías. Bueno, algunos compiladores de C podrían aceptarlo, pero eso sigue siendo la asignación dinámica y no el estándar C (89) ni C++; ver [VLA] (http://en.wikipedia.org/wiki/Variable-length_array). ISO C99 admite VLA. –

2

Cuando está utilizando new en C++ la memoria se asigna a través de malloc que llama a la llamada al sistema sbrk (o similar). Por lo tanto, nadie, excepto el sistema operativo, tiene conocimiento sobre el tamaño solicitado. Por lo tanto, tendrá que usar delete (que llama al free, que va a sbrk nuevamente) para devolver la memoria al sistema. De lo contrario, obtendrá una pérdida de memoria.

Ahora, cuando se trata de su segundo caso, el compilador tiene conocimiento sobre el tamaño de la memoria asignada. Es decir, en su caso, el tamaño de uno int. Establecer un puntero a la dirección de este int no cambia nada en el conocimiento de la memoria necesaria. O con otras palabras: el compilador puede ocuparse de liberar la memoria. En el primer caso con new esto no es posible.

Además de eso: new respectivamente malloc no es necesario asignar exactamente el tamaño requerido, lo que hace las cosas un poco más complicadas.

Editar

Dos más frases comunes: El primer caso también se conoce como asignación de memoria estática (hecho por el compilador), el segundo caso se refiere a la asignación de memoria dinámica (hecho por el sistema de tiempo de ejecución).

2

Leer más sobre dynamic memory allocation y también garbage collection

que realmente necesita para leer un buen C o C++ libro de programación.

Explicar en detalle llevaría mucho tiempo.

El montón es la memoria dentro de la cual ocurre la asignación dinámica (con new en C++ o en C). Hay system calls involucrados en el crecimiento y la reducción del montón. En Linux, son mmap & munmap (utilizados para implementar malloc y new, etc.).

Puede llamar muchas veces a la primitiva de asignación. ¡De modo que podría poner int *p = new int; dentro de un bucle y obtener una nueva ubicación cada vez que realice un bucle!

No se olvide de liberar memoria (con delete en C++ o en C). De lo contrario, obtendrá un memory leak -un tipo de error malicioso-. En Linux, valgrind ayuda a atraparlos.

1

En pocas palabras, la duración del objeto asignado dinámicamente es controlado por usted y no por el idioma. Esto le permite dejarlo vivir el tiempo que sea necesario (a diferencia del final del alcance), posiblemente determinado por una condición que solo puede calcularse en tiempo de ejecución.

Además, la memoria dinámica suele ser mucho más "escalable", es decir, puede asignar más objetos y/o objetos más grandes en comparación con la asignación basada en la pila.

La asignación esencialmente "marca" una pieza de memoria para que no se pueda asignar ningún otro objeto en el mismo espacio. La desasignación "desmarca" esa parte de la memoria para que pueda ser reutilizada para asignaciones posteriores. Si no puede desasignar la memoria después de que ya no la necesite, obtendrá una condición conocida como "pérdida de memoria": su programa ocupará una memoria que ya no necesita, lo que puede ocasionar una falla en la asignación de nueva memoria (debido a la falta de memoria), y en general, poner una tensión innecesaria en el sistema.

4
  1. Su programa obtiene una porción inicial de memoria al inicio. Esta memoria se llama pila . La cantidad suele ser de alrededor de 2 MB en estos días.

  2. Su programa puede solicitar al sistema operativo para memoria adicional. Esto se llama asignación de memoria dinámica. Esto asigna memoria en tienda gratuita (terminología C++) o montón (terminología C). Puede solicitar tanta memoria como el sistema esté dispuesto a dar (múltiples gigabytes).

La sintaxis para asignar una variable en la pila se ve así:

{ 
    int a; // allocate on the stack 
} // automatic cleanup on scope exit 

La sintaxis para asignar una variable usando la memoria de la tienda libre es el siguiente:

int * a = new int; // ask OS memory for storing an int 
delete a; // user is responsible for deleting the object 


Para responder a sus preguntas:

Cuándo se prefiere un método al otro.

  1. pila Generalmente se prefiere la asignación.
  2. Se requiere asignación dinámica cuando necesita almacenar un objeto polimórfico utilizando su tipo de base.
  3. Use siempre puntero inteligente para automatizar la eliminación:
    • C++ 03: boost::scoped_ptr, boost::shared_ptr o std::auto_ptr.
    • C++ 11: std::unique_ptr o std::shared_ptr.

Por ejemplo:

// stack allocation (safe) 
Circle c; 

// heap allocation (unsafe) 
Shape * shape = new Circle; 
delete shape; 

// heap allocation with smart pointers (safe) 
std::unique_ptr<Shape> shape(new Circle); 

Más aún ¿por qué necesito para liberar memoria en el primer caso, pero no en el segundo caso.

Como mencioné anteriormente, las variables asignadas de la pila se desasignan automáticamente en la salida del alcance. Tenga en cuenta que no tiene permitido eliminar la memoria de la pila. Si lo hace, inevitablemente bloqueará su aplicación.

+0

Esta respuesta es mucho mejor que las otras porque realmente explica qué asignación de memoria usar en qué situación –

Cuestiones relacionadas