当我“扔”某事时,它存储在内存中的什么位置?

时间:2011-07-07 14:04:14

标签: c++ exception exception-handling

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

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

5 个答案:

答案 0 :(得分:51)

是的,答案是编译器相关的。

我的编译器(g++ 4.4.3)的快速实验表明,它的运行时库首先尝试malloc内存以查找异常,如果失败,则尝试在进程范围内的“紧急缓冲区”中分配空间“它存在于数据部分。如果不能解决问题,则会调用std::terminate()

紧急缓冲区的主要目的似乎是在进程耗尽堆空间后抛出std::bad_alloc(在这种情况下malloc调用将失败)。 / p>

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

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

答案 1 :(得分:20)

来自this page

  

存在异常需要存储   抛出。此存储必须保留   堆栈正在解开,因为它   将由处理程序使用,并且必须   是线程安全的。 异常对象   因此通常存储   尽管如此,在堆中分配   实现可能会提供   紧急缓冲区支持投掷   低内存下的bad_alloc异常   条件。

现在,这只是Itanium ABI,我正在寻找GCC,Clang和MSVC的具体细节。但是,标准没有指定任何内容,这似乎是实现异常存储的明显方法,所以......

答案 2 :(得分:4)

我不知道这是否会回答你的问题,但this (How a C++ compiler implements exception handling)是关于异常处理的优秀文章:。我强烈推荐它(:

很抱歉答案很简短,但文章中的所有信息都很棒,我不能在这里挑选和发布一些信息。

答案 3 :(得分:0)

C ++标准通常指定语言的行为方式,但不指定编译器应如何实现该行为。我认为这个问题属于这一类。实现这样的事情的最佳方式取决于机器的细节 - 一些处理器有很多通用寄存器,有些只有很少。甚至可以使用特殊寄存器构建处理器以用于异常,在这种情况下,编译器应该可以自由地利用该功能。

答案 4 :(得分:-2)

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