Pila, Estática y Montón en C++


He buscado, pero no he entendido muy bien estos tres conceptos. ¿Cuándo tengo que usar asignación dinámica (en el montón) y cuál es su ventaja real? ¿Cuáles son los problemas de estática y pila? ¿Podría escribir una aplicación completa sin asignar variables en el montón?

He oído que otros lenguajes incorporan un "recolector de basura" para que no tengas que preocuparte por la memoria. ¿Qué hace el recolector de basura?

¿Qué podrías hacer manipulando la memoria ¿solo que no podrías usar este recolector de basura?

Una vez alguien me dijo que con esta declaración:

int * asafe=new int;

Tengo un "puntero a un puntero". ¿Qué significa? Es diferente de:

asafe=new int;

?

Author: user2864740, 2009-01-03

9 answers

Se hizo una pregunta similar, pero no se preguntó acerca de la estática.

Resumen de lo que son la memoria estática, la pila y la pila:

  • Una variable estática es básicamente una variable global, incluso si no puede acceder a ella globalmente. Por lo general, hay una dirección para él que está en el propio ejecutable. Solo hay una copia para todo el programa. No importa cuántas veces entres en una llamada de función (o clase) (y en cuántos hilos!) la variable se refiere a la misma ubicación de la memoria.

  • El montón es un montón de memoria que se puede utilizar dinámicamente. Si desea 4kb para un objeto, entonces el asignador dinámico mirará a través de su lista de espacio libre en el montón, elegirá un fragmento de 4kb y se lo dará. Generalmente, el asignador dinámico de memoria (malloc, new, etc.) comienza al final de la memoria y funciona hacia atrás.

  • Explicar cómo una pila crece y se encoge está un poco fuera del alcance de esta respuesta, pero basta con decir que siempre añadir y quitar del final solamente. Las pilas generalmente comienzan altas y crecen hacia abajo a direcciones más bajas. Se queda sin memoria cuando la pila se encuentra con el asignador dinámico en algún lugar en el medio (pero consulte memoria física versus virtual y fragmentación). Los subprocesos múltiples requerirán múltiples pilas (el proceso generalmente reserva un tamaño mínimo para la pila).

Cuando se desea utilizar cada uno:

  • Las estáticas / globales son útiles para la memoria que conoces siempre va a necesitar y usted sabe que usted no desea desasignar. (Por cierto, se puede pensar que los entornos embebidos solo tienen memoria estática... la pila y el montón son parte de un espacio de direcciones conocido compartido por un tercer tipo de memoria: el código del programa. Los programas a menudo hacen asignaciones dinámicas fuera de su memoria estática cuando necesitan cosas como listas enlazadas. Pero independientemente, la memoria estática en sí (el búfer) no está "asignada", sino que otros objetos se asignan fuera de la memoria retenida por el búfer para este propósito. También puede hacer esto en juegos no incrustados, y los juegos de consola con frecuencia evitarán los mecanismos de memoria dinámica integrados en favor de controlar estrictamente el proceso de asignación mediante el uso de búferes de tamaños preestablecidos para todas las asignaciones.)

  • Las variables de pila son útiles para cuando sepa que mientras la función esté en el ámbito (en la pila en algún lugar), querrá que las variables permanezcan. Las pilas son buenas para las variables que necesita para código donde se encuentran, pero que no es necesario fuera de ese código. También son muy agradables para cuando está accediendo a un recurso, como un archivo, y desea que el recurso desaparezca automáticamente cuando deje ese código.

  • Las asignaciones de montones (memoria asignada dinámicamente) son útiles cuando se quiere ser más flexible que lo anterior. Con frecuencia, se llama a una función para responder a un evento (el usuario hace clic en el botón "crear cuadro"). La respuesta adecuada puede requerir la asignación de un nuevo objeto (un nuevo objeto de Caja) que debe permanecer mucho tiempo después de que la función salga, por lo que no puede estar en la pila. Pero no sabes cuántas cajas querrías al inicio del programa, por lo que no puede ser una estática.

