KEN's .NET

[IL11] 比較してみる

ホーム > KEN's .NET > [IL11] 比較してみる

ここでは.NET Frameworkのアセンブリ言語MSIL(Microsoft Intermediate Language)を使ったプログラミングを紹介します。 読者は.NET プログラミング経験があることを想定しています。

1. はじめに

IL9、IL10の回で分岐命令の説明を行いました。 分岐命令のうちbgt命令のようにスタック上の2値を扱う命令は、 その2値の比較を行い、その結果に応じて指定のラベルに分岐します。 このように条件付きの分岐命令では比較と分岐という2つの作業を行います。 このうち比較だけを行うための命令を紹介します。

2. 比較命令

比較命令はc〜の名前になっており、cはcompareの意味で、後ろに続くeq、gt、ltはbeq、bgt、bltと同じ意味です。 b〜の分岐命令とは、大小比較の結果をスタックに返すだけで分岐しないという点が異なります。 比較内容に合致する場合は1、そうでない場合は0を返します。

表1 比較命令一覧
命令命令書式説明スタック遷移図例外
ceqceqvalue1がvalue2に等しいなら1(int32)を、そうでなければ0(int32)をスタックに置く。…, value1, value2 → …, resultなし
cgtcgtvalue1がvalue2より大きいなら1(int32)を、そうでなければ0(int32)をスタックに置く。…, value1, value2 → …, resultなし
cgt.uncgt.unvalue1がvalue2より大きいなら1(int32)を、そうでなければ0(int32)をスタックに置く。整数値に対しては符号なし、浮動小数点数に対しては順序なしで比較を行う。…, value1, value2 → …, resultなし
cltcltvalue1がvalue2より小さいなら1(int32)を、そうでなければ0(int32)をスタックに置く。…, value1, value2 → …, resultなし
clt.unclt.unvalue1がvalue2より小さいなら1(int32)を、そうでなければ0(int32)をスタックに置く。整数値に対しては符号なし、浮動小数点数に対しては順序なしで比較を行う。…, value1, value2 → …, resultなし

3. 比較命令を使って分岐をさせよう

比較命令とbrfalse/brtrueの組合せにより、分岐命令と同じようなことができます。 サンプルプログラムを図1(CompareInt32.il)に、実行結果の例は図2に掲載しました。 今回は2ファイルに分割しているのでアセンブルの方法についても図3に載せてあります。

このプログラムはコンソールで2つの整数を入力し、その2値の大小比較した結果をコンソールに表示します。 IL9の回には2つの固定値を比較し、大きいか大きくないかだけを判定するサンプルプログラムを紹介しましたが、 実現していることはほぼ同じです。ただし、コンソールから2つの整数を入力する際にエラーチェックを行っていたり、 2値の比較で前者の値が大きい、小さい、後者の値と同じという3つの状態を判定していたり、 より実践的な構成になっています。

ソースコードの行数も増えてきたので、複数のilファイルで1プログラムを構成するようにました。 メイン処理のComareInt32.ilとコンソールからの入力用メソッドを切り出したInputInt32.ilの2ファイルに分けています。 InputInt32.ilについては特に説明しませんが、いつもどおり本文の最後でダウンロードできるようにしてあります。

図1 比較命令を使った分岐のサンプルプログラム
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
.assembly CompareInt32 {}
.method public static void Main()
{
    .entrypoint
    .maxstack 3
    .locals init (int32 firstNumber, int32 secondNumber)

    // 比較する2つの整数を入力
    ldstr "1つ目の整数を入力してください。:"
    call int32 InputInt32(string)
    stloc firstNumber
    ldstr "2つ目の整数を入力してください。:"
    call int32 InputInt32(string)
    stloc secondNumber

    // 2つの値を比較した結果の文字列を取得し表示
    ldloc firstNumber
    ldloc secondNumber
    call string GetComparedResult(int32, int32)
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}

