【问题标题】:Will C++ exceptions safely propagate through C code?C++ 异常会安全地通过 C 代码传播吗?
【发布时间】:2011-01-07 06:24:57
【问题描述】:

我有一个 C++ 应用程序,它调用 SQLite(SQLite 在 C 中)sqlite3_exec(),它又可以调用我在 C++ 中实现的回调函数。 SQLite 被编译成静态库。

如果异常逃脱了我的回调,它是否会通过 SQLite 的 C 代码安全地传播到调用 sqlite3_exec() 的 C++ 代码?

【问题讨论】:

  • 回调应该是C函数吧?
  • @Neil Butterworth:我猜 SQLite 不在乎——这完全取决于异常的传播方式。
  • 你用什么编译的? g++ 将采用 *.c 文件。但会将它们编译为 C++ 文件。

标签: c++ c exception visual-c++ exception-handling


【解决方案1】:

没有一个答案提到使用 -fexceptions 构建您的 C 库?加入 -fno-omit-framepointer 就可以了。这甚至适用于共享库(用 C 编写),只要您从主程序中抛出,然后再次在主程序中捕获。

【讨论】:

    【解决方案2】:

    这是一个非常有趣的问题,出于好奇,我自己测试了它。在我的 OS X w/gcc 4.2.1 上,答案是肯定的。它完美地工作。我认为真正的测试是对 C++ 使用 gcc,对 C 部分使用其他一些(MSVC?、LLVM?),看看它是否仍然有效。

    我的代码:

    callb.h:

    #ifdef __cplusplus
    extern "C" {
    #endif
    
    typedef void (*t_callb)();
    void cfun(t_callb fn);
    
    #ifdef __cplusplus
    }
    #endif
    

    callb.c:

    #include "callb.h"
    
    void cfun(t_callb fn) {
     fn();
    }
    

    main.cpp:

    #include <iostream>
    #include <string>
    #include "callb.h"
    
    void myfn() {
      std::string s( "My Callb Except" );
      throw s;
    }
    
    int main() {
      try {
        cfun(myfn); 
      }
      catch(std::string s) {
        std::cout << "Caught: " << s << std::endl;
      }
      return 0;
    }
    

    【讨论】:

    • 仅使用测试不能回答这个问题——尤其是这样一个简单的函数。我怀疑如果它使用尾调用优化,那么当抛出异常时,堆栈上甚至没有 c-function 的迹象。
    【解决方案3】:

    SQLite 期望您在错误时返回 SQLITE_ABORT,在没有错误时返回 0。因此,您应该将所有 C++ 回调包装在 try catch 中。然后在 catch 中返回一个 SQLite SQLITE_ABORT 错误码,否则为零。

    如果您绕过通过 SQLite 返回,则会出现问题,因为在您从回调返回后,它不会释放/完成它所做的任何代码。这将导致数不清的问题,其中一些可能非常晦涩。

    【讨论】:

      【解决方案4】:

      我的猜测是这取决于编译器。但是,在回调中抛出异常将是一个非常糟糕的主意。要么它完全不起作用,要么 SQLite 库中的 C 代码将无法处理它。考虑一下这是否是 SQLite 中的一些代码:

      {
        char * p = malloc( 1000 );
        ...
        call_the_callback();  // might throw an exception
        ...
        free( p );
      }
      

      如果异常“有效”,C 代码将无法捕获它,并且 p 将永远不会被释放。当然,图书馆可能分配的任何其他资源也是如此。

      【讨论】:

      • 出于同样的原因,我什至不会从 C++ 库调用的回调代码中抛出异常,除非其文档明确允许这种行为。有很多(糟糕的)代码是在没有例外的情况下编写的。
      • 然后回到全局变量来存储确切的错误原因:)
      • @Matthieu 好吧,它不一定是全局的——也许是静态类?
      • @Neil:可能是的,但是类静态类似于单例,它本身类似于全局变量。我总是对共享状态保持警惕!不过,我不认为在这种情况下可以做得更好。
      【解决方案5】:

      如果您从 sqlite 调用的回调来自您调用 sqlite3_exec() 的同一线程,则调用堆栈中某处的 throw 应该被更高级别的 catch 捕获。

      自己测试应该很简单,不是吗?

      [编辑] 在自己深入挖掘之后,我发现 C++ 标准对于从 c 调用的 c++ 函数在抛出异常时应该具有的行为有些模糊。

      您绝对应该使用 API 所期望的错误处理机制。否则,您的 API 本身将处于未定义状态,任何进一步的调用都可能失败/崩溃。

      【讨论】:

      • 首先,它可能是依赖于实现或未定义的行为。当被问到这个问题时,我也完全忘记了 C 代码的异常安全性。所以仅仅写一个“Hello world”并运行它是不够的。
      【解决方案6】:

      已经有一个回调协议用于中止 API 调用。来自the docs

      如果 sqlite3_exec() 回调返回 非零,sqlite3_exec() 例程 返回 SQLITE_ABORT 而不调用 再次回调并且没有运行 任何后续 SQL 语句。

      我强烈建议您使用它而不是异常。

      【讨论】:

      • 他可以在他的 C++ 代码中使用异常。只需确保回调中的 C++ 代码被包装在 try catch 中,这样它们就不会比包装函数更进一步回滚,例如......不会进入 SQLite 或更高版本。看我的回答。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-22
      • 2011-09-19
      相关资源
      最近更新 更多