Recolección de basura

He oído mucho últimamente acerca de lo grandes que son los recolectores de basura, así que tal vez un poco de una voz disidente sería útil.

La recolección de basura es un mecanismo maravilloso para cuando el rendimiento no es un gran problema. Oigo Los GCS son cada vez mejores y más sofisticados, pero el hecho es que puede verse obligado a aceptar una penalización de rendimiento (dependiendo del caso de uso). Y si eres perezoso, todavía puede no funcionar correctamente. En el mejor de los casos, los Recolectores de basura se dan cuenta de que su memoria desaparece cuando se da cuenta de que no hay más referencias a ella (ver recuento de referencias). Pero, si tiene un objeto que se refiere a sí mismo (posiblemente refiriéndose a otro objeto que se refiere de nuevo), entonces recuento de referencia solo no indicará que la memoria se puede eliminar. En este caso, el GC necesita mirar toda la sopa de referencia y averiguar si hay islas a las que solo se hace referencia por sí mismas. De improviso, supongo que es una operación O (n^2), pero sea lo que sea, puede empeorar si estás preocupado por el rendimiento. (Editar: Martin B señala que es O (n) para algoritmos razonablemente eficientes. Eso sigue siendo O (n) demasiado si usted está preocupado por el rendimiento y puede desasigne en tiempo constante sin recolección de basura.)

Personalmente, cuando escucho a la gente decir que C++ no tiene recolección de basura, mi mente lo etiqueta como una característica de C++, pero probablemente estoy en la minoría. Probablemente lo más difícil para la gente aprender sobre programación en C y C++ son los punteros y cómo manejar correctamente sus asignaciones de memoria dinámica. Algunos otros lenguajes, como Python, serían horribles sin GC, así que creo que todo se reduce a lo que quieres de un lenguaje. Si desea un rendimiento confiable, entonces C++ sin recolección de basura es la única cosa de este lado de Fortran que se me ocurre. Si desea facilidad de uso y ruedas de entrenamiento (para evitar que se caiga sin requerir que aprenda la gestión de memoria "adecuada"), elija algo con un GC. Incluso si sabe cómo administrar bien la memoria, le ahorrará tiempo que puede gastar optimizando otro código. Realmente no hay mucho de una penalización de rendimiento más, pero si realmente necesita confiable rendimiento (y la capacidad de saber exactamente lo que está pasando, cuándo, bajo las portadas) entonces me quedaría con C++. Hay una razón por la que cada motor de juego importante del que he oído hablar está en C++ (si no es C o assembly). Python, et al están bien para scripting, pero no el motor principal del juego.

 204
Author: markets,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:18:18

Lo siguiente, por supuesto, no es muy preciso. Tómalo con un grano de sal cuando lo leas:)

Bueno, las tres cosas a las que te refieres son la duración del almacenamiento automático, estático y dinámico, que tiene algo que ver con cuánto tiempo viven los objetos y cuándo comienzan a vivir.


Duración del almacenamiento automático

Utiliza la duración de almacenamiento automático para datos de corta duración y pequeños , que solo se necesitan localmente dentro de algunos bloque:

if(some condition) {
    int a[3]; // array a has automatic storage duration
    fill_it(a);
    print_it(a);
}

El tiempo de vida termina tan pronto como salimos del bloque, y comienza tan pronto como se define el objeto. Son el tipo más simple de duración de almacenamiento y son mucho más rápidos que la duración de almacenamiento dinámico en particular.


Duración del almacenamiento estático

Se utiliza la duración de almacenamiento estático para las variables libres, a las que cualquier código puede acceder todo el tiempo, si su ámbito permite dicho uso (ámbito de espacio de nombres), y para las variables locales que necesitan ampliar su vida útil a lo largo de la salida de su ámbito (ámbito local), y para las variables miembro que necesitan ser compartidas por todos los objetos de su clase (ámbito de clases). Su vida depende del alcance en el que se encuentren. Ellos pueden tener ámbito de espacio de nombres y ámbito local y el ámbito de la clase. Lo que es cierto acerca de ambos es que, una vez que comienza su vida, la vida termina en el final del programa. Aquí hay dos ejemplos:

