C コンパイラは、計算型 goto 文として知られる C の拡張機能を認識します。計算型 goto 文を使用すると、実行時に分岐先を判別することができます。次のように演算子 '&&' を使用して、ラベルのアドレスを取得し、void * 型のポインタに割り当てることができます。
void *ptr; ... ptr = &&label1; |
あとに続く goto 文は、ptr により label1 に分岐できます。
goto *ptr; |
ptr は実行時に計算されるため、ptr は有効範囲内にある任意のラベルのアドレスを取得でき、goto 文はこのアドレスに分岐することができます。
ジャンプテーブルを実装するには、計算型 goto 文を次の方法で使用します。
static void *ptrarray[] = { &&label1, &&label2, &&label3 };
|
これで、次のようにインデックスを指定して配列要素を選択できます。
goto *ptrarray[i]; |
ラベルのアドレスは、現在の関数スコープからしか計算できません。現在の関数以外のラベルについてアドレスを取得しようとすると、予測できない結果になります。
ジャンプテーブルは switch 文と同様の働きをしますが、両者にはいくつかの相違点があり、ジャンプテーブルではプログラムフローの追跡がより困難になる可能性があります。顕著な相違点は、switch 文のジャンプ先は必ず予約語から順方向にあることです。 計算型 goto 文を使用してジャンプテーブルを実装すると、順方向と反対方向の両方に分岐することができます。
#include <stdio.h>
void foo()
{
void *ptr;
ptr = &&label1;
goto *ptr;
printf("Failed!\n");
return;
label1:
printf("Passed!\n");
return;
}
int main(void)
{
void *ptr;
ptr = &&label1;
goto *ptr;
printf("Failed!\n");
return 0;
label1:
foo();
return 0;
}
|
次の例では、プログラムフローの制御にジャンプテーブルを使用しています。
#include <stdio.h>
int main(void)
{
int i = 0;
static void * ptr[3]={&&label1, &&label2, &&label3};
goto *ptr[i];
label1:
printf("label1\n");
return 0;
label2:
printf("label2\n");
return 0;
label3:
printf("label3\n");
return 0;
}
%example: a.out
%example: label1
|
また、計算型 goto 文は、スレッド化されたコードのインタプリタとしても使用されます。高速ディスパッチを行うために、インタプリタ関数の範囲内にあるラベルアドレスを、スレッド化されたコードに格納することができます。
前述の例を別の方法で記述すると、次のようになります。
static const int ptrarray[] = { &&label1 - &&label1, &&label2 - &&label1, &&label3 - &&label1 };
goto *(&&label1 + ptrarray[i]);
|
この例では必要な動的再配置を行う回数が少ないので、データ (ptrarray の要素) を読み取り専用にすることができ、共有ライブラリコードに関してはさらに効果的です。