【问题标题】:Does this code contain a hidden bug?此代码是否包含隐藏的错误?
【发布时间】:2011-10-29 12:14:39
【问题描述】:

以下代码:

  • 使用 gcc 版本 4.4.5 (Ubuntu/Linaro 4.4.4-14ubuntu5/32bits) 编译时运行良好
  • 使用 MSVC10 (Win7/32bits) 编译时运行良好
  • 使用 gcc 版本 4.5.2(Win7/32 位上的 MinGW)运行时崩溃

ma​​in.cpp

# include <iostream>
# include <csetjmp>
# include <stdexcept>

using namespace std ;

void do_work(jmp_buf context)
{
    try
    {
        throw runtime_error("Ouch !") ;
    }
    catch(exception & e)
    {
    }

    longjmp(context, -1) ;                        //BP1
}

int main(int, char *[])
{
    jmp_buf context ;

    try
    {
        if( setjmp(context) != 0 )
        {
            throw runtime_error("Oops !") ;       //BP2
        }

        do_work(context) ;
    }
    catch(exception & e)
    {
        cout << "Caught an exception saying : " << e.what() << endl ;
    }
}

我尝试调试它,但程序的行为很奇怪。有时我可以通过第一个断点 (BP1),然后在 BP2 崩溃,有时控制永远不会到达 BP1,就像程序陷入无限循环一样。我不能用我的调试技能说更多。

这段代码是我能得到的最少的代码,它展示了 MinGW 4.5 的奇怪行为。我还注意到:

  • 如果我用其内容替换do_work 函数调用,程序运行良好。
  • 如果我删除 do_work 中的 try{ ... } catch(...){ } 块,程序运行正常。
  • 优化标志无效(总是崩溃)。

我知道 C++ 代码中的setjmp/longjmp 问题,但我不得不使用它来与一些遗留 C 代码交互。

我的问题:

  • 这是一个错误/错误/错误的代码吗?还是 MinGW 4.5 对代码处理不当? (责备该工具是苛刻和冒昧的,但我怀疑其中的某些设置)。

感谢您的建议。

如有必要,请重新标记。

【问题讨论】:

    标签: c++ mingw setjmp


    【解决方案1】:

    Unix 上的 longjmp(3) 手册页说:

    longjmp() 例程不能在例程之后调用 调用 setjmp() 例程返回

    我认为它解释了您对“有时控制永远不会到达 BP1”的担忧。 我不认为“运行良好”是可靠的判断。我宁愿期望它随机运行良好并且通常会弄乱堆栈。

    在将 longjmp/setjmp 与 C++ 异常混合使用时应考虑一些明确的建议,以避免崩溃和未定义的行为:

    • 不要在 C++ 程序中使用 setjmp/longjmp。
    • 如果您在可能发生异常的程序中使用 setjmp/longjmp 函数,只要它们不交互,您就是安全的。
    • 永远不要 longjmp 进出 try 子句和 catch 子句。
    • 永远不要在自动对象的初始化点进行 longjmp。
    • 永远不要使用 longjmp 破坏自动对象的点,尤其是在析构函数非常重要的情况下。
    • 切勿从信号处理程序中抛出。
    • 切勿从嵌套信号处理程序调用 longjmp。
    • 只要在 X 处引发异常并在 X 处捕获的异常具有相同的效果,longjmp 从位置 X 到位置 Y 的行为就保持可预测和有效。
    • 如果您将 setjmp/longjmp 与异常混合使用,请不要期望可移植性。
    • 请参阅您正在使用的文档编译器中的相关详细信息。例如,如果您使用 Visual C++,请阅读 Use setjmp/longjmp

    问题提到在用 C++ 编写的程序中处理遗留 C 代码。在审查 Boost 库之一期间,对 jpeg 库中的 sjlj 问题进行了有趣的讨论。讨论很长,但这里是essence with recommended options

    【讨论】:

    • main()longjmp() 发生时尚未返回
    • 谢谢!但除了第 1 点之外,我看不出我在哪里错过了要点(不是双关语)。
    • @overcoder 是的,没错。我想在我的回答中介绍一些背景信息。关于 MinGW,您确定您使用的是启用了 longjmp 异常的 MinGW?运行 g++ -v 来检查它。我不是 MinGW 用户,但 AFAIK MinGW 区分了 C++ EH 与 setjmp/longjmp 的构建。请注意,像 pthread 这样的 MinGW 就绪库为 MingW 提供单独的构建(例如,用于 C++ EH 的 pthreadGCE.dll 和用于 sjlj EH 的 pthreadGC.dll)。只是这两种气体不会混合。
    • @mloskot:让我换句话说:如果我在 C++ 程序中使用调用 setjmp/longjmp 的 C 库,并且如果将整个 main 包装在 try-catch 块中,我应该期待未定义的行为吗?
    • @mloskot:对我来说,gcc -v 显示了--enable-sjlj-exceptions 标志。
    【解决方案2】:

    C++ 只对longjmp() 的使用做了一个额外的限制:

    如果任何自动对象将被抛出的异常破坏,将控制权转移到程序中的另一个(目标)点,则在将控制权转移到同一(目的地)点具有未定义的行为。 (18.7)

    您似乎意识到了这一点,而您的程序却没有这样做。我投票编译器缺陷。

    【讨论】:

      【解决方案3】:

      longjmpsetjmp 是 c 函数,使用 C++ 异常处理语义和对象销毁语义调用它们是未定义的行为,这意味着它是否有效取决于实现(您的测试表明这一点,SEH 堆栈展开语义会破坏事物,具体取决于所使用的类型,iirc 您的工作对象正在使用 dwarf2)

      【讨论】:

      • C++ 标准在 C.2.2(标准 C 库)中说:The C++ Standard library provides 54 standard macros from the C library 这包括setjmp。此外,调用longjmp 后不会留下任何具有自动存储功能的变量。此外,正如我所注意到的,如果我删除 do_work 函数调用并将其替换为函数内容,则程序可以工作。如果 setjmp/longjmp 机制干扰了 C++ 异常,那么事情永远不会起作用。
      • @Necrolis:我没有需要正确销毁的基于框架的对象。而 MSVC10 在满足这一要求时会生成正确的程序。
      • @overcoder 你怎么知道它产生了正确的程序。它可以通过通量是正确的,并且实际上会产生未指定或未定义的行为。
      • @mloskot:我必须承认我依赖这些工具。我不能肯定地说代码是正确的,但是,据我尝试,在 C++ 中使用setjmp/longjump 并注意必要的谨慎从未导致我崩溃。所以我在问:setjmp/longjump 在 C++ 中有限制使用还是未定义的行为
      • 它可以在一个单一的限制下使用——不要跳过自动变量的破坏。但是,这种限制在实践中很难保证。这意味着您需要控制 setjmp 和 longjmp 之间的整个调用链。避免在一些程式化的例子中除外。我唯一的用途是在一些调试宏中。
      猜你喜欢
      • 2017-11-08
      • 1970-01-01
      • 2018-11-25
      • 1970-01-01
      • 2022-11-24
      • 1970-01-01
      • 2013-12-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多