¿Uso apropiado de pila y montón en C ++?

122

He estado programando durante un tiempo, pero ha sido principalmente Java y C #. En realidad, nunca tuve que administrar la memoria por mi cuenta. Recientemente comencé a programar en C ++ y estoy un poco confundido sobre cuándo debería almacenar cosas en la pila y cuándo almacenarlas en el montón.

Según tengo entendido, las variables a las que se accede con mucha frecuencia deben almacenarse en la pila y los objetos, las variables raramente utilizadas y las grandes estructuras de datos deben almacenarse en el montón. ¿Es esto correcto o soy incorrecto?

Alejandro
fuente

Respuestas:

242

No, la diferencia entre stack y heap no es el rendimiento. Es vida útil: cualquier variable local dentro de una función (cualquier cosa que no malloc () o nueva) vive en la pila. Desaparece cuando regresa 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, acérquese desde el otro extremo, en lugar de tratar de entender lo que hace la pila en términos de un lenguaje de alto nivel, busque "pila de llamadas" y "convención de llamadas" y vea qué la máquina realmente lo hace cuando llamas a una función. La memoria de la computadora es solo una serie de direcciones; "Heap" y "stack" son invenciones del compilador.

Crashworks
fuente
77
Sería seguro agregar que la información de tamaño variable generalmente va en el montón. Las únicas excepciones que conozco son los VLA en C99 (que tiene un soporte limitado) y la función alloca () que a menudo es mal entendida incluso por los programadores de C.
Dan Olson
10
Buena explicación, aunque en un escenario multiproceso con asignaciones frecuentes y / o desasignaciones, el montón es un punto de contención, lo que afecta el rendimiento. Aún así, el alcance es casi siempre el factor decisivo.
peterchen 01 de
18
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 01 de
1
¿Es cierto que "la memoria de la computadora es solo una serie de direcciones", "montón" y "pila" son invenciones de la compilación? He leído en muchos lugares que la pila es una región especial de la memoria de nuestra computadora.
Vineeth Chitteti
2
@kai Esa es una forma de visualizarlo, pero no es necesariamente cierto físicamente hablando. El sistema operativo es responsable de asignar la pila y el montón de una aplicación. El compilador también es responsable, pero principalmente se basa en el sistema operativo para hacerlo. La pila es limitada y el montón no. Esto se debe a la forma en que el sistema operativo maneja la clasificación de estas direcciones de memoria en algo más estructurado para que múltiples aplicaciones puedan ejecutarse en el mismo sistema. Heap y stack no son los únicos, pero generalmente son los únicos que preocupan a la mayoría de los desarrolladores.
tsturzl
42

Yo diría:

Guárdelo en la pila, si PUEDE.

Guárdelo en el montón, si lo NECESITA.

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

  • Es demasiado grande: en los programas multiproceso en el sistema operativo de 32 bits, la pila tiene un tamaño pequeño y fijo (al menos en el momento de la creación de subprocesos) (generalmente solo unos pocos megas). Esto es para que pueda crear muchos subprocesos sin agotar la dirección espacio. Para programas de 64 bits, o programas de un solo hilo (Linux de todos modos), este no es un problema importante. Bajo Linux de 32 bits, los programas de un solo hilo usualmente usan pilas dinámicas que pueden seguir creciendo hasta llegar a la cima 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 sensibles, asignar objetos de tamaño no fijo en el montón (generalmente matrices cuyo tamaño no se conoce en tiempo de compilación).

MarkR
fuente
1
Cualquier cosa más que un par de KB generalmente se pone mejor en el montón. No sé detalles, pero no recuerdo haber trabajado nunca con una pila que era "unos pocos megas".
Dan Olson
2
Eso es algo que no le preocuparía a un usuario al principio. Para el usuario, los vectores y las listas parecen estar asignados en la pila, incluso si este STL almacena los contenidos en el montón. La pregunta parecía más en la línea de decidir cuándo llamar explícitamente a new / delete.
David Rodríguez - dribeas 01 de
1
Dan: He puesto 2 conciertos (Sí, G como en GIGS) en la pila debajo de Linux de 32 bits. Los límites de la pila dependen del sistema operativo.
Mr.Ree
66
Mrree: La pila de Nintendo DS es de 16 kilobytes. Algunos límites de pila dependen del hardware.
Ant
Ant: todas las pilas dependen del hardware, del sistema operativo y del compilador.
Viliami
24

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

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

En el cuerpo de una función, que declara una vector(matriz dinámica) de diez enteros en la pila. Pero el almacenamiento gestionado por el vectorno está en la pila.

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

No tan. Supongamos que la función era:

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

    // fill v with numbers

    result.swap(v);
}

Por lo tanto, cualquier cosa que tenga una swapfunción (y cualquier tipo de valor complejo debería tener una) puede servir como una especie de referencia modificable a algunos datos de almacenamiento dinámico, en un sistema que garantiza un único propietario de esos datos.

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

