4

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

考虑这种情况:

function a() {
  return;
}

function b(){
  a();
}

您可以从子函数调用父函数中的返回吗?

现在在这种情况下,您可以简单地做return a();,这会发生,但是假设您对不执行退货感兴趣。

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

我的逻辑是,如果您可以continue从将在父级上调用 continue 的子循环执行 a,这应该是相同的,但循环不会影响堆栈,因此 continue 确实有效。

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

4

3 回答 3

1

传统的 C 解决方案是longjmp函数,它可以任意向上跳转堆栈。请注意,总是有足够聪明的人不使用它,异常处理在很大程度上取得了成功。

于 2013-02-21T11:36:49.413 回答
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 内联某些内容或删除更高优化级别的堆栈帧,它会失败。但您可以添加检查指令的检查,如果它们已优化)

于 2013-02-21T11:41:37.180 回答
0

如果您在使用 MSVC 的 Windows 上,您可以使用异常(结构化异常处理,SEH)来实现类似的效果。正如 thiton 所说,在其他平台上,您可以使用 setjmp/longjmp。

使用 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() 中捕获异常。这不是真正的“return^2”,因为调用函数(main)必须配合。通常,您还需要与您想要跳过的函数(此处为 func_a)合作,因为它们可能会做一些事情并需要清理。只是说“从 func_b 返回,并停止 func_a 正在做的任何事情并从那里返回”可能是非常危险的。但是,如果您使用异常,您可以将代码包装在 func_a 中的 try/finally 子句中:

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

这在本机支持异常的语言(C++、Python、Java 等)中当然要好得多,并且不只是将其作为专有扩展加以固定。

请注意,有些人认为将异常用于控制流是不好的做法,并说应为真正的异常事件(如 IO 错误)保留异常。在许多情况下它确实有意义(例如,您正在解析某些东西,并在堆栈深处意识到您必须倒带并以不同的方式解析某些东西,您可以抛出自定义异常)。一般来说,我会说尽量不要太聪明,尽量不要做会让你的程序的读者感到困惑的事情。当您似乎需要使用这样的技巧时,通常有一种方法可以重组程序,以一种对语言来说很自然的方式来完成。或者,您使用的语言可能不是解决问题的好选择。

于 2013-02-21T12:46:18.103 回答