2009-03-01 14 views
120

He estado programando por un tiempo pero ha sido principalmente Java y C#. Nunca tuve que administrar la memoria por mi cuenta. Recientemente comencé a programar en C++ y estoy un poco confundido sobre cuándo debo almacenar cosas en la pila y cuándo almacenarlas en el montón.Uso adecuado de pila y pila en C++?

Mi comprensión es que las variables a las que se accede con mucha frecuencia se deben almacenar en la pila y los objetos, las variables raramente utilizadas y las estructuras de datos grandes deben almacenarse todas en el montón. ¿Es correcto o soy incorrecto?

+0

posible duplicado de [¿Cuándo es mejor usar la pila en lugar del montón y viceversa?] (Http://stackoverflow.com/questions/102009/when-is-it-best-to-use -the-stack-instead-of-the-heap-and-vice-versa) –

Respuesta

238

No, la diferencia entre la pila y el montón no es el rendimiento. Su vida útil: cualquier variable local dentro de una función (todo lo que no sea malloc() o nuevo) vive en la pila. Se va cuando regresas de la función. Si desea que algo viva más tiempo que la función que lo declaró, debe asignarlo en el montón.

class Thingy; 

Thingy* foo() 
{ 
    int a; // this int lives on the stack 
    Thingy B; // this thingy lives on the stack and will be deleted when we return from foo 
    Thingy *pointerToB = &B; // this points to an address on the stack 
    Thingy *pointerToC = new Thingy(); // this makes a Thingy on the heap. 
            // pointerToC contains its address. 

    // this is safe: C lives on the heap and outlives foo(). 
    // Whoever you pass this to must remember to delete it! 
    return pointerToC; 

    // this is NOT SAFE: B lives on the stack and will be deleted when foo() returns. 
    // whoever uses this returned pointer will probably cause a crash! 
    return pointerToB; 
} 

Para una comprensión más clara de lo que es la pila, por lo que vienen desde el otro extremo - en lugar de tratar de entender lo que hace la pila en términos de un lenguaje de alto nivel, mirar hacia arriba "pila de llamadas" y "convención de llamadas" y vea qué hace realmente la máquina cuando llama a una función. La memoria de la computadora es solo una serie de direcciones; "montón" y "pila" son invenciones del compilador.

+6

Sería seguro agregar que la información de tamaño variable generalmente va en el montón. Las únicas excepciones que conozco son VLA en C99 (que tiene soporte limitado) y la función alloca() que a menudo es mal interpretada incluso por los programadores C. –

+10

Buena explicación, aunque en un escenario de subprocesos múltiples con asignaciones frecuentes y/o desasignaciones, el montón * es * un punto de discordia, lo que afecta el rendimiento. Aún así, Scope es casi siempre el factor decisivo. – peterchen

+17

Claro, y new/malloc() es en sí mismo una operación lenta, y es más probable que stack esté en dcache que una línea de montón arbitraria. Estas son consideraciones reales, pero generalmente secundarias a la cuestión de la vida útil. – Crashworks

1

La opción de asignar en el montón o en la pila está hecha para usted, dependiendo de cómo se asigne su variable. Si asigna algo dinámicamente, utilizando una llamada "nueva", está asignando desde el montón. Si asigna algo como una variable global, o como un parámetro en una función, se asigna en la pila.

+3

Sospecho que estaba preguntando cuándo poner las cosas en el montón, no cómo. –

6

También almacenaría un elemento en el montón si necesita ser utilizado fuera del alcance de la función en la que se creó. Una expresión idiomática utilizada con los objetos de la pila se denomina RAII: esto implica utilizar el objeto basado en la pila como un contenedor para un recurso, cuando el objeto se destruye, el recurso se limpia. Los objetos basados ​​en pila son más fáciles de seguir cuando podría estar lanzando excepciones: no necesita preocuparse por eliminar un objeto basado en el montón en un manejador de excepciones. Esta es la razón por la que los punteros sin formato no se usan normalmente en C++ moderno, se usaría un puntero inteligente que puede ser un contenedor basado en pila para un puntero sin formato a un objeto basado en el montón.

5

Para agregar a las otras respuestas, también puede tratarse del rendimiento, al menos un poco. No es que deba preocuparse por esto a menos que sea relevante para usted, pero:

La asignación en el montón requiere buscar un bloque de memoria, que no es una operación de tiempo constante (y requiere algunos ciclos y gastos generales). Esto puede ser más lento a medida que la memoria se fragmenta o se acerca al 100% de su espacio de direcciones. Por otro lado, las asignaciones de pila son de tiempo constante, básicamente operaciones "libres".

Otra cosa a considerar (una vez más, realmente solo es importante si se convierte en un problema) es que normalmente el tamaño de la pila es fijo, y puede ser mucho menor que el tamaño del montón. Entonces, si está asignando objetos grandes o muchos objetos pequeños, probablemente quiera usar el montón; si se queda sin espacio en la pila, el tiempo de ejecución arrojará la excepción titular del sitio. No suele ser un gran problema, pero hay otra cosa que considerar.

+0

Tanto el montón como la pila son memoria virtual paginada. El tiempo de búsqueda del montón es sorprendentemente rápido en comparación con lo que se necesita para mapear en la memoria nueva. En Linux de 32 bits, puedo poner> 2gig en mi pila. En Macs, creo que la pila está limitada a 65 Meg. –

39

diría:

tienda en la pila, si se puede.

Guárdelo en el montón, si es NECESARIO.

Por lo tanto, prefiera la pila al montón.Algunas de las posibles razones por las que no se puede almacenar algo en la pila son:

  • Es demasiado grande - en programas multihilo en SO de 32 bits, la pila tiene un pequeño y fijo (en el momento de hilo de creación de por lo menos) tamaño (por lo general, solo unos pocos megas. Esto es para poder crear muchos subprocesos sin agotar el espacio de direcciones. Para los programas de 64 bits o los programas de subproceso único (Linux de todos modos), esto no es un problema importante. , los programas de un solo subproceso suelen utilizar acumulaciones dinámicas que pueden seguir creciendo hasta llegar a la parte superior del montón.
  • Debe acceder a él fuera del alcance del marco de pila original: esta es realmente la razón principal.

Es posible, con compiladores razonables, asignar objetos de tamaño no fijo en el montón (generalmente matrices cuyo tamaño no se conoce en tiempo de compilación).

+0

Cualquier cosa más que un par de KB generalmente es mejor poner en el montón. No sé detalles, pero no recuerdo haber trabajado nunca con una pila que era "unos pocos megas". –

+2

Eso es algo con lo que no me interesaría un usuario al principio. Para el usuario, los vectores y las listas parecen estar asignados en la pila, incluso si STL almacena los contenidos en el montón. La pregunta parecía estar más en la línea de decidir cuándo llamar explícitamente nuevo/eliminar. –

+1

Dan: He puesto 2 gigas (Sí, G como en GIGS) en la pila en 32 bits de Linux. Los límites de la pila dependen del sistema operativo. –

0

En mi opinión hay dos factores decisivos

1) Scope of variable 
2) Performance. 

