2013-03-17 8 views
12

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

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

(C-スタイリッシュな)は次のようになります。

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

バー()がスローされます。例外ルーチンは次の処理を行います。

ああ、戻りアドレスは関数foo()です。戻りアドレスは最初のtry-catchブロックにあり、Type型を投げるので、例外ハンドラルーチンはアドレスfoo + catch1_Typeにあります。だから、スタックをクリーンアップして、そこで終わります!

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

例外ハンドラは、別の関数でもかまいませんが、それと同等ですが、スタックフレームのローカル変数のアドレスを取得する方法は他の関数でfooですか?それはまた不可能と思われる。

だから、それを行う方法はありますか?私はこれを使ってアセンブラに入ることはしませんが、他のすべてが失敗した場合でも許容されます(ただし、ローカル変数 - 男性、異なる最適化レベルを使用する場合はどこにいるか分かりません)。

そして、この質問の目的は、を回避することです。 setjmp/longjmpアプローチです。

編集:私はかなりクールなアイディアを発見しましたが、完全には機能しません:gccの中

ネストされた関数を。彼らは何をすることができますか?

  • ローカル変数へのアクセス権を持って、
  • は、親関数でのgotoローカルラベルへの可能性を持っています!
  • は、関数の呼び出し先によって呼び出すことができます。ただし、関数がネストされた関数へのポインタを渡すことを前提としているので、呼び出し先のポインタによって利用できます。何でもゼロコストをやってから私を防ぎ

ダウンサイドは:彼らは未使用なら

  • 彼らも、-O0レベルで実行に最適化しています。これについて何かできますか?可能であれば、例外がスローされたときにシンボル名でアドレスを取得することができます。スローされていないときにノッキングを発生させる例外を実装する作業を行うだけです...
+0

これは、この問題について若干異なるテイクのように思える:http://stackoverflow.com/questions/307610/how-do-exceptions-work-behind-the-scenes-in-c ...それC++での例外の動作を確認します。 C++が例外を実装する方法を理解すれば、Cプログラム用にその実装をコピーすることが可能かどうかを判断できます。 –

+0

@MartinAtkins、私はg ++で例外がどのように実装されているかを少ししか認識していませんが、g ++の例外サポートが部分的にコンパイラとリンカ側にあるので、C言語で複製することは問題になりますツールを変更することなく。私はあなたがより多くのマナを得るために言及した話題を読むでしょう。 –

+0

あなたは完全に間違っています、 '試して'/'キャッチ'構造はコストがかかります。最初に、 'try'は何らかの形でスタックとすべてのレジスタの位置をマークしなければならず、' catch'/'throw'は' try'コンテキストを1つずつunwindしなければなりません。 C 'setjmp' /' longjmp'には「同等の」機能があります。これはまったく同じですが、その周りには接着剤が少なくて済むだけです。 –

答えて

3

周りに糊だ私はこのアイデアを再生するいくつかの時間を見つけた、と私は自分のための解決策を見つけることに非常に近いんだと質問。

  • gccは、親関数のローカル変数にアクセスできるネストされた関数を許可し、親関数のラベルに移動できます!
  • gccは定義されているが参照されていない場合、内部関数のコードを出力しません。ローカル関数へのポインタを取得するインラインno-op関数を定義し、それを親関数で呼び出すことができます。これにより、内部関数のコード生成が強制され、ゼロコストになります(より高い最適化レベルでは、インラインno-op呼び出しは削除されます)。より最近のgccでインライン呼び出しを最適化すると内部関数のコードが生成されなくなる可能性があります。
  • 悪いことは、ネストされた(内部の)関数をグローバルシンボルにすることはできません。彼らは常にローカルなので、dlsymを使用してアドレスを取得する可能性はありません。
  • プログラムがそのようなネストされた関数を使用している場合は、valgrindがクラッシュしています;)単純なテストプログラムを検証できましたが、valgrindを使用してメモリ違反がないことを確認できませんでした。

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

まあ、呼び出し元にポインタを渡すなど、内部関数を正しく使用したい場合は、例外がスローされた場合でも呼び出すことができます。 setjmp/longjmpよりも速い。

私はハッキングを続けるだろう。多分、私は道を見つけるだろう(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; 
} 
+1

C++の初期段階では、C++コンパイラ( 'cfront')はCコンパイラによってオブジェクトコードにコンパイルされたCコードを生成しました。例外処理は、そのシステムの中断を余儀なくされた機能の1つでした。例外を処理できるCコードを生成する方法はありませんでした。詳細については、Stroustrupの「Design and C++の進化(D&E)」を参照してください。 –

+0

例外ハンドラを記録する単一のグローバル変数 'g_r 'を持つことは、ネストされた' try'ブロックの問題に繋がります。最低限、例外ハンドラのスタックを維持する必要があります。 –

+0

@JonathanLeffler、私は知っています。このコードはほんの少しの実験ですが、私は例えばしようとしています。 dlopen selfとシンボル名で内部関数のアドレスを取得...正直言って、私はグローバルスタックも良い選択肢ではないと思います。ポインタをどこかに格納しなければならない場合は、おそらく最速のコードがスタックになるため、関数の引数(スタックへのアクセスは、私が考えるグローバルデータへのアクセスよりもキャッシュの方が速く、x86_64では引数がレジスタスタックメモリではありません)。 –

関連する問題