KEN's .NET

[IL6] 定数のロードあれこれ

ホーム > KEN's .NET > [IL6] 定数のロードあれこれ

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

1. はじめに

今までの回では、あまり詳細な説明なしに定数のロードのためにldc.i4命令などを利用してきました。 ldcで始まる名前の命令はいくつかありますが、それらの紹介も兼ねてこの機会にldcで始まる命令を説明します。

2. ldc命令一覧

ldc(load constantの意)で始まる命令を以下の表にまとめました。 ldc.XX (XXはロードする定数のデータ型を示す)の形式のものがこれらの命令群の基本となる命令です。 さらにldc.XX.num (numは0、1、2のような数字、もしくはs、m1/M1)のような形式がありますが、 これらはldc.XX命令の中でよく使われるであろうパターンについて専用の命令を用意したものです。 XXにはi4、i8、r4、r8があり、iはinteger(整数)、rはreal number(実数)を意味しており、 後に続く4、8はバイト数を意味します。つまり、i4は4バイト(=32ビット)の整数型を意味します。 また、numに数字が指定されているもの、例えば、ldc.i4.1はldc.i4 1と同じ意味です。 numにm1/M1が指定されている場合はこれは-1を意味しています。numにsが指定されている場合は、 -128〜127までの範囲の数値をパラメータとして渡すための専用命令です。 (ECMA 335の仕様書(英文)ではs付き命令のことはshort formと呼んでおり、一覧表では短形式と記載しています)

表1 ldc命令一覧
命令命令書式説明スタック遷移図例外
ldc.i4ldc.i4 num32ビット整数numをスタックにロードする。… → …, numなし
ldc.i4.0ldc.i4.00(int32)をスタックにロードする。… → …, numなし
ldc.i4.1ldc.i4.11(int32)をスタックにロードする。… → …, numなし
ldc.i4.2ldc.i4.22(int32)をスタックにロードする。… → …, numなし
ldc.i4.3ldc.i4.33(int32)をスタックにロードする。… → …, numなし
ldc.i4.4ldc.i4.44(int32)をスタックにロードする。… → …, numなし
ldc.i4.5ldc.i4.55(int32)をスタックにロードする。… → …, numなし
ldc.i4.5ldc.i4.56(int32)をスタックにロードする。… → …, numなし
ldc.i4.7ldc.i4.77(int32)をスタックにロードする。… → …, numなし
ldc.i4.8ldc.i4.88(int32)をスタックにロードする。… → …, numなし
ldc.i4.m1/ldc.i4.M1ldc.i4.m1/ldc.i4.M1-1(int32)をスタックにロードする。… → …, numなし
ldc.i4.sldc.i4.s numldc.i4の短形式… → …, numなし
ldc.i8ldc.i8 num64ビット整数numをスタックにロードする。… → …, numなし
ldc.r4ldc.r4 num32ビット浮動小数点数numをF型(実行環境依存のサイズの浮動小数点数型)としてスタックにロードする。… → …, numなし
ldc.r8ldc.r8 num64ビット浮動小数点数numをF型としてスタックにロードする。… → …, numなし

3. ldc.i〜を使って整数をロードする

図1 ldc.i〜命令のサンプルプログラム
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
.assembly ConstInt {}
.method public static void main()
{
    .entrypoint
    .maxstack 2
    ldstr "(1) ldc.i4.0〜ldc.i4.8を使ってみる。0〜8をロードする"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i4.0
    call void [mscorlib]System.Console::WriteLine(int32)
    ldc.i4.1
    call void [mscorlib]System.Console::WriteLine(int32)
    ldstr ":2〜7は省略"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i4.8
    call void [mscorlib]System.Console::WriteLine(int32)

    ldstr "(2) ldc.i4.m1を使ってみる。→ -1をロードする。"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i4.m1
    call void [mscorlib]System.Console::WriteLine(int32)

    ldstr "(3) ldc.i4.sを使ってみる。"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i4.s 127
    call void [mscorlib]System.Console::WriteLine(int32)
    ldstr "ldc.i4.sに128を指定すると…→{0}"
    ldc.i4.s 128
    box int32
    call void [mscorlib]System.Console::WriteLine(string, object)
    ldstr "※ldc.i4.sは符号付きの1バイトの範囲内(-128〜127)のint32を\n"
        + "ロードするのに適している。処理内容はldc.i4と同じ。"
    call void [mscorlib]System.Console::WriteLine(string)
    ldstr "(4) ldc.i4.sで1バイト範囲以上の値を扱うとどうなるか?"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i4.s 0x100
    call void [mscorlib]System.Console::WriteLine(int32)
    ldstr "※下位1バイト以外は無視される。0x100は0x00と同じ結果になるので0。"
    call void [mscorlib]System.Console::WriteLine(string)

    ldstr "(5) ldc.i8を使ってみる"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.i8 0x7FFFFFFFFFFFFFFF
    call void [mscorlib]System.Console::WriteLine(int64)
    ldc.i8 0xFFFFFFFFFFFFFFFF
    call void [mscorlib]System.Console::WriteLine(int64)
    ret
}