// static storage duration. in global namespace scope
string globalA; 
int main() {
    foo();
    foo();
}

void foo() {
    // static storage duration. in local scope
    static string localA;
    localA += "ab"
    cout << localA;
}

El programa imprime ababab porque localA no es destruido al salir de su bloque. Se puede decir que los objetos que tienen ámbito local comienzan su vida cuando el control alcanza su definición. Para localA, sucede cuando se ingresa el cuerpo de la función. Para los objetos en el ámbito del espacio de nombres, la vida útil comienza en el inicio del programa. Lo mismo es cierto para los objetos estáticos de ámbito de clase:

class A {
    static string classScopeA;
};

string A::classScopeA;

A a, b; &a.classScopeA == &b.classScopeA == &A::classScopeA;

Como ves, classScopeA no está ligado a objetos particulares de su clase, sino a la clase misma. La dirección de los tres nombres anteriores es la misma, y todos denotan el mismo objeto. Hay una regla especial sobre cuándo y cómo se inicializan los objetos estáticos, pero no nos preocupemos por eso ahora. Eso se entiende por el término fiasco de orden de inicialización estática.


Duración dinámica del almacenamiento

La última duración de almacenamiento es dinámica. Lo utilizas si quieres que los objetos vivan en otra isla, y quieres poner punteros alrededor de esa referencia. También los usa si sus objetos son grandes, y si desea crear arrays de tamaño solo conocidos en tiempo de ejecución. Debido a esta flexibilidad, los objetos que tienen una duración de almacenamiento dinámica son complicados y lentos de administrar. Los objetos que tienen esa duración dinámica comienzan su vida útil cuando ocurre una invocación de operador nueva apropiada:

int main() {
    // the object that s points to has dynamic storage 
    // duration
    string *s = new string;
    // pass a pointer pointing to the object around. 
    // the object itself isn't touched
    foo(s);
    delete s;
}

void foo(string *s) {
    cout << s->size();
}

Su vida útil termina solo cuando llamas delete para ellos. Si te olvidas de eso, esos objetos nunca terminan su vida. Y los objetos de clase que definen un constructor declarado por el usuario no tendrán llamaron sus destructores. Los objetos que tienen una duración de almacenamiento dinámica requieren el manejo manual de su vida útil y el recurso de memoria asociado. Las bibliotecas existen para facilitar su uso. Recolección de basura explícita para objetos particulares se puede establecer usando un puntero inteligente:

int main() {
    shared_ptr<string> s(new string);
    foo(s);
}

void foo(shared_ptr<string> s) {
    cout << s->size();
}

No tiene que preocuparse por llamar a delete: El rpp compartido lo hace por usted, si el último puntero que hace referencia al objeto sale del ámbito. El ptr compartido en sí tiene automático duración del almacenamiento. Así que su vida útil se administra automáticamente, lo que le permite verificar si debe eliminar el objeto dinámico apuntado en su destructor. Para la referencia shared_ptr, consulte documentos de boost: http://www.boost.org/doc/libs/1_37_0/libs/smart_ptr/shared_ptr.htm

 51
Author: Johannes Schaub - litb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-11-02 20:27:56

Se ha dicho elaboradamente, al igual que "la respuesta corta":

  • Variable Estática (clase)
    lifetime = tiempo de ejecución del programa (1)
    visibilidad = determinada por modificadores de acceso (privado/protegido/público)

  • Variable estática (ámbito global)
    lifetime = tiempo de ejecución del programa (1)
    visibility = la unidad de compilación en la que se crea una instancia(2)

  • Variable de montón
    lifetime = definido por usted (nuevo en eliminar)
    visibilidad = definido por usted (a lo que le asigne el puntero)

  • Variable de pila
    visibilidad = desde la declaración hasta la salida del ámbito
    lifetime = desde la declaración hasta que se sale del ámbito de la declaración


