Cuando "arrojo" algo, ¿dónde se almacena en la memoria?


82

Entiendo que cuando algo es thrown, la pila se 'desenrolla' hasta el punto en que se captura, y se ejecutan los destructores de las instancias de clase en la pila en cada contexto de función (por lo que no debe lanzar una excepción de un destructor - podrías acabar tirando una segunda) ... pero me pregunto dónde en la memoria se almacena el objeto que he tirado mientras esto sucede.

¿Depende de la implementación? Si es así, ¿existe algún método en particular utilizado por los compiladores más populares?


1
Buena pregunta: probablemente no esté especificado por el estándar, ya que el estándar ni siquiera dice que debe tener una pila. Prácticamente, ¿tal vez se asigna en el montón y se libera cuando sale el bloque de captura?
Kerrek SB

@Kerrek: Creo que lo más probable es que el objeto se coloque en la pila en la parte "inferior". Luego, relájese hacia arriba, recordando dónde estaba, y una vez que haya terminado de desenrollar (incluyendo dar a las cláusulas catch la oportunidad de abordar la excepción por referencia y resolverlo raise), destrúyalo. El problema con la asignación de montón es, ¿qué debe hacer si falla su intento de asignar la excepción? Tendría que abortar (como lo haría con el desbordamiento de la pila si al ponerlo en la pila "falla" debido a la falta de espacio). A diferencia de Java, la implementación no puede lanzar una excepción diferente.
Steve Jessop

@Steve: También es posible: el artículo de Kiril dice que la excepción se asigna en la pila, y una estructura de información auxiliar registra su dirección y eliminador, etc., pero me imagino que las implementaciones son libres de hacer esto de la forma que deseen.
Kerrek SB

@Kerrek: sí, sujeto a la restricción de que realmente tiene que lanzar la excepción, y el problema habitual de que quedarse sin pila permite que la implementación eluda tales responsabilidades :-) Si lo pone en la pila, entonces porque se lanzan excepciones por el tipo estático de la expresión de lanzamiento, su marco de pila para toda la función podría incluir cualquier espacio que se necesite para lanzar ese tipo, aunque no sé si MSVC hace eso.
Steve Jessop

Respuestas:


51

Sí, la respuesta depende del compilador.

Un experimento rápido con mi compilador ( g++ 4.4.3) revela que su biblioteca de tiempo de ejecución primero intenta mallocmemorizar la excepción y, en su defecto, intenta asignar espacio dentro de un "búfer de emergencia" de todo el proceso que vive en el segmento de datos. Si eso no funciona, llama std::terminate().

Parece que el propósito principal del búfer de emergencia es poder lanzar std::bad_allocdespués de que el proceso se haya quedado sin espacio de almacenamiento dinámico (en cuyo caso la mallocllamada fallaría).

La función relevante es __cxa_allocate_exception:

extern "C" void *
__cxxabiv1::__cxa_allocate_exception(std::size_t thrown_size) throw()
{
  void *ret;

  thrown_size += sizeof (__cxa_refcounted_exception);
  ret = malloc (thrown_size);

  if (! ret)
    {
      __gnu_cxx::__scoped_lock sentry(emergency_mutex);

      bitmask_type used = emergency_used;
      unsigned int which = 0;

      if (thrown_size > EMERGENCY_OBJ_SIZE)
        goto failed;
      while (used & 1)
        {
          used >>= 1;
          if (++which >= EMERGENCY_OBJ_COUNT)
            goto failed;
        }

      emergency_used |= (bitmask_type)1 << which;
      ret = &emergency_buffer[which][0];

    failed:;

      if (!ret)
        std::terminate ();
    }

  // We have an uncaught exception as soon as we allocate memory.  This
  // yields uncaught_exception() true during the copy-constructor that
  // initializes the exception object.  See Issue 475.
  __cxa_eh_globals *globals = __cxa_get_globals ();
  globals->uncaughtExceptions += 1;

  memset (ret, 0, sizeof (__cxa_refcounted_exception));

  return (void *)((char *)ret + sizeof (__cxa_refcounted_exception));
}

No sé qué tan típico es este esquema.


"en cuyo caso la mallocllamada fallaría" bien, normalmente, pero no necesariamente.
Ayxan Haqverdili

20

De esta pagina :

Se necesita almacenamiento para lanzar excepciones. Este almacenamiento debe persistir mientras se desenrolla la pila, ya que será utilizado por el controlador y debe ser seguro para subprocesos. Por lo tanto, el almacenamiento de objetos de excepción normalmente se asignará en el montón , aunque las implementaciones pueden proporcionar un búfer de emergencia para admitir el lanzamiento de excepciones bad_alloc en condiciones de poca memoria.

Ahora, esto es solo el Itanium ABI y estoy buscando detalles específicos para GCC, Clang y MSVC. Sin embargo, el estándar no especifica nada y esta parece ser la forma obvia de implementar el almacenamiento de excepciones, así que ...


1
¿Sigues buscando detalles? A veces sería bueno poder aceptar más de una respuesta :)
sje397

4

No sé si esto responderá a su pregunta, pero este (Cómo un compilador de C ++ implementa el manejo de excepciones) es un excelente artículo sobre el manejo de excepciones:. Lo recomiendo altamente (:

Lo siento por la respuesta corta, pero toda la información del artículo es excelente, no puedo elegir y publicar información aquí.


Busque excpt_infoen esa página, le da información sobre cómo lo hace MSVC.
Steve Jessop

1
Ese enlace realmente describe cómo no hacerlo en una buena implementación. Sé que algunos VC ++ más antiguos usaban algo como esto, pero dudo que lo encuentres en algún compilador moderno.
James Kanze

0

El estándar C ++ generalmente especifica cómo se comporta el lenguaje pero no cómo el compilador debe implementar ese comportamiento. Creo que esta pregunta entra en esa categoría. La mejor manera de implementar algo como esto depende de las características específicas de la máquina: algunos procesadores tienen muchos registros de propósito general, algunos tienen muy pocos. Incluso se podría construir un procesador con un registro especial solo para excepciones, en cuyo caso el compilador debería tener la libertad de aprovechar esa característica.


-2

Bueno, no puede estar en la pila, ya que se va a deshacer, y no puede estar en el montón, ya que eso significaría que el sistema probablemente no podría lanzar std::bad_alloc. Aparte de eso, depende completamente de la implementación: no se especifica la implementación (que debe estar documentada), pero no se especifica. (Una implementación podría usar el montón la mayor parte del tiempo, siempre que tenga algún tipo de respaldo de emergencia que permita un número limitado de excepciones incluso cuando no haya más memoria).


3
Tu respuesta se contradice.
Lightness Races in Orbit

¿Cómo? Presenta las limitaciones de una implementación, que están implícitas en el estándar.
James Kanze

Un ejemplo: "no puede estar en el montón" ... "Una implementación podría usar el montón la mayor parte del tiempo". Además, toda la primera mitad de la respuesta es incorrecta, como se explica en otras respuestas (¡y, de hecho, parcialmente en la segunda parte de la suya!).
Lightness Races in Orbit

El estándar requiere std::bad_allocque funcione. Esto significa que una implementación no puede usar sistemáticamente el montón. El estándar no lo permite. Baso mis afirmaciones en el estándar.
James Kanze

FSVO de "sistemáticamente", eso es correcto. Sin embargo, como indica en la segunda parte de su respuesta, una implementación puede usar el montón ^ H ^ H ^ H ^ Hfreestore junto con alternativas.
Lightness Races in Orbit
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.