¿Qué es el desbobinado de la pila?

193

¿Qué es el desbobinado de la pila? ¡Busqué pero no pude encontrar una respuesta esclarecedora!

Rajendra Uppal
fuente
76
Si no sabe qué es, ¿cómo puede esperar que sepa que no son lo mismo para C y para C ++?
dreamlax
@dreamlax: Entonces, ¿cómo el concepto de "desbobinado de pila" es diferente en C y C ++?
Destructor
2
@PravasiMeet: C no tiene manejo de excepciones, por lo que el desbobinado de la pila es muy sencillo, sin embargo, en C ++, si se lanza una excepción o sale una función, el desbobinado de la pila implica la destrucción de cualquier objeto C ++ con una duración de almacenamiento automática.
dreamlax

Respuestas:

150

El desenrollado de la pila generalmente se menciona en relación con el manejo de excepciones. Aquí hay un ejemplo:

void func( int x )
{
    char* pleak = new char[1024]; // might be lost => memory leak
    std::string s( "hello world" ); // will be properly destructed

    if ( x ) throw std::runtime_error( "boom" );

    delete [] pleak; // will only get here if x == 0. if x!=0, throw exception
}

int main()
{
    try
    {
        func( 10 );
    }
    catch ( const std::exception& e )
    {
        return 1;
    }

    return 0;
}

Aquí la memoria asignada para pleakse perderá si se produce una excepción, mientras que la memoria asignada a sserá liberada adecuadamente por el std::stringdestructor en cualquier caso. Los objetos asignados en la pila se "desenrollan" cuando se sale del alcance (aquí el alcance es de la función func). Esto lo hace el compilador insertando llamadas a destructores de variables automáticas (pila).

Ahora, este es un concepto muy poderoso que conduce a la técnica llamada RAII , que es Adquisición de recursos es inicialización , que nos ayuda a administrar recursos como memoria, conexiones de bases de datos, descriptores de archivos abiertos, etc. en C ++.

Ahora eso nos permite ofrecer garantías de seguridad excepcionales .

Nikolai Fetissov
fuente
¡Eso fue realmente esclarecedor! Así que entiendo esto: si mi proceso se bloquea inesperadamente al salir de CUALQUIER bloque en el que se estaba abriendo la pila de tiempo, entonces podría suceder que el código después del código del controlador de excepción no se ejecute en absoluto, y puede causar pérdidas de memoria, montón corrupción etc.
Rajendra Uppal
15
Si el programa "falla" (es decir, finaliza debido a un error), entonces cualquier pérdida de memoria o corrupción del montón es irrelevante ya que la memoria se libera al finalizar.
Tyler McHenry
1
Exactamente. Gracias. Solo estoy un poco disléxico hoy.
Nikolai Fetissov
11
@TylerMcHenry: el estándar no garantiza que los recursos o la memoria se liberen al finalizar. Sin embargo, la mayoría de los sistemas operativos lo hacen.
Mooing Duck
3
delete [] pleak;solo se alcanza si x == 0.
Jib
71

Todo esto se relaciona con C ++:

Definición : a medida que crea objetos estáticamente (en la pila en lugar de asignarlos en la memoria de almacenamiento dinámico) y realiza llamadas a funciones, se "apilan".

Cuando se sale de un alcance (cualquier cosa delimitada por {y }) (al usar return XXX;, llegar al final del alcance o lanzar una excepción) todo lo que está dentro de ese alcance se destruye (los destructores son llamados para todo). Este proceso de destruir objetos locales y llamar a destructores se denomina desenrollado de pila.

Tiene los siguientes problemas relacionados con el desenrollado de la pila:

  1. evitar fugas de memoria (cualquier cosa asignada dinámicamente que no sea administrada por un objeto local y limpiada en el destructor se filtrará) - vea RAII referido por Nikolai, y la documentación para boost :: scoped_ptr o este ejemplo de uso de boost :: mutex :: scoped_lock .

  2. coherencia del programa: las especificaciones de C ++ establecen que nunca debe lanzar una excepción antes de que se haya manejado cualquier excepción existente. Esto significa que el proceso de desenrollado de la pila nunca debería generar una excepción (ya sea usar solo código garantizado para no arrojar destructores, o rodear todo en destructores con try {y } catch(...) {}).

Si algún destructor arroja una excepción durante el desbobinado de la pila, terminará en la tierra del comportamiento indefinido que podría causar que su programa finalice inesperadamente (comportamiento más común) o que el universo termine (teóricamente posible pero aún no se ha observado en la práctica).

utnapistim
fuente
2
De lo contrario. Si bien no se debe abusar de los gotos, causan el desbobinado de la pila en MSVC (no en GCC, por lo que probablemente sea una extensión). setjmp y longjmp hacen esto de forma multiplataforma, con algo menos de flexibilidad.
Patrick Niedzielski el
10
Acabo de probar esto con gcc y llama correctamente a los destructores cuando sales de un bloque de código. Consulte stackoverflow.com/questions/334780/… . Como se menciona en ese enlace, esto también forma parte del estándar.
Damyan
1
leyendo Nikolai's, jrista's y tu respuesta en este orden, ¡ahora tiene sentido!
n611x007
@sashoalm ¿Realmente crees que es necesario editar una publicación siete años después?
David Hoelzer el
41