(1) ldc.i4.0〜ldc.i4.8を使ってみる。0〜8をロードする
0
1
:2〜7は省略
8
(2) ldc.i4.m1を使ってみる。→ -1をロードする。
-1
(3) ldc.i4.sを使ってみる。
127
ldc.i4.sに128を指定すると…→-128
※ldc.i4.sは符号付きの1バイトの範囲内(-128〜127)のint32を
ロードするのに適している。処理内容はldc.i4と同じ。
(4) ldc.i4.sで1バイト範囲以上の値を扱うとどうなるか?
0
※下位1バイト以外は無視される。0x100は0x00と同じ結果になるので0。
(5) ldc.i8を使ってみる
9223372036854775807
-1
図2 ldc.i〜命令のサンプルプログラムの実行結果

ソースコードと実行結果を見比べていただければ、各命令の仕様が理解できると思います。 いくつか重要なポイントについてのみ補足しておきます。

  • 26〜29行目で、ldc.i4.sに128を渡した場合に結果は-128となっていますが、 このことが「.s」付きの命令が符号付きの1バイトを扱う命令であることを示しています。 128は16進数で0x80であり最上位ビットが1ですので、符号付き整数では負数を意味します。
  • 30〜31行目の文字列のロードで今までに説明していない文字列表現のルールを示しています。
    • \nで改行を表すことができる(C#と同じ)
    • 1つの文字列を表現するために、+を使って複数に分割した形で表現できる
  • 33〜35行目でldc.i4.sで1バイト以上の範囲の値をパラメータとして渡すとどうなるか?を実験しています。 下位1バイト分以外は無視されるようです。 .NET Framework1.1のilasmでアセンブルした場合、「: warning -- Emitting 0x100 as a byte: data truncated to 0x0」 という警告が表示されました。(.NET Framework2.0のilasmでは特に警告はでないようです。)

4. ldc.r〜を使って浮動小数点数をロードする

図3 ldc.r〜命令のサンプルプログラム
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.assembly ConstFloat {}
.method public static void main()
{
    .entrypoint
    .maxstack 2

    ldstr "(1) ldc.r4を使ってみる"
    call void [mscorlib]System.Console::WriteLine(string)
    ldc.r4 1.05000000001
    ldc.r4 3.0
    div
    call void [mscorlib]System.Console::WriteLine(float64)

    ldstr "(2) ldc.r8を使ってみる"
    ldc.r8 1.05000000001
    ldc.r8 3.0
    div
    call void [mscorlib]System.Console::WriteLine(float64)
    ret
}

(1) ldc.r4を使ってみる
0.349999984105428
(2) ldc.r8を使ってみる
0.350000000000333
図4 ldc.r〜命令のサンプルプログラムの実行結果

ldc.r〜命令では4バイトと8バイトで異なる精度の結果になるということを確認できました。 最初に32ビット浮動小数点数同士を割り算しています。32ビット同士の計算ですので、 12行目のConsole::WriteLineはfloat32で見せるべきですが、計算結果の正確な値を確認するためfloat64で表示させています。 ちなみにfloat32はVBならSingle、C#ならfloatにあたります。また、float64はVBならDouble、C#ならdoubleにあたります。

5. 学んだこと

  • ldc.i〜命令で整数定数を、ldc.r〜命令で浮動小数点数定数をロードできる
  • 「.s」と末尾に付く命令はshort form(当サイトでは短形式と呼ぶ)と呼ばれる符号付き1バイト整数範囲の値を扱うことに特化した命令
  • 文字列表現で\nで改行を意味する。1つの文字列を+で複数の文字列に分割して表現できる

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


ホーム > KEN's .NET > [IL6] 定数のロードあれこれ

[e-mail] yone_ken00@hotmail.com