1可以返回调用2返回吗?

时间:2013-02-21 11:29:55

标签: c logic theory

这主要是理论上的问题,因为它没有多大用处。

考虑这种情况:

function a() {
  return;
}

function b(){
  a();
}

你能从一个孩子调用父函数中的返回吗?

现在在这种情况下,你可以简单地做return a();,但这会发生,但是假设你对没有预先生成回报感兴趣。

我知道在将它翻译成汇编时这没有意义,在这种情况下你可以使用goto,但我们都知道它有多危险。

我的逻辑说如果你可以从一个将在父节点上调用continue的子循环中预先形成一个continue,这应该是相同的,但是循环不会影响堆栈,所以有意义的是继续工作

我想知道是否有任何方法可以使用事件或oop方法处理这种情况?

3 个答案:

答案 0 :(得分:1)

传统的C解决方案是longjmp函数,它可以在堆栈中以任意方式跳转。请注意,总有一些人明智地不使用它,并且很大程度上取决于异常处理。

答案 1 :(得分:0)

您可以使用宏而不是函数。

#define a() ... return ...

用例可能是在发布版本中没有完全删除的断言,但是中止了一个函数:

#define REQUIRE(x)  do { assert((x)); if (!(x)) return; } while (0)

你也可以在汇编程序中破解一些东西来获取调用函数的堆栈框架并使用那里的返回地址:

void return2(){
  void * frame;
  #if (defined(x86_64) || defined(__x86_64__))
  __asm__(
    "pop %rbp\n"        //skip over stack frame of return2
    "mov %rsp, %rbp\n"
    "pop %rax\n"
    "pop %rbp\n"        //skip over stack frame of caller
    "mov %rsp, %rbp\n"
    "pop %rax\n"
  );

  #else
  #error only implmented for amd64...
  #endif
}

然后

void a(){
    printf("a 0\n");
    return2();
    printf("a 1\n");
}

void b(){
    printf("b 0\n");
    a();
    printf("b 1\n");
}

int main(int argc, char* argv[])
{
    printf("main 0\n");
    b();
    printf("main 1\n");
    return 0;
}

打印

main 0
b 0
a 0
main 1

这是所有人中最危险的解决方案(如果gcc在更高的优化级别内联某些内容或删除堆栈帧,则会失败。但您可以添加检查指令的检查,如果它们已经过优化)

答案 2 :(得分:0)

如果您使用MSVC在Windows上,则可以使用异常(Strucured Exception Handling,SEH)来实现类似的功能。在其他平台上,你可以使用setjmp / longjmp,正如thiton所说。

使用SEH,您可以执行以下操作(由于我没有准备好Visual Studio的Windows,因此没有尝试过):

#include "stdio.h"
#include "Windows.h"

void func_b() {
    printf("In func_b()\n");
    // return safely to main
    RaiseException(1, EXCEPTION_NONCONTINUABLE, 0, NULL);
    printf("At end of func_b()\n");
}

void func_a() {
    printf("In func_a()\n");
    func_b();
    printf("At end of func_a()\n");
}

void main() {
    printf("In func_a()\n");
    __try {
        func_a();
    }
    __except (GetExceptionCode() == 1) {
        printf ("Early return to main()\n");
    }
    printf("At end of main()\n");
}

RaiseException调用导致控件上升到堆栈,直到捕获到异常,在main()中。这不是真正的“返回^ 2”,因为调用函数(main)必须一直播放。一般来说,你还需要合作你想要跳过的函数(这里是func_a),因为它们可能会做东西并且需要清理。只是说“从func_b返回,并停止func_a正在做什么并从那里返回”也可能非常危险。但是,如果使用异常,则可以将代码包装在try / finally子句中的func_a中:

FILE* f;
__try {
    f = fopen("file.txt", "r");
    func_b();
}
__finally {
    fclose(f);
    printf("Cleanup for func_a()\n");
}

当然,在本机支持异常(C ++,Python,Java,...)的语言中,这当然要好得多,而且不要只是将它作为专有扩展来实现。

请注意,有些人认为对控制流使用异常是不好的做法,并且应该为真正异常的事件(如IO错误)保留异常。有一些案例确实有意义(例如,您正在解析某些内容,并深入了解堆栈,您必须以不同方式回放和解析某些内容,您可以抛出自定义异常)。一般来说,我会说尽量不要过于聪明,尽量不要做一些让你的程序读者感到困惑的事情。当看起来你需要使用这样的技巧时,通常会有一种方法来重构程序,以一种对语言来说很自然的方式来执行它。或者也许您使用的语言不是解决问题的好选择。