2010-03-08 12 views
21

En el siguiente código, la variable basada en pila 'ex' se lanza y atrapa en una función más allá del alcance en el que se declaró ex. Esto me parece un poco extraño, ya que las variables basadas en la pila (AFAIK) no se pueden usar fuera del alcance en el que se declararon (la pila se desenrolla).¿Cómo se interceptan las excepciones asignadas en la pila más allá de su alcance?

void f() { 
    SomeKindOfException ex(...); 
    throw ex; 
} 

void g() { 
    try { 
     f(); 
    } catch (SomeKindOfException& ex) { 
     //Handling code... 
    } 
} 

He añadido una sentencia print al destructor de SomeKindOfException y se nota que el ex se destruye una vez que se sale del ámbito de f(), pero luego se ha atrapado en g() y destruido de nuevo una vez que sale de alcance allí también.

¿Algún ayuda?

+0

¿Es correcto usar una referencia aquí? 'catch (SomeKindOfException & ex)' Creo que esto es peligroso, ya que no llama al constructor de copia y se obtiene acceso a un área de memoria que pertenece a la pila dealocated de f()! Supongo que esto debería ser correcto en su lugar: 'catch (SomeKindOfException ex)' – Dacav

+0

Es correcto (y aún mejor - ver http://www.parashift.com/c++faq-lite/exceptions.html section 17.7) para atrapar por referencia. Como indican las respuestas a mi pregunta, la excepción que se captura no es el objeto basado en la pila, sino una copia que reside en un lugar diferente que puede sobrevivir al desenrollado de la pila, por lo tanto, no existe tal riesgo. –

+0

Bueno, corrí un experimento ayer por la tarde y sí, es mucho mejor usar las referencias. Mira esto: http: // pastebin.com/8YQuNAux Si lo ejecuta, puede observar que la excepción se asigna dinámicamente (en el sentido de 'nuevo') en cada captura sin referencia: si utiliza las referencias, en cambio, se asigna solo una vez y se destruye automáticamente cuando el alcance está terminado. También creo que este comportamiento depende estrictamente del compilador. – Dacav

Respuesta

18

El objeto de excepción se copia en una ubicación especial para sobrevivir al desenrollado de la pila. La razón por la que ve dos destrucciones es porque cuando sale de f() la excepción original se destruye y cuando sale g() la copia se destruye.

+0

Oh, esto es lo que esperaba intuitivamente, me alegro de que corresponda a la verdad. También se debe llamar al constructor de copia, entonces, ¿cómo lidiar con esto adecuadamente? Supongamos que su clase de excepción tiene una variable de instancia que contiene el mensaje de error: ¡en cada 'tirada 'subsiguiente tiene una nueva asignación de copia de esa variable! Hablando de las mejores prácticas, ¿qué piensas acerca de garantizar una copia superficial solo para todas las excepciones? – Dacav

8

El objeto se copia en un objeto de excepción que sobrevive al desenrollado de la pila. De dónde proviene la memoria para ese objeto no se especifica. Para objetos grandes, probablemente será malloc 'ed, y para objetos más pequeños, la implementación podría tener un búfer preasignado (podría imaginar que esto podría usarse para una excepción bad_alloc).

La referencia ex luego se une a ese objeto excepción, que es una temporal (no tiene nombre).

+1

Gracias por aclarar la ubicación de la memoria de la excepción, edité mi comentario anterior para eliminar esa pregunta. –

1

Como la especificación establece explícitamente, que se crea un objeto temporal en lugar del operando throw.

8

C++ estándar 15.1/4:

La memoria para la copia temporal de la excepción de ser lanzado se asigna de una manera no especificada, excepto como se indica en 3.7.3.1. El temporal persiste siempre que se ejecute un controlador para esa excepción . En particular, si un controlador sale ejecutando un lanzamiento; instrucción, que pasa el control a otro controlador para la misma excepción, por lo que el temporal permanece. Cuando el último controlador que se ejecuta para la excepción se cierra por cualquier medio que no sea throw; el objeto temporal se destruye y la implementación puede desasignar la memoria para el objeto temporal; cualquier desasignación se realiza de una manera no especificada. La destrucción se produce inmediatamente después de la destrucción del objeto declarado en la declaración de excepción en el controlador.

No hay nada más que decir.

5

Cuando arroja ex, se copia en una ubicación de memoria especial utilizada para objetos de excepción lanzados. Dicha copia se lleva a cabo por el constructor de copia normal.

Esto se puede ver fácilmente desde este ejemplo:

#include <iostream> 

void ThrowIt(); 

class TestException 
{ 
    public: 
    TestException() 
    { 
     std::cerr<<this<<" - inside default constructor"<<std::endl; 
    } 

    TestException(const TestException & Right) 
    { 
     (void)Right; 
     std::cerr<<this<<" - inside copy constructor"<<std::endl; 
    } 

    ~TestException() 
    { 
     std::cerr<<this<<" - inside destructor"<<std::endl;  
    } 
}; 

int main() 
{ 
    try 
    { 
     ThrowIt(); 
    } 
    catch(TestException & ex) 
    { 
     std::cout<<"Caught exception ("<<&ex<<")"<<std::endl; 
    } 
    return 0; 
} 

void ThrowIt() 
{ 
    TestException ex; 
    throw ex; 
} 

Salida de ejemplo:

[email protected]:~/cpp/test$ g++ -O3 -Wall -Wextra -ansi -pedantic ExceptionStack.cpp -o ExceptionStack.x 
[email protected]:~/cpp/test$ ./ExceptionStack.x 
0xbf8e202f - inside default constructor 
0x9ec0068 - inside copy constructor 
0xbf8e202f - inside destructor 
Caught exception (0x9ec0068) 
0x9ec0068 - inside destructor 

Por cierto, se puede ver aquí que la posición de memoria utilizada para el objeto lanzado (0x09ec0068) está definitivamente lejos de la del objeto original (0xbf8e202f): la pila, como de costumbre, tiene direcciones altas, mientras que la memoria utilizada para el objeto arrojado está bastante abajo en el espacio de direcciones virtuales. Aún así, este es un detalle de implementación, ya que, como se señaló en otras respuestas, el estándar no dice nada sobre dónde debería estar la memoria para el objeto arrojado y cómo debería asignarse.

+0

Usando VC++, la ubicación de la memoria del objeto arrojado es muy similar a la del objeto original (es decir, en la pila). Esto realmente demuestra el punto de Johannes y Kirill, que la ubicación de la memoria no está especificada. –

+0

Tienes razón, esto es realmente un detalle de implementación. Voy a aclarar la respuesta. –

3

Además de lo que dice la norma en 15.1/4 ("Manejo de excepciones/Lanzar una excepción") - que la memoria para la copia temporal de la excepción lanzada está asignada de una manera no especificada - un par de otros bits de curiosidades sobre cómo se asigna el objeto de excepción son:

  • 3.7.3.1/4 ("funciones de asignación") de la norma indica que el objeto de excepción no puede ser asignada por una expresión new o una llamada a una 'función de asignación global' (es decir, una sustitución operator new()). Tenga en cuenta que malloc() no es una 'función de asignación global' como lo define el estándar, por lo que malloc() es definitivamente una opción para asignar el objeto de excepción.

  • "Cuando se produce una excepción, el objeto de excepción se crea y un colocan generalmente en una especie de pila de datos de excepción" (Stanley Lippman, "Dentro de la C++ Object Model" - 7.2 El manejo de excepciones)

  • De "The C++ Programming Language, 3rd Edition" de Stroustrup: "Se requiere una implementación C++ para tener suficiente memoria libre para poder lanzar bad_alloc en caso de agotamiento de la memoria. Sin embargo, es posible que arrojando alguna otra excepción causará agotamiento de la memoria. "(14.4.5 Agotamiento de recursos); y, "La implementación puede aplicar una amplia variedad de estrategias para almacenar y transmitir excepciones. Sin embargo, se garantiza que hay memoria suficiente para permitir que new arroje la excepción de falta de memoria estándar, bad_alloc" (14.3 excepciones de captura) .

Tenga en cuenta que las citas de Stroustrup son pre-estándar. Me parece interesante que el estándar no parezca garantizar que Stroustrup haya pensado lo suficiente como para mencionarlo dos veces.

Cuestiones relacionadas