(1) más exactamente: desde la inicialización hasta la desinicialización de la unidad de compilación (es decir, el archivo C / C++). El orden de inicialización de las unidades de compilación no está definido por el estándar.

(2) Cuidado: si instanciar una variable estática en un encabezado, cada unidad de compilación obtiene su propia copia.

 35
Author: peterchen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-02-10 07:14:32

Estoy seguro de que uno de los pedantes encontrará una respuesta mejor en breve, pero la principal diferencia es la velocidad y el tamaño.

Pila

Dramáticamente más rápido para asignar. Se hace en O (1) ya que se asigna al configurar el marco de la pila, por lo que es esencialmente libre. El inconveniente es que si te quedas sin espacio en la pila, estás deshuesado. Puede ajustar el tamaño de la pila, pero IIRC tiene ~2MB para jugar. Además, tan pronto como salga de la función, todo en la pila se borrará. Así que puede ser problemático referirse a él más tarde. (Los punteros para apilar objetos asignados conducen a errores.)

Montón

Dramáticamente más lento para asignar. Pero tienes a GB con quien jugar y a quien apuntar.

Recolector de basura

El recolector de basura es un código que se ejecuta en segundo plano y libera memoria. Cuando asigna memoria en el montón, es muy fácil olvidarse de liberarla, lo que se conoce como fuga de memoria. Con el tiempo, la memoria que consume su aplicación crece y crece hasta se estrella. Tener un recolector de basura liberando periódicamente la memoria que ya no necesita ayuda a eliminar esta clase de errores. Por supuesto, esto tiene un precio, ya que el recolector de basura ralentiza las cosas.

 5
Author: Chris Smith,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-03 06:06:29

¿Cuáles son los problemas de static y stack?

El problema con la asignación "estática" es que la asignación se hace en tiempo de compilación: no se puede usar para asignar un número variable de datos, cuyo número no se conoce hasta el tiempo de ejecución.

El problema con la asignación en la "pila" es que la asignación se destruye tan pronto como la subrutina que hace la asignación regresa.

Podría escribir una aplicación completa sin asignar variables en el montón?

Tal vez, pero no una aplicación grande, normal y no trivial (pero los llamados programas "incrustados" podrían escribirse sin el montón, utilizando un subconjunto de C++).

¿Qué recolector de basura hace ?

Sigue observando sus datos ("marcar y barrer") para detectar cuando su aplicación ya no hace referencia a ella. Esto es conveniente para la aplicación, porque la aplicación no necesita desasignar los datos ... pero el recolector de basura podría ser computacionalmente caro.

Los recolectores de basura no son una característica habitual de la programación en C++.

¿Qué podrías hacer manipulando la memoria por ti mismo que no podrías hacer usando este recolector de basura?

Aprenda los mecanismos de C++ para la desasignación de memoria determinista:

  • 'estático': nunca desasignado
  • 'stack': tan pronto como la variable "sale de alcance"
  • 'heap': cuando se elimina el puntero (eliminado explícitamente por el aplicación, o suprimido implícitamente dentro de una u otra subrutina)
 3
Author: ChrisW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-03 06:12:21

La asignación de memoria de pila (variables de función, variables locales) puede ser problemática cuando la pila es demasiado "profunda" y se desborda la memoria disponible para las asignaciones de pila. El heap es para objetos a los que se debe acceder desde varios subprocesos o durante todo el ciclo de vida del programa. Puede escribir un programa completo sin usar el montón.

Puede filtrar la memoria con bastante facilidad sin un recolector de basura, pero también puede dictar cuándo se liberan los objetos y la memoria. He corrido a problemas con Java cuando se ejecuta el GC y tengo un proceso en tiempo real, porque el GC es un hilo exclusivo (nada más se puede ejecutar). Por lo tanto, si el rendimiento es crítico y puede garantizar que no haya objetos filtrados, no usar un GC es muy útil. De lo contrario, solo te hace odiar la vida cuando tu aplicación consume memoria y tienes que rastrear la fuente de una fuga.

 1
