4

これはほとんど理論的な質問であり、あまり使用されていません。

この状況を考慮してください:

function a() {
  return;
}

function b(){
  a();
}

子関数から親関数でreturnを呼び出すことはできますか?

さて、この場合、あなたは簡単に行うことができreturn a();、それは起こりますが、あなたがリターンを実行しないことに興味を持っているとしましょう。

それをアセンブリに変換するとき、これは意味がないことを私は知っています。この場合、uは使用できますgotoが、それがどれほど危険かは誰もが知っています。

私のロジックでは、親でcontinueを呼び出す子ループからを実行できる場合continue、これは同じである必要がありますが、ループはスタックに影響を与えないため、continueが機能することは理にかなっています。

イベントまたはoopアプローチを使用してこのケースを処理する方法があるかどうか疑問に思っていますか?

4

3 に答える 3

1

従来の C ソリューションはlongjmp関数であり、スタックを任意の方法でジャンプできます。注意してください、それを使用しないのに十分賢明な人は常に存在し、例外処理によって大部分が成功しました。

于 2013-02-21T11:36:49.413 に答える
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」ではありません。呼び出し元の関数 (メイン) が対応する必要があるからです。一般に、ジャンプしたい関数 (ここでは 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 に答える
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 に答える