En un sentido general, una pila "desenrollar" es prácticamente sinónimo del final de una llamada a la función y la posterior aparición de la pila.

Sin embargo, específicamente en el caso de C ++, el desbobinado de la pila tiene que ver con cómo C ++ llama a los destructores para los objetos asignados desde el inicio de cualquier bloque de código. Los objetos que se crearon dentro del bloque se desasignan en orden inverso a su asignación.

jrista
fuente
44
No hay nada especial en los trybloques. Los objetos de pila asignados en cualquier bloque (estén tryo no) están sujetos a desenrollarse cuando el bloque sale.
Chris Jester-Young
Ha pasado un tiempo desde que hice mucha codificación en C ++. Tuve que sacar esa respuesta de las profundidades oxidadas. ; P
jrista
no te preocupes Todos tienen "lo malo" de vez en cuando.
bitc
13

El desbobinado de la pila es un concepto principalmente de C ++, que trata de cómo se destruyen los objetos asignados a la pila cuando se sale de su alcance (ya sea normalmente o mediante una excepción).

Digamos que tiene este fragmento de código:

void hw() {
    string hello("Hello, ");
    string world("world!\n");
    cout << hello << world;
} // at this point, "world" is destroyed, followed by "hello"
Chris Jester-Young
fuente
¿Esto se aplica a algún bloque? Quiero decir, si solo hay {// algunos objetos locales}
Rajendra Uppal
@Rajendra: Sí, un bloque anónimo define un área de alcance, por lo que también cuenta.
Michael Myers
12

No sé si leíste esto todavía, pero el artículo de Wikipedia sobre la pila de llamadas tiene una explicación decente.

Desbobinado:

Al regresar de la función llamada, el cuadro superior saldrá de la pila, quizás dejando un valor de retorno. El acto más general de extraer uno o más cuadros de la pila para reanudar la ejecución en otra parte del programa se denomina desenrollado de la pila. y debe realizarse cuando se usan estructuras de control no locales, como las que se usan para el manejo de excepciones. En este caso, el marco de la pila de una función contiene una o más entradas que especifican manejadores de excepciones. Cuando se lanza una excepción, la pila se desenrolla hasta que se encuentra un controlador que está preparado para manejar (capturar) el tipo de excepción lanzada.

Algunos lenguajes tienen otras estructuras de control que requieren un desenrollado general. Pascal permite que una instrucción goto global transfiera el control de una función anidada a una función externa previamente invocada. Esta operación requiere que la pila se desenrolle, eliminando tantos marcos de pila como sea necesario para restaurar el contexto adecuado para transferir el control a la declaración de destino dentro de la función externa adjunta. Del mismo modo, C tiene las funciones setjmp y longjmp que actúan como gotos no locales. Common Lisp permite controlar lo que sucede cuando la pila se desenrolla utilizando el operador especial de desenrollado-protección.

Al aplicar una continuación, la pila se desenrolla (lógicamente) y luego se rebobina con la pila de la continuación. Esta no es la única forma de implementar continuaciones; por ejemplo, usando múltiples pilas explícitas, la aplicación de una continuación simplemente puede activar su pila y liquidar un valor para ser pasado. El lenguaje de programación Scheme permite ejecutar thunks arbitrarios en puntos específicos al "desenrollar" o "rebobinar" de la pila de control cuando se invoca una continuación.

Inspección [editar]

John Weldon
fuente
9

Leí una publicación de blog que me ayudó a entender.

¿Qué es el desbobinado de la pila?

En cualquier lenguaje que admita funciones recursivas (es decir, casi todo excepto Fortran 77 y Brainf * ck), el tiempo de ejecución del lenguaje mantiene una pila de las funciones que se están ejecutando actualmente. El desbobinado de la pila es una forma de inspeccionar y posiblemente modificar esa pila.

¿Por qué querrías hacer eso?

La respuesta puede parecer obvia, pero hay varias situaciones relacionadas, pero sutilmente diferentes, en las que el desenrollado es útil o necesario:

  1. Como mecanismo de control de flujo de tiempo de ejecución (excepciones de C ++, C longjmp (), etc.).
  2. En un depurador, para mostrar al usuario la pila.
  3. En un generador de perfiles, para tomar una muestra de la pila.
  4. Desde el programa en sí (como desde un controlador de bloqueo para mostrar la pila).

Estos tienen requisitos sutilmente diferentes. Algunos de estos son críticos para el rendimiento, otros no. Algunos requieren la capacidad de reconstruir registros desde el marco externo, otros no. Pero entraremos en todo eso en un segundo.

Puedes encontrar la publicación completa aquí .

L. Langó
fuente
7

Todos han hablado sobre el manejo de excepciones en C ++. Pero, creo que hay otra connotación para el desenrollado de la pila y que está relacionada con la depuración. Un depurador tiene que desenrollar la pila cada vez que se supone que debe ir a un cuadro anterior al cuadro actual. Sin embargo, esto es una especie de desenrollado virtual, ya que necesita rebobinar cuando vuelve al marco actual. El ejemplo de esto podría ser comandos arriba / abajo / bt en gdb.

bbv
fuente
55
La acción del depurador generalmente se denomina "Caminata de pila" que simplemente analiza la pila. "Desenrollar pila" implica no solo "Caminar pila" sino también llamar a los destructores de objetos que existen en la pila.
Adisak
@Adisak No sabía que también se llama "caminata de pila". Siempre he estado viendo "desbobinado de pila" en el contexto de todos los artículos del depurador e incluso dentro del código gdb. Sentí que el "desbobinado de la pila" es más apropiado ya que no se trata solo de mirar en la información de la pila para cada función, sino que implica el desenrollado de la información del marco (cf CFI en enano). Esto se procesa en orden una función por una.
bbv
Supongo que la "caminata de la pila" se hizo más famosa por Windows. Además, encontré como ejemplo code.google.com/p/google-breakpad/wiki/StackWalking, aparte del documento enano del estándar en sí, utiliza el término desenrollado pocas veces. Aunque de acuerdo, es un desenrollado virtual. Además, la pregunta parece ser preguntar por todos los significados posibles que puede sugerir "desenrollar la pila".
bbv
7

En mi opinión, el siguiente diagrama en este artículo explica muy bien el efecto del desbobinado de la pila en la ruta de la siguiente instrucción (que se ejecutará una vez que se arroje una excepción que no se haya detectado):

ingrese la descripción de la imagen aquí

En la foto:

  • La primera es una ejecución de llamada normal (sin excepción).
  • Parte inferior cuando se produce una excepción.

En el segundo caso, cuando se produce una excepción, la pila de llamadas de función se busca linealmente para el controlador de excepciones. La búsqueda finaliza en la función con un controlador de excepciones, es decir, main()con un try-catchbloque adjunto , pero no antes de eliminar todas las entradas anteriores de la pila de llamadas de función.

Saurav Sahu
fuente
Los diagramas son buenos, pero la explicación es un poco confusa, a saber. ... con el bloque try-catch adjunto, pero no antes de eliminar todas las entradas anteriores de la pila de llamadas de función ...
Atul
3

El tiempo de ejecución de C ++ destruye todas las variables automáticas creadas entre lanzar y atrapar. En este sencillo ejemplo a continuación, f1 () throws y main () catches, entre objetos de tipo B y A se crean en la pila en ese orden. Cuando f1 () arroja, se llaman los destructores de B y A.

#include <iostream>
using namespace std;

class A
{
    public:
       ~A() { cout << "A's dtor" << endl; }
};

class B
{
    public:
       ~B() { cout << "B's dtor" << endl; }
};

void f1()
{
    B b;
    throw (100);
}

void f()
{
    A a;
    f1();
}

int main()
{
    try
    {
        f();
    }
    catch (int num)
    {
        cout << "Caught exception: " << num << endl;
    }

    return 0;
}

La salida de este programa será

B's dtor
A's dtor

Esto se debe a que la pila de llamadas del programa cuando se lanza f1 () parece

f1()
f()
main()

Entonces, cuando se activa f1 (), la variable automática b se destruye, y luego cuando se activa f (), la variable automática a se destruye.

Espero que esto ayude, ¡feliz codificación!

DigitalEye
fuente
2

Cuando se produce una excepción y el control pasa de un bloque try a un controlador, el tiempo de ejecución de C ++ llama a los destructores para todos los objetos automáticos construidos desde el comienzo del bloque try. Este proceso se llama desbobinado de pila. Los objetos automáticos se destruyen en orden inverso a su construcción. (Los objetos automáticos son objetos locales que se declararon automáticos o se registraron, o no se declararon estáticos o externos. Un objeto automático x se elimina cada vez que el programa sale del bloque en el que se declara x).

Si se produce una excepción durante la construcción de un objeto que consiste en subobjetos o elementos de matriz, los destructores solo se invocan para aquellos subobjetos o elementos de matriz construidos con éxito antes de que se lance la excepción. Solo se llamará a un destructor para un objeto estático local si el objeto se construyó correctamente.

MK
fuente
Debería proporcionar un enlace al artículo original donde copió esta respuesta de: IBM Knowledge Base
Desenrollado de
0

En la pila Java, el desenrollado o desenrollado no es muy importante (con el recolector de basura). En muchos documentos de manejo de excepciones, vi este concepto (desbobinado de pila), en especial, esos escritores se ocupan del manejo de excepciones en C o C ++. con try catchbloques que no debemos olvidar: pila libre de todos los objetos después de bloques locales .

usuario2568330
fuente