1. ホーム
  2. c

[解決済み] スイッチケース組立レベルコード

2022-03-03 17:15:04

質問

WindowsのcygwinでC言語のプログラミングをしています。C言語でのプログラミングに少し慣れてきたところで、フードの下を見て、私が書いたコードに対してコンパイラが何をしているのか見てみたくなったのです。

そこで、switch case文を含むコードブロックを書き下ろし、usingでアセンブリに変換してみました。

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 のラダーで生成されるコードとほとんど変わらないのですが、ケース数が増えてくると、このような変わった実装が見られるようになります。

よろしくお願いします。

どのように解決するのですか?

まず、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
...

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