13

g++ コンパイラには、ゼロコストの例外処理機能があります。私の理解では、try何もしませんが、例外がスローされると、例外ハンドラーのサブルーチンが実行されます。このような:

void foo() {
    try {
        bar(); // throws.
    } catch (Type exc) {
        baz();
    }
}

疑似コード (c-stylish) では、次のようになります。

void foo() {
    bar();
    return;
catch1_Type:
    baz();
}

bar() スロー。例外ルーチンは次のことを行います。

ああ、戻りアドレスは関数 foo() にあります! また、戻りアドレスは最初の try-catch ブロックにあり、型 Type をスローするため、例外ハンドラー ルーチンはアドレス foo+catch1_Type にあります。スタックをクリーンアップして、そこにたどり着きます!

今私の質問: C でそれを実装する方法はありますか? (gcc でサポートされている C 方言に興味がありますが、C99 以降でもかまいません)。catch1_Typeラベルのアドレスを取得する方法はわかりませんが、スタックの検査とトラバーサルに libunwind などを使用できることは知っています。できない場合があります。

例外ハンドラーは別の関数である可能性がありますが、それは同様に問題ありませんが、その他の関数でスタックフレームのローカル変数のアドレスを取得する方法はfoo? それも無理のようです。

では・・・何かいい方法はないでしょうか?これでアセンブラに入りたくないのですが、他のすべてが失敗した場合も受け入れられます(ローカル変数ですが、さまざまな最適化レベルを使用している場合、それらがどこにあるのかわかりません)。

明確にするために、この質問の目的はsetjmp/longjmp アプローチを避けることです。

編集:私は非常にクールなアイデアを見つけましたが、完全には機能しません:

gcc のネストされた関数。彼らは何ができますか?

  • ローカル変数にアクセスでき、
  • 親関数でローカル ラベルに移動する可能性があります。
  • ネストされた関数にポインターを渡す場合、関数の呼び出し先から呼び出すことができるため、呼び出し先のポインターで使用できます。

私がゼロコストで何かをするのを妨げる欠点:

  • 未使用の場合、-O0 レベルでも最適化されます。これについて何かできることはありますか?できれば、例外がスローされたときにシンボル名でアドレスを取得でき、スローされない場合はコストがかからない例外を実装するだけです...
4

1 に答える 1

3

私はこのアイデアを試す時間を見つけました。私自身の質問に対する解決策を見つけることに非常に近づいています。詳細は次のとおりです。

  • gcc は、親関数のローカル変数にアクセスできるネストされた関数を許可し、親関数のラベルに移動できます!
  • 内部関数が定義されているだけで参照されていない場合、gcc は内部関数のコードを出力しません。ローカル関数へのポインタを取得するインライン no-op 関数を定義し、それを親関数で呼び出すことができます。これにより、内部関数のコード生成が強制され、ゼロコストになります (より高い最適化レベルでは、インライン no-op 呼び出しが削除されます)。最近の gcc では、インライン呼び出しを最適化すると、内部関数のコードが生成されない可能性があります..
  • 悪いことに、ネストされた (内部) 関数を強制的にグローバル シンボルにする方法がわかりません。それらは常にローカルであるため、dlsym を使用してアドレスを取得することはできません。
  • プログラムがそのようなネストされた関数を使用している場合、別の悪いニュースです。

チェックに使用したコードには明らかな欠陥があります。関数の実行中に例外処理ルーチンへのグローバル ポインターを設定する必要があるため、「ゼロ コスト」ではありません。これをコンパイル時に作成できれば!

結局のところ、呼び出し先にポインターを渡すなど、内部関数を適切に使用したい場合は、例外がスローされた場合に呼び出すことができます。おそらく、 setjmp/ロングジャン...

私はハッキングを続けます、多分私は方法を見つけるでしょう (親のパーソナリティ ルーチンとして関数を登録する GAS を強制するコードのアセンブラー チャンク?)。

#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>

typedef void (*catch_routine)(void*);

catch_routine g_r = NULL;

void tostr_internal(char* str, int a)
{
    int result = a + 'a';
    if (result < 'a' || result > 'z')
    {
        // handle exception
        if(g_r)
        {
            g_r(&a);
        }
        else
        {
            fprintf(stderr, "Exception not caught!");
            abort();
        }
    }
    else
    {
        str[0] = result;
        str[1] = '\0';
    }
}

char* tostring(int a)
{
    __label__ exhandler;
    char* string = (char*)malloc(2*sizeof(char));

    void personality(void* exid) {
        fprintf(stderr, "Number %d is not a character!\n", *(int*)(exid));
        free(string);
        goto exhandler;
    }
    g_r = personality;

    tostr_internal(string, a);
    return string;

exhandler:
    return NULL;
}

int main(int a, char** b)
{
    int i = 0;

    for(i = 0; i < 10000; i++)
    {
        int trythisbastard = i % 95;
        char* result = tostring(trythisbastard);
        if (result)
        {
            fprintf(stderr, "Number %d is %s\n", trythisbastard, result);
            free(result);
        }
    }

    return 0;
}
于 2013-03-31T16:57:20.890 に答える