Author: Rob Elsner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-03 06:09:58

Qué pasa si su programa no sabe por adelantado cuánta memoria debe asignar (por lo tanto, no puede usar variables de pila). Digamos listas enlazadas, las listas pueden crecer sin saber por adelantado cuál es su tamaño. Por lo tanto, la asignación en un montón tiene sentido para una lista vinculada cuando no se sabe cuántos elementos se insertarían en ella.

 1
Author: kal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-01-03 06:36:24

Una ventaja de la CG en algunas situaciones es una molestia en otras; la confianza en la CG alienta a no pensar mucho en ella. En teoría, espera hasta el período "inactivo" o hasta que sea absolutamente necesario, cuando robará el ancho de banda y causará latencia de respuesta en su aplicación.

Pero no tienes que 'no pensar en ello.'Al igual que con todo lo demás en aplicaciones multiproceso, cuando puedes ceder, puedes ceder. Así, por ejemplo, en. Net, es posible solicitar un GC; haciendo esto, en lugar de menos frecuente más larga ejecución GC, puede tener más frecuente más corta ejecución GC, y difundir la latencia asociada con esta sobrecarga.

Pero esto derrota la atracción primaria de GC que parece ser "alentada a no tener que pensar mucho en ella porque es auto-mat-ic."

Si se expuso por primera vez a la programación antes de que GC se hiciera prevalente y se sintiera cómodo con malloc / free y new / delete, entonces podría incluso ser el caso de que encuentre GC un poco molesto y / o desconfía (como uno podría desconfiar de la 'optimización', que ha tenido una historia a cuadros.) Muchas aplicaciones toleran latencia aleatoria. Pero para las aplicaciones que no lo hacen, donde la latencia aleatoria es menos aceptable, una reacción común es evitar los entornos GC y moverse en la dirección de código puramente no administrado (o Dios no lo quiera, un arte moribundo, lenguaje ensamblador.)

Tuve un estudiante de verano aquí hace un tiempo, un interno, un chico inteligente, que fue destetado en GC; estaba tan adament sobre la superioridad de GC que incluso cuando programaba en C/C++ no administrado se negó a seguir el modelo malloc / free new / delete porque, cito, " no debería tener que hacer esto en un lenguaje de programación moderno."¿ Y sabes? Para aplicaciones pequeñas y de ejecución corta, de hecho puede salirse con la suya, pero no para aplicaciones de rendimiento de ejecución larga.

 1
Author: frediano,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-22 13:06:52

Stack es una memoria asignada por el compilador, cuando alguna vez compilamos el programa, por defecto el compilador asigna alguna memoria desde el sistema operativo (podemos cambiar la configuración desde la configuración del compilador en tu IDE) y el sistema operativo es el que te da la memoria, depende de mucha memoria disponible en el sistema y muchas otras cosas, y viniendo a stack memory es allocate cuando declaramos una variable copian (ref como formales) esas variables son empujadas a stack siguen algunas convenciones de nomenclatura por defecto su CDECL en Visual studios ejemplo: notación de infijo: c = a + b; el empuje de la pila se realiza de derecha a izquierda, b para apilar, operador, a para apilar y resultado de los i, e c para apilar. En notación pre fix: =+taxi Aquí todas las variables se empujan a la pila 1st (de derecha a izquierda)y luego se realiza la operación. Esta memoria asignada por el compilador es fija. Así que supongamos que se asigna 1MB de memoria a nuestra aplicación, digamos que las variables utilizan 700kb de memoria (todas las variables locales se envían a la pila a menos que se asignan dinámicamente) por lo que la memoria restante de 324 kb se asigna a heap. Y esta pila tiene menos tiempo de vida, cuando el alcance de la función termina estas pilas se borra.

 0
Author: raj,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-11-06 04:50:32