【问题标题】:Possibilities to quit a function退出功能的可能性
【发布时间】:2013-07-13 10:28:13
【问题描述】:

我想知道 C/C++ 中的一般主题。 假设我们正在执行一个调用函数 B() 的函数 A(),我们能否确定在 A() 中对 B() 的调用总是会在调用本身“之后”返回。

在一个更一般的问题中,退出函数的可能性有哪些?

C 关键字是(维基百科):auto、break、case、char、const (C89)、continue、default、do、double、else、enum (C89)、extern、float、for、goto、if、inline (C99)、int、long、register、restrict (C99)、return、short、signed (C89)、sizeof、static、struct、switch、typedef、union、unsigned、void (C89)、volatile (C89)、while、 _Bool (C99)、_Complex (C99)、_Imaginary (C99)。

据我所知,这个话题中有趣的是:

  • break/continue :在循环或开关中使用(正如我在尝试后被 GCC 告知的那样),它们无法退出函数。
  • goto :标签的范围受函数限制,因此 goto 无法退出函数
  • return :可以退出函数但总是返回到调用后的指令。我们对这个很安全。
  • exit()/abort() 函数将结束应用程序。我们不会返回调用点,但是..我们根本不会返回。

我认为这是针对 C 语言的。您认为还有另一种方法可以退出函数而不返回调用点吗?


在 C++ 中,异常显然不会返回调用点。他们要么去一个catch块,要么到达调用函数,寻找一个catch块。

据我所知,这将是唯一的情况。

感谢您帮助我 =)

【问题讨论】:

  • 你只关心标准C吗?或者你对setjmp(3)longjmp(3) 感兴趣吗?
  • -1 没有像 C/C++ 这样的语言
  • @BartekBanachewicz:考虑到他们共同的传统,以及许多较旧的 C 构造在 C++ 中工作的事实,这似乎是一个狡辩。
  • 听起来你真正的问题可能集中在“可达性”和/或静态分析/证明关于代码的陈述上。你在写这样的分析工具吗?
  • @CarlNorum: setjmplongjmp 是标准 C。它们也是标准 C++,但或多或​​少在该语言中不可用。

标签: c++ c function exception return


【解决方案1】:

查找setjmp()longjmp()setjmp() 在调用点记录一定数量的本地状态。 longjmp() 将返回到您调用setjmp() 的位置,可能会跨越多个功能级别。

您可以将它用于 C 中原始形式的异常处理。它非常、非常少使用。

【讨论】:

    【解决方案2】:

    你可以退出一个函数

    • 返回
    • 在其正文的末尾静默(没有在 void function() 中明确返回)
    • 一个例外
    • longjmp()
    • 内联汇编跳转到某个地址

    【讨论】:

    • 未定义的行为。既然它可以做任何事情,它可以让你离开一个功能。 (最重要的是,这里未定义的行为可以修改堆栈上的返回地址。)
    • 所以基本上,它只能使用 longjump() 或 setjump() 或在 C 中使用内联汇编跳转发生,在 C++ 中例外?
    • 它可能发生在我上面描述的所有五种情况下,加上詹姆斯描述的那一种,尽管我没有看到后者在现实中发生。说它仍然是一个有效的可能性,只是不太可能。
    • 嗯,詹姆斯举了一个很好的例子:堆栈粉碎。这是一种广泛使用的用于提升权限的攻击媒介。它依赖于超出数组正常范围时产生的未定义行为,并使用它来接管任务。您还可以争辩说,崩溃是另一种形式的“未定义的行为,导致您无法从函数中按预期返回。”
    【解决方案3】:

    我记得当我需要一种方法可以在不退出的情况下退出一个大程序时创建了这个函数

    int     my_exit()
    {
      pid_t pid;
      int   i;
    
      pid = getpid();
      i = kill(pid, SIGQUIT);
      if (i == -1)
        return (-1);
      return (0);
    }
    

    【讨论】:

    • 像这样杀死应用程序可能会导致很多问题 - 例如文件可能无法写入等。
    【解决方案4】:

    '假设我们正在执行一个调用函数 B() 的函数 A(),我们能否确定在 A() 中对 B() 的调用总是会在调用本身“之后”返回。不,因为:

    “B”可能会引发“A”中未捕获的异常。

    “B”可能包含无限循环。

    'B' 可能会进行一个永远不会返回的阻塞 OS 调用。

    【讨论】:

      【解决方案5】:

      在标准 C(使用setjmp/longjmp)和 C++(使用异常)中,可以有效地返回更接近 CFG 根的标记点。实际上,一个函数可能永远不会返回,但如果它确实返回,它将返回到调用之后的点。

      然而,setjmp 机制的低级特性实际上使得实现协程成为可能(尽管是以不可移植的方式)。 Posix 试图通过强制 makecontext 和朋友来改善这种情况,这允许显式堆栈交换,但这些功能在 Posix.1-2001 中被弃用并从 Posix.1-2008 中删除,理由是可移植性问题,并建议改为使用线程。尽管如此,还是有许多协程库在使用这些特性,让 C 程序员可以享受协程的灵活性。

      在协程控制流中,虽然(co)调用之后的执行路径可能是曲折的,但函数调用仍然是永远不会返回或最终返回到紧随其后的点的情况。然而,C 库工具的低级特性使得实现更复杂的控制流成为可能,例如,给定的(共同)调用可能会返回多次。 (我从未在生产代码中看到过这种特殊的异常情况,但我不能声称看到了世界上所有生产代码的一小部分:)。

      对 C 的 gcc 扩展允许使用“标签值”,即指向代码中标签的指针。这些是真实值(void * 类型),因此它们可以作为参数传递给函数。 (gcc 手册警告不要这样做。)通过一些逆向工程,可能可以编写一个函数,该函数接受一个或多个标签参数并将其中一个作为返回点。这显然是对该功能的滥用,可能既不便携也不面向未来,并且几乎肯定会破坏现有的任何编码标准。

      与实际上是核心语言一部分的 C++ 异常不同,C 库工具的有趣之处在于它们实际上是函数;在 C 中,与许多编程语言一样,函数可以通过函数指针间接调用,因此可能无法通过静态分析轻松计算在给定调用点调用哪个函数。所以,至少在理论上,我会说所有的赌注都没有了。但在实践中,一个安全的假设可能是函数调用最终会返回到紧随其后的点,或者返回到调用堆栈下方的某个位置,可能是操作系统环境。

      【讨论】:

        猜你喜欢
        • 2011-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-18
        • 2011-04-17
        • 2015-08-24
        • 2021-01-30
        • 2012-07-26
        相关资源
        最近更新 更多