web-dev-qa-db-ja.com

スイッチケースアセンブリレベルコード

私はcygwinウィンドウでCをプログラミングしています。少しCプログラミングを行い、言語に慣れた後、内部を調べて、自分が作成したコードに対してコンパイラーが何を行っているかを確認したいと思いました。

そこで、switch caseステートメントを含むコードブロックを書き留め、以下を使用してそれらをアセンブリに変換しました。

gcc -S foo.c  

Cソースは次のとおりです。

switch(i)
{
    case 1:
    {
        printf("Case 1\n");
        break;
    }
    case 2:
    {           printf("Case 2\n");
        break;
    }
    case 3:
    {
        printf("Case 3\n");
        break;
    }
    case 4:
    {
        printf("Case 4\n");
        break;
    }
    case 5:
    {
        printf("Case 5\n");
        break;
    }
    case 6:
    {
        printf("Case 6\n");
        break;
    }
    case 7:
    {
        printf("Case 7\n");
        break;
    }
    case 8:
    {
        printf("Case 8\n");
        break;
    }
    case 9:
    {
        printf("Case 9\n");
        break;
    }
    case 10:
    {
        printf("Case 10\n");
        break;
    }
    default:
    {
        printf("Nothing\n");
        break;
    }
}  

これで、同じものの結果のアセンブリは次のようになります。

movl    $5, -4(%ebp)
cmpl    $10, -4(%ebp)
ja  L13
movl    -4(%ebp), %eax
sall    $2, %eax
movl    L14(%eax), %eax
jmp *%eax
.section .rdata,"dr"
.align 4
L14:
.long   L13
.long   L3
.long   L4
.long   L5
.long   L6
.long   L7
.long   L8
.long   L9
.long   L10
.long   L11
.long   L12
.text
L3:
movl    $LC0, (%esp)
call    _printf
jmp L2
L4:
movl    $LC1, (%esp)
call    _printf
jmp L2
L5:
movl    $LC2, (%esp)
call    _printf
jmp L2
L6:
movl    $LC3, (%esp)
call    _printf
jmp L2
L7:
movl    $LC4, (%esp)
call    _printf
jmp L2
L8:
movl    $LC5, (%esp)
call    _printf
jmp L2
L9:
movl    $LC6, (%esp)
call    _printf
jmp L2
L10:
movl    $LC7, (%esp)
call    _printf
jmp L2
L11:
movl    $LC8, (%esp)
call    _printf
jmp L2
L12:
movl    $LC9, (%esp)
call    _printf
jmp L2
L13:
movl    $LC10, (%esp)
call    _printf
L2:  

さて、アセンブリでは、コードは最初に最後のケース(つまりケース10)を最初にチェックします。これは非常に奇妙です。そして、それは「i」を「eax」にコピーし、私を超えた何かをすることです。

コンパイラがswitch..caseのジャンプテーブルを実装していると聞きました。このコードが行っていることですか?またはそれは何をしているのですか、そしてなぜですか?ケースの数が少ない場合、コードはif ... elseラダーで生成されたものと非常に似ていますが、ケースの数が増えると、この異常な外観の実装が見られます。

前もって感謝します。

16
puffadder

最初に、コードはiを10と比較し、値が10より大きい場合にデフォルトのケースにジャンプします(cmpl $10, -4(%ebp)の後に_ja L13_が続きます)。

コードの次のビットは、入力を2ずつ左にシフトしています(_sall $2, %eax_)。これは、ジャンプテーブルへのオフセットを生成する4の倍数と同じです(テーブルの各エントリは4バイトの長さであるため)。

次に、ジャンプテーブルからアドレスをロードし(movl L14(%eax), %eax)、そこにジャンプします(_jmp *%eax_)。

ジャンプテーブルは、単にアドレスのリストです(アセンブリコードではラベルで表されます)。

_L14:
.long   L13
.long   L3
.long   L4
...
_

注意すべき点の1つは、_L13_がデフォルトのケースを表していることです。これは、ジャンプテーブルの最初のエントリ(iが0の場合)であり、最初に特別に処理されます(i> 10の場合)。

26

はい、ジャンプテーブルです。最初のチェックは、値がケースに含まれているかどうかをチェックし、含まれていない場合はデフォルトにジャンプすることです。このようなテーブルでは、%eaxが0の場合、L14(%eax)はテーブルの最初の要素(L13)を指すことを忘れないでください。したがって、テーブルではcase 10:は、10ではなく9でインデックス付けされます。

切り替えを行う方法は、caseにある値によって異なります。この場合、それらは「シーケンス」にあるため、単純なジャンプテーブルが可能です。

3
ShinTakezou

ために [1..10]コンパイラはテーブルを生成するので、どこかに移動するために値を比較する必要はありません。直接次のことを行います。goto table[i]。そうすればそれはより速くなります。

しかし、場合i > 10デフォルトのステートメントにジャンプします。ジャンプする前に最初にチェックする必要があります。そうしないと、プログラムが惨めにクラッシュします。

スパース値(23、9233、91238など、1、2、3 ...ではない)がある場合、コンパイラーはそのようなテーブルを生成せず、各値を比較します。

2
Nicolas Viennot

はい、最初のeaxは、ジャンプテーブル(ラベルL14:に続く)からアドレスを取得するためのスイッチ値(乗算としてのsallシフト)によって計算されます。

jmp *%eaxは、ケースのラベルに近いジャンプです。 (eaxの近くのjmp)

他のラベルに続くコードは印刷するだけで、他の場合はスキップします。

0
stacker