// 引数で2つの整数を渡して大小比較の結果の文字列を返すメソッド
.method public static string GetComparedResult(int32 firstNumber, int32 secondNumber)
{
    .maxstack 3

    // メソッド最後のString.Formatメソッドの第一引数の文字列を設定
    ldarg firstNumber
    ldarg secondNumber
    cgt     // firstNumber > secondNumber
    brfalse ELSE
    THEN:
        ldstr "1つ目の整数が大きい。(1つ目:{0}、2つ目:{1})"
        br END
    ELSE:
        ldarg firstNumber
        ldarg secondNumber
        ceq    // firstNumber == secondNumber(C#)、firstNumber = secondNumber(VB)
        brfalse ELSE2
        THEN2:
            ldstr "1つ目と2つ目の値が等しい。(1つ目:{0}、2つ目:{1})"
            br END2
        ELSE2:
            ldstr "1つ目の整数が小さい。(1つ目:{0}、2つ目:{1})"
        END2:
    END:
    // メソッド最後のString.Formatメソッドの第二引数、第三引数の値を設定
    ldarg firstNumber
    box int32
    ldarg secondNumber
    box int32
    call string [mscorlib]System.String::Format(string, object, object)
    ret
}

  • Mainメソッド - メイン処理です。ここから処理が始まります。
    • 9〜11行目と12〜14行目、コンソールから2つの値を受付け、ローカル変数firstNumberとsecondNumberに保存します。
    • int32 InputInt32(string)はInputInt32.ilファイルで定義したメソッドです。 引数で指定された文字列をコンソールに表示し入力待ちをします。コンソールから1行入力されたら、 その値を整数に変換して返します。入力された値が整数でない場合、符号付き4バイト整数の範囲外の値の場合は、 エラーメッセージを表示し、再入力を促します。
    • 17〜20行目、ローカル変数firstNumber、secondNumberを引数としてGetComparedResultメソッドを呼び出し、 その結果をコンソールに表示します。

  • GetComparedResultメソッド - Mainメソッドから呼ばれます。引数の2値を比較した結果の文字列を返します。
    • 25行目、GetComparedResultメソッドのシグニチャ(引数の数や型、戻り値の型など)を定義しています。Mainメソッド同様グローバルメソッドです。 どの名前空間、クラスにも属していないので、呼び出すときはクラス名等の修飾は必要ありません。
    • 30、31行目、まだ説明していない命令ですが、ldarg(load argument)命令で引数で指定された値をスタックにロードします。 ldloc命令などと使い方は同じです。
    • 32行目のcgt命令で30、31行目でロードした2値を比較します。firstNumberの方が大きければ、 スタックには1が、firstNumberがsecondNumberと等しいかsecondNumberの方が大きければ、スタックには0がロードされます。
    • cgt命令の結果を受けて、33行目のbrfalse命令で分岐します。firstNumberの方が大きければ、分岐せずTHENラベルへ処理が進み、 firstNumberの方が大きくなければ、37行目のELSEラベルに分岐します。 bgt命令を使った場合と異なり、cgt命令とbrfalse命令を組み合わせるとVB/C#のIF文の条件分岐と同じ並び順を実現できます。
    • 38〜47行目、先のcgt命令、brfalse命令の場合と同様に今度はceq命令とbrfalse命令を組み合わせて、 firstNumberとsecondNumberが等しいかどうかを判定しています。
    • 50〜54行目、事前に35、43、46行目のいずれかのldstrで読み込んだ文字列とfirstNumber、secondNumberを使って、 String::Formatメソッドを呼び出します。これにより書式化した文字列を作成しスタックに積みます。 そしてretでこの文字列を戻り値として返します。

> CompareInt32.exe
1つ目の整数を入力してください。:123
2つ目の整数を入力してください。:12
1つ目の整数が大きい。(1つ目:123、2つ目:12)

> CompareInt32.exe
1つ目の整数を入力してください。:312
2つ目の整数を入力してください。:80990
1つ目の整数が小さい。(1つ目:312、2つ目:80990)

> CompareInt32.exe
1つ目の整数を入力してください。:45
2つ目の整数を入力してください。:45
1つ目と2つ目の値が等しい。(1つ目:45、2つ目:45)

> CompareInt32.exe
1つ目の整数を入力してください。:dfskj
入力が不正です。整数を入力してください!:123456789012345
入力が不正です。約-21億〜21億の範囲で整数を入力してください!:12
2つ目の整数を入力してください。:12
1つ目と2つ目の値が等しい。(1つ目:12、2つ目:12)
図2 図1のサンプルプログラムの実行結果の例
(1つ目の値が大きい、小さい、2つ目と等しい、入力値が異常(数値以外、オーバーフロー)

> ilasm /out:ComareInt32.exe ComareInt32.il InputInt32.il
図3 サンプルプログラムのアセンブル方法(複数ソースコードのアセンブル)
(各ファイルは必要に応じて絶対パスなどに変更してください)

4. 学んだこと

  • 戻り値1 or 0を返す比較命令ceq、cgt、cltがある
  • 比較命令はbrtrue/brfalse命令と組み合わせることでbgtをはじめとした分岐命令と同じことを実現できる
  • 独自メソッドの引数指定はC#風。ldarg命令でスタックにロードする
  • 複数のilファイルにソースコードを分けておいて、一緒にアセンブルできる

A. サンプルダウンロード


ホーム > KEN's .NET > [IL11] 比較してみる

[e-mail] yone_ken00@hotmail.com