¿Cómo se implementa el tiempo de ejecución de manejo de excepciones de C ++?

84

Estoy intrigado por cómo funciona el mecanismo de manejo de excepciones de C ++. Específicamente, ¿dónde se almacena el objeto de excepción y cómo se propaga a través de varios ámbitos hasta que se detecta? ¿Está almacenado en algún área global?

Dado que esto podría ser específico del compilador, ¿alguien podría explicar esto en el contexto del conjunto de compiladores g ++?

Paul Joseph
fuente
4
Leer este artículo te ayudará
Ahmed Said
No lo sé, pero supongo que la especificación C ++ tiene una definición clara. (Aunque puedo estar equivocado)
Paul Nathan
2
No, la especificación no da una definición. Dicta el comportamiento, no la implementación. Paul, es posible que desee especificar en qué implementación está interesado.
Rob Kennedy
1
Pregunta relacionada: stackoverflow.com/questions/307610/…
CesarB
Consulte la sección 5.4 del Informe técnico sobre el rendimiento de C ++
Yogesh Arora

Respuestas:

49

Las implementaciones pueden diferir, pero hay algunas ideas básicas que se derivan de los requisitos.

El objeto de excepción en sí mismo es un objeto creado en una función, destruido en un llamador de la misma. Por lo tanto, normalmente no es posible crear el objeto en la pila. Por otro lado, muchos objetos de excepción no son muy grandes. Ergo, uno puede crear, por ejemplo, un búfer de 32 bytes y desbordarse en el montón si realmente se necesita un objeto de excepción más grande.

En cuanto a la transferencia real de control, existen dos estrategias. Uno es registrar suficiente información en la propia pila para desenrollar la pila. Esta es básicamente una lista de destructores para ejecutar y controladores de excepciones que pueden detectar la excepción. Cuando ocurre una excepción, ejecute la pila ejecutando esos destructores hasta que encuentre una captura coincidente.

La segunda estrategia mueve esta información a tablas fuera de la pila. Ahora, cuando ocurre una excepción, la pila de llamadas se usa para averiguar qué ámbitos se ingresan pero no se salen. Luego, se buscan en las tablas estáticas para determinar dónde se manejará la excepción lanzada y qué destructores se ejecutan en el medio. Esto significa que hay menos gastos generales de excepción en la pila; Las direcciones de retorno son necesarias de todos modos. Las tablas son datos adicionales, pero el compilador puede colocarlas en un segmento del programa cargado por demanda.

MSalters
fuente
4
AFAIR g ++ usa el segundo enfoque, la tabla de direcciones, presumiblemente por razones de compatibilidad con C. El compilador de Microsoft C ++ usa un enfoque combinado, ya que sus excepciones de C ++ están construidas sobre SEH (manejo estructurado de excepciones). En cada función de C ++, MSC ++ crea y registra un registro de manejo de excepciones SEH, que apunta a una tabla con rangos de direcciones para bloques try-catch y destructores en esta función en particular. lanza paquetes de una excepción de C ++ como una excepción de SEH y llama a RaiseException (), luego SEH devuelve el control a la rutina del controlador específico de C ++.
Anton Tykhyy
1
@Anton: sí, usa el enfoque de tabla de direcciones. Vea mi respuesta a otra pregunta en stackoverflow.com/questions/307610/… para obtener más detalles.
CesarB
Gracias por la respuesta. Puede ver cómo los puristas de C pueden estar aterrorizados por C ++ y sus excepciones. La idea de que un simple intento / captura puede crear involuntariamente una cantidad de objetos de pila en tiempo de ejecución o inflar su programa con tablas adicionales es la razón por la que los sistemas integrados a menudo los evitan.
Speedplane
@speedplane: No, eso se debe más a una falta de comprensión. El manejo de errores nunca es gratuito. C simplemente te obliga a escribirlo tú mismo. Y todos sabemos cuántos programas C carecen de una free()o una fclose()en alguna ruta de código que se usa raramente.
MSalters
@MSalters No estoy en desacuerdo, es casi por completo una falta de comprensión. Los ingenieros a menudo no entienden cómo funcionan las excepciones y cómo afectarán las excepciones a su código, lo que luego, justificadamente, genera dudas al usar las excepciones. Si la implementación del manejo de excepciones se comunicara con mayor claridad (y no pareciera magia), muchos dudarían menos en usarlos.
Avión de velocidad
20

Esto se define en 15.1 Lanzar una excepción del estándar.

El lanzamiento crea un objeto temporal.
No se especifica cómo se asigna la memoria para este objeto temporal.

Después de la creación del objeto temporal, el control se pasa al controlador más cercano en la pila de llamadas. desenrollando la pila entre el lanzamiento y el punto de captura. A medida que se desenrolla la pila, las variables de la pila se destruyen en orden inverso al de creación.

A menos que se vuelva a lanzar la excepción, el temporal se destruye al final del controlador donde se detectó.

Nota: Si captura por referencia, la referencia se referirá al temporal. Si captura por valor, el objeto temporal se copia en el valor (y por lo tanto requiere un constructor de copia).

Consejos de S.Meyers (captura por referencia constante).

try
{
    // do stuff
}
catch(MyException const& x)
{
}
catch(std::exception const& x)
{
}
Martin York
fuente
3
Algo más que no se especifica es cómo el programa desenrolla la pila y cómo el programa sabe dónde está el "controlador más cercano". Estoy bastante seguro de que Borland tiene una patente sobre un método para implementar eso.
Rob Kennedy
Siempre que los objetos se destruyan en orden inverso al de su creación, los detalles de la implementación no son importantes a menos que sea un ingeniero de compilación.
Martin York
1
Votó en contra: a) "Scott Meyers", no "S. Myers"; b) cita falsa: "C ++ efectivo": "Ítem 13: Detectar excepciones por referencia ". Esto permitirá ajustar / agregar información al objeto de excepción.
Sebastian Mach
3
@phresnel: No olvide el artículo 21: "Utilice const siempre que sea posible". No hay buenos argumentos para modificar una excepción. Debería estar a) "arreglando y descartando", b) volviendo a lanzar o c) generando una nueva excepción.
Martin York
1
@phresnel: Sí, tienes tus razones (no estoy de acuerdo con tu lógica), yo tengo las mías y, aunque no afirmaré haber hablado con ellos sobre este tema específico ni conocer sus mentes (Meyers, Alexandrescu y Sutter), creo mi interpretación es válida. Pero si se encuentra en el área de Seattle, puede hablar con los tres, ya que son asistentes habituales del Grupo de usuarios de North West C ++ (Meyers con menos frecuencia que los demás).
Martin York
13

Puede echar un vistazo aquí para obtener una explicación detallada.

También puede ser útil echar un vistazo a un truco utilizado en C simple para implementar algún tipo básico de manejo de excepciones. Esto implica usar setjmp () y longjmp () de la siguiente manera: el primero guarda la pila para marcar el manejador de excepciones (como "catch"), mientras que el segundo se usa para "arrojar" un valor. El valor "arrojado" se ve como si hubiera sido devuelto por una función llamada. El "bloque de prueba" finaliza cuando se llama de nuevo a setjmp () o cuando la función regresa.

Eduard - Gabriel Munteanu
fuente