Si hace eso, puede pensar en todas las variables en su programa como si fueran tipos de valores simples, y olvidarse del montón por completo (excepto al escribir una nueva clase de contenedor de valor para algunos datos de montón, lo que debería ser inusual) .

Simplemente tiene que retener un conocimiento especial para ayudarlo a optimizar: cuando sea posible, en lugar de asignar una variable a otra como esta:

a = b;

intercambiarlos así:

a.swap(b);

porque es mucho más rápido y no arroja excepciones. El único requisito es que no necesita bcontinuar manteniendo el mismo valor (en su alugar, obtendrá el valor, que se tiraría a la basura a = b).

La desventaja es que este enfoque lo 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 referencias rvalue .

En las situaciones más complicadas de todas, llevaría esta idea al extremo general y usaría una clase de puntero inteligente como la shared_ptrque ya está en tr1. (Aunque diría que si parece que lo necesita, posiblemente se haya mudado fuera del punto ideal de aplicabilidad de Standard C ++).

Daniel Earwicker
fuente
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 crea. Una expresión idiomática utilizada con los objetos de la pila se llama RAII: esto implica el uso del objeto basado en la pila como un contenedor para un recurso, cuando el objeto se destruye, el recurso se limpiará. Los objetos basados ​​en la pila son más fáciles de rastrear cuando podría estar lanzando excepciones: no necesita preocuparse por eliminar un objeto basado en el montón en un controlador de excepciones. Esta es la razón por la cual los punteros sin formato no se usan normalmente en C ++ moderno, 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.

1800 INFORMACIÓN
fuente
5

Para agregar a las otras respuestas, también puede tratarse sobre el 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 encontrar un seguimiento de un bloque de memoria, que no es una operación de tiempo constante (y toma algunos ciclos y sobrecarga). Esto puede ser más lento a medida que la memoria se fragmenta y / o se está acercando al uso del 100% de su espacio de direcciones. Por otro lado, las asignaciones de pila son operaciones de tiempo constante, básicamente "libres".

Otra cosa a tener en cuenta (nuevamente, solo es importante si se convierte en un problema) es que, por lo general, el tamaño de la pila es fijo y puede ser mucho más bajo 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 te quedas sin espacio de pila, el tiempo de ejecución arrojará la excepción titular del sitio. No suele ser un gran problema, pero otra cosa a tener en cuenta.

Mella
fuente
Tanto el montón como la pila son memoria virtual paginada. El tiempo de búsqueda del montón es increíblemente rápido en comparación con lo que se necesita para asignar en una nueva memoria. Bajo Linux de 32 bits, puedo poner> 2gig en mi pila. En Mac, creo que la pila está limitada a 65 Meg.
Mr.Ree
3

Stack es más eficiente y más fácil de administrar los datos de ámbito.

Pero el montón debería usarse para algo más grande que unos pocos KB (es fácil en C ++, solo cree un boost::scoped_ptren la pila para mantener un puntero a la memoria asignada).

Considere un algoritmo recursivo que sigue llamando a sí mismo. ¡Es muy difícil limitar 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 regresar NULLo throwing.

Fuente : Kernel de Linux cuya pila no es mayor de 8 KB.

unixman83
fuente
Para referencia de otros lectores: (A) El "deber" aquí es puramente la opinión personal del usuario, extraída de, en el mejor de los casos, 1 cita y 1 escenario que muchos usuarios tienen pocas probabilidades de encontrar (recursividad). Además, (B) proporciona la biblioteca estándar std::unique_ptr, que debería preferirse a cualquier biblioteca externa como Boost (aunque eso alimenta las cosas al estándar con el tiempo).
underscore_d
2

Para completar, puede leer el artículo de Miro Samek sobre los problemas de usar el montón en el contexto del software incorporado .

Un montón de problemas

Daniel Daranas
fuente
1

La opción de asignar en el montón o en la pila es la que se hace 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.

Rob Lachlan
fuente
44
Sospecho que estaba preguntando cuándo poner las cosas en el montón, no cómo.
Steve Rowe
0

En mi opinión, hay dos factores decisivos.

1) Scope of variable
2) Performance.

Preferiría usar stack en la mayoría de los casos, pero si necesita acceso a variables fuera del alcance, puede usar heap.

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

anand
fuente
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 te guía con un depurador. Aquí está la Parte 3 sobre la Pila. http://www.altdevblogaday.com/2011/12/14/cc-low-level-curriculum-part-3-the-stack/

CABALLETE
fuente
El enlace parece estar muerto, pero la comprobación de la Máquina de devolución de archivos de Internet indica que solo habla de la pila y, por lo tanto, no hace nada para responder la pregunta específica aquí de pila versus montón . -1
underscore_d