operaciones - pilas y colas en c++
¿Uso correcto de pila y pila en C++? (10)
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 acceder a un alcance externo variable, puede usar heap.
Para mejorar el rendimiento durante el uso de montones, también puede usar la funcionalidad para crear bloque de montones y eso puede ayudar a obtener rendimiento en lugar de asignar cada variable en una ubicación de memoria diferente.
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.
Según entiendo, las variables a las que se accede con mucha frecuencia se deben almacenar en la pila y los objetos, las variables que rara vez se usan y las estructuras de datos grandes deben almacenarse en el montón. ¿Es correcto o soy incorrecto?
Es más sutil de lo que sugieren las otras respuestas. 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, eso declara un vector
(matriz dinámica) de diez enteros en la pila. Pero el almacenamiento administrado por el vector
no está en la pila.
Ah, pero (las otras respuestas sugieren) la vida útil de ese almacenamiento está limitada por la vida útil del vector
, que aquí está basado en la pila, por lo que no importa cómo se implemente, solo podemos tratarlo como una pila. objeto con semántica de valores.
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 elemento con una función de swap
(y cualquier tipo de valor complejo debería tener uno) puede servir como un tipo de referencia reutilizable para algunos datos de almacenamiento dinámico, bajo 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 local desnudas. Todas las asignaciones de montón deben estar ocultas dentro de las clases.
Si lo hace, puede pensar en todas las variables de su programa como si fueran simples tipos de valores, y olvidarse por completo del montón (excepto cuando escribe una nueva clase contenedora similar a un valor para algunos datos del montón, lo que debería ser inusual) .
Simplemente debe conservar un poco de conocimiento especial para ayudarlo a optimizar: cuando sea posible, en lugar de asignar una variable a otra como esta:
a = b;
cambiarlos así:
a.swap(b);
porque es mucho más rápido y no arroja excepciones. El único requisito es que no necesite b
para continuar manteniendo el mismo valor (en cambio, obtendrá el valor de uno, que se descartaría 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 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 shared_ptr
que ya está en tr1. (Aunque yo argumentaría que si parece que lo necesita, posiblemente se haya alejado del punto óptimo de aplicabilidad de Standard C ++).
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.
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, acércate desde el otro extremo, en lugar de tratar de entender qué hace la pila en términos de un lenguaje de alto nivel, busca "call stack" y "calling convention" y mira qué la máquina realmente funciona cuando llamas a una función. La memoria de la computadora es solo una serie de direcciones; "montón" y "pila" son invenciones del compilador.
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:
Asignar 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 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 (de nuevo, realmente solo es importante si se convierte en un problema) es que generalmente 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.
Para completar, puede leer el artículo de Miro Samek sobre los problemas de usar el montón en el contexto del software integrado .
Stack es más eficiente y más fácil de manejar los datos de alcance.
Pero heap debería usarse para algo más grande que unos pocos KB (es fácil en C ++, solo crea un boost::scoped_ptr
en 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 y / o adivinar el uso total de la pila! Mientras que en el montón, el asignador ( malloc()
o new
) puede indicar throw
de memoria devolviendo NULL
o throw
.
Fuente : Linux Kernel cuya pila no es más grande que 8KB.
También podría almacenar 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.
Yo diría:
Guárdelo en la pila, si PUEDE.
Guárdelo en el montón, si 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 programas multiproceso con sistema operativo de 32 bits, la pila tiene un tamaño pequeño y fijo (al menos en el tiempo de creación del hilo) (normalmente solo unos pocos megas. Esto es para poder crear muchos hilos sin agotar la dirección Para programas de 64 bits o de subproceso único (Linux de todos modos), esto no es un problema importante. En Linux de 32 bits, los programas de subprocesos únicos generalmente usan acumulaciones dinámicas que pueden seguir creciendo hasta llegar a la cima del montón.
- Necesita acceder a él fuera del alcance del marco de pila original: esta es realmente la razón principal.
Es posible, con compiladores sensatos, asignar objetos de tamaño no fijo en el montón (generalmente matrices cuyo tamaño no se conoce en tiempo de compilación).
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/