Yo prefiero usar la pila en la mayoría de los casos, pero si necesita acceso a la variable fuera del alcance que puede utilizar montón.

Para mejorar el rendimiento al usar montones también puede usar la funcionalidad para crear bloques de montones y eso puede ayudar a obtener rendimiento en lugar de asignar cada variable en una ubicación de memoria diferente.

24

Es más sutil que las otras respuestas sugieren. No existe una división absoluta entre los datos en la pila y los datos en el montón basados ​​en cómo lo declaras. Por ejemplo:

std::vector<int> v(10); 

En el cuerpo de una función, que declara un (array dinámico) vector de diez números enteros en la pila. Pero el almacenamiento administrado por vector no está en la pila.

Ah, pero (las otras respuestas sugieren) la vida útil de ese almacenamiento está limitada por la duración del vector, que aquí está basado en la pila, por lo que no importa cómo se implemente, solo podemos tratarlo como un objeto basado en pila con semántica de valores.

No es así. Supongamos que la función era:

void GetSomeNumbers(std::vector<int> &result) 
{ 
    std::vector<int> v(10); 

    // fill v with numbers 

    result.swap(v); 
} 

Así que cualquier cosa con una función swap (y cualquier tipo de valor complejo debe tener uno) puede servir como una especie de referencia rebindable a algunos datos del montón, bajo un sistema que garantiza un único propietario del esa información

