当我“扔”东西时,它在哪里存储在内存中?


82

我知道当thrown为n时,堆栈将被“解卷”到被捕获的位置,并且在每个函数上下文中运行堆栈上的类实例的析构函数(这就是为什么您不应该从析构函数中引发异常) -您可能最终会抛出第二个)。但是我想知道发生这种情况时,我抛出的对象存储在内存中的什么位置?

是否依赖于实现?如果是这样,大多数流行的编译器是否使用特定的方法?


1
很好的问题-该标准可能未指定,因为该标准甚至没有说您必须拥有一个堆栈。实际上,也许是在catch块退出时将其分配在堆上并释放了?
Kerrek SB 2011年

@Kerrek:我认为更有可能将对象放在堆栈的“底部”。然后向上展开,记住它的位置,然后完成展开(包括为任何catch子句提供通过引用来处理该异常并将其重新添加的机会raise),销毁它。堆分配的问题是,如果尝试分配异常失败,该怎么办?您将不得不中止(就像由于空间不足而将其放到堆栈上“失败”时,堆栈溢出一样)。与Java不同,该实现不允许抛出其他异常。
史蒂夫·杰索普

@Steve:也是可能的-Kiril的文章说异常是在堆栈上分配的,辅助信息结构记录了它的地址和Deleter等。
Kerrek SB 2011年

@Kerrek:是的,受制于它必须实际引发异常的约束,而通常的问题是,用尽堆栈可以使实现规避此类职责:-)如果将其放在堆栈中,则因为引发了异常通过throw表达式的静态类型,整个函数的堆栈框架可以包括抛出该类型所需的任何空间,尽管我不知道MSVC是否这样做。
史蒂夫·杰索普

Answers:


51

是的,答案取决于编译器。

通过对我的编译器(g++ 4.4.3)进行的快速实验发现,其运行时库首先尝试malloc为异常存储内存,否则,尝试在驻留在数据段上的整个进程“紧急缓冲区”内分配空间。如果仍然无法解决问题,则会呼叫std::terminate()

看来,紧急缓冲区的主要目的是能够std::bad_alloc在进程用完堆空间后抛出(在这种情况下,malloc调用将失败)。

相关功能是__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));
}

我不知道这种方案有多典型。


malloc通常,但不一定,“在这种情况下,呼叫将失败”。
Ayxan Haqverdili

20

从此页面

需要存储以引发异常。解开堆栈时,该存储必须持久,因为它将由处理程序使用,并且必须是线程安全的。因此,异常对象存储通常将在堆中分配,尽管 实现可能会提供紧急缓冲区以支持在内存不足的情况下引发bad_alloc异常。

现在,这只是Itanium ABI,我在寻找有关GCC,Clang和MSVC的详细信息。但是,该标准未指定任何内容,这似乎是实现异常存储的显而易见的方法,因此...


1
还在寻找细节吗?有时能够接受多个答案会很不错:)
sje397 2011年

4

我不知道这是否能回答您的问题,但是这篇(C ++编译器如何实现异常处理)是一篇关于异常处理的绝妙文章:我强烈推荐它(:

简短的答案很抱歉,但是本文中的全部信息非常棒,我无法在此处选择并发布一些信息。


excpt_info在该页面中搜索,它提供了一些信息,说明MSVC是如何做到的。
史蒂夫·杰索普

1
该链接实际上描述了如何在一个好的实现中不这样做。我知道一些较旧的VC ++使用了类似的方法,但是我怀疑您会在任何现代编译器中找到它。
James Kanze 2011年

0

C ++标准通常指定语言的行为方式,但没有指定编译器应如何实现该行为。我认为这个问题属于这一类。实施此类操作的最佳方法取决于机器的具体情况-有些处理器有很多通用寄存器,有些则很少。甚至可以为异常创建带有特殊寄存器的处理器,在这种情况下,编译器应可以自由利用该功能。


-2

好吧,它不能在堆栈上,因为它将被取消缠绕,也不能在堆栈上,因为那将意味着系统可能不会抛出std::bad_alloc。除此之外,它完全取决于实现:未指定实现(必须记录在案),但未指定。(实现可以在大多数时间使用堆,只要它具有某种紧急备份,即使没有更多的内存也可以允许有限数量的异常。)


3
您的回答自相矛盾。
Lightness Races in Orbit

怎么样?它提出了对实现的约束,这些约束在标准中是隐含的。
James Kanze 2011年

一个例子:“它不能在堆上”……“一个实现可以在大多数时间使用堆”。此外,答案的整个前半部分是错误的,正如其他答案中所解释的(实际上,部分答案在您的第二部分中!)。
Lightness Races in Orbit

该标准要求std::bad_alloc有效。这意味着实现无法系统地使用堆。该标准不允许这样做。我将主张建立在标准之上。
James Kanze 2011年

FSVO的“系统地”,是正确的。但是,正如您在答案的第二部分中所述,实现确实可以将heap ^ H ^ H ^ H ^ Hfreestore与替代项结合使用。
Lightness Races in Orbit
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.