Por lo tanto, el enfoque moderno de C++ es nunca almacena la dirección de los datos del montón en variables de puntero local desnudas. Todas las asignaciones de montón deben estar ocultas dentro de las clases.

Si hace eso, puede pensar en todas las variables de su programa como si fueran simples tipos de valor, y olvidarse completamente del montón (excepto cuando escribe una nueva clase contenedora de valor para algunos datos de montón, que deberían ser inusual).

Usted sólo tiene que mantener un bit especial de conocimiento para ayudarle a optimizar: siempre que sea posible, en lugar de asignar una variable a otra así:

a = b; 

ellas se comparten la siguiente manera:

a.swap(b); 

porque es mucho más rápido y no arroja excepciones. El único requisito es que no necesita b para continuar manteniendo el mismo valor (en su lugar obtendrá el valor a, que se descartará en a = b).

La desventaja es que este enfoque obliga a devolver valores de funciones a través de parámetros de salida en lugar del valor de retorno real. Pero lo están arreglando en C++ 0x con rvalue references.

En las situaciones más complicadas de todas, llevaría esta idea al extremo general y usaría una clase de puntero inteligente como shared_ptr que ya está en tr1. (Aunque yo argumentaría que si pareces necesitarlo, posiblemente te hayas movido fuera del punto óptimo de aplicabilidad de Standard C++.)

2

Para más información, puedes leer el artículo de Miro Samek sobre los problemas de usar el montón en el contexto de software integrado.

A Heap of Problems

3

pila es más eficiente y más fácil a los datos de ámbito gestionados.

Pero montón se debe utilizar para algo más grande que un pocos KB (es fácil en C++, basta con crear una boost::scoped_ptr en la pila para mantener un puntero a la memoria asignada).

Considere la posibilidad de un algoritmo recursivo que sigue llamando a sí mismo. ¡Es muy difícil limitar y/o adivinar el uso total de la pila! Mientras que en el montón, el asignador (malloc() o new) puede indicar falta de memoria al devolver NULL o throw ing.

Fuente: ¡Kernel de Linux cuya pila no es más grande que 8KB!

+0

Para referencia de otros lectores: (A) El "debería" aquí es puramente la opinión personal del usuario, extraída de, como máximo, 1 cita y 1 escenario que es poco probable que muchos usuarios encuentren (recursividad). Además, (B) la biblioteca estándar proporciona 'std :: unique_ptr', que debe preferirse a cualquier biblioteca externa como Boost (aunque eso alimenta cosas al estándar a lo largo del tiempo). –

0

probablemente esto ha sido respondido bastante bien. Me gustaría señalarle la siguiente serie de artículos para tener una comprensión más profunda de los detalles de bajo nivel. Alex Darby tiene una serie de artículos, donde lo guiará con un depurador. Aquí está la Parte 3 sobre la Pila. http://www.altdevblogaday.com/2011/12/14/c-c-low-level-curriculum-part-3-the-stack/

+0

Parece que el enlace está muerto, pero al comprobar la Máquina de devolución de archivos de Internet indica que solo habla de la pila y, por lo tanto, no responde a la pregunta específica de stack ** _ versus_heap **. -1 –