KEN's .NET

[IL13] 型と型変換

ホーム > KEN's .NET > [IL13] 型と型変換

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

1. はじめに

今までにstring、int32、int64、objectといったいくつかの基本的な型を使用しました。 また、class [mscorlib]System.Text.StringBuilderやvalueType [mscorlib]System.Int32 などのような参照型、値型の表記による型も使用しています。 これまで型についてはあまり説明せずに使ってきましたが、 今回はILで解釈できる型にはどんな型があるのか?ということを紹介します。 また、様々な型を扱うようになると型同士の変換が必要になる場面があるので、 IL命令として用意されている型変換の命令を紹介します。

2. 型

表1をご覧ください。ILで解釈できるすべての型を示しています。

表1 型一覧
説明
int8符号付き8ビット整数
int16符号付き16ビット整数
int32符号付き32ビット整数
int64符号付き64ビット整数
unsigned int8符号無し8ビット整数
unsigned int16符号無し16ビット整数
unsigned int32符号無し32ビット整数
unsigned int64符号無し64ビット整数
boolブール値を表す
float3232ビット浮動小数点数
float6462ビット浮動小数点数
char16ビットUnicodeの文字コード番号
objectSystem.Object
stringSystem.String
class TypeReferenceユーザ定義参照型のTypeReference
valuetype TypeReferenceボックス化されていないユーザ定義の値型のTypeReference
Type [ (Bound (, Bound ...)) ]Typeの配列(オプションで次元数、要素数(Bound)の指定付き)
native intプラットフォーム依存の32か64ビットの符号付き整数
native unsigned intプラットフォーム依存の32か64ビットの符号無し整数
! Int32型定義内のジェネリックパラメータ。0から始まるインデックスでアクセスされる。.NET Framework2.0で追加された
!! Int32メソッド定義内のジェネリックパラメータ。0から始まるインデックスでアクセスされる。.NET Framework2.0で追加された
void型ではなく、メソッドの戻り値、または、void *の一部としてのみ使用される
method CallConv Type * ( Parameters )メソッドポインタ
Type &Typeへのマネージドポインタ(Typeはマネージドポインタ、typedref以外)
Type *Typeへのアンマネージドポインタ
Type < GenArgs >ジェネリックタイプのインスタンス化。.NET Framework2.0で追加された
Type modopt ( TypeReference )呼び出し側によって無視されうるカスタム修飾子
Type modreq ( TypeReference )呼び出し側が理解するカスタム修飾子
Type pinnedローカル変数に対してのみ適用可。 ガベージコレクタが参照値を移動しない
typedrefSystem.TypedReference(型付けされた参照)mkrefanyで作成され、 refanytype、refanyvalで使用される
※表中の斜体字はその文字列そのものではなく、その項目に相当する内容が入ることを意味します。 例えば、class TypeReferenceの場合、StringBuilderクラスであれば、 TypeReferenceには[mscorelib]System.Text.StringBuilderが入る、といった具合です。

実際にプログラムで使いながら徐々に覚えていけばよいので、よく使う型についてのみ説明をします。 (正味の話、表の最後いくつかの型は、まだ詳細な説明ができない、 という事情もあります(^^;)

  • 整数型にはバイト数に応じてint8〜int64まであります。各々、符号無しを表すunsignedを冠した型があります。
  • ブール値を表すboolはサイズが1バイトで0=False、非0=Trueを意味します。
  • 浮動小数点型は32、64ビットの2種類、float32、float64があります。これらの値がスタックに保存される際は、 F型と呼ばれるプラットフォーム依存の32、または64ビットの浮動小数点数になります。(データに応じて適切なビット数が使われるが、 例えば、CLRの実装によってはF型が常に64ビット浮動小数点数ということがありえる)
  • char、string、object同様、System名前空間にある対応する構造体、クラスと同じです。 整数型、ブール型、浮動小数点型も同様にSystem名前空間にある対応する構造体があります。 System.DateTime構造体やSystem.Decimal構造体に対するILの組み込み型はありません。
  • class / valueType TypeReferenceは、 .NET Frameworkクラスライブラリや独自定義の参照型、値型を利用する際に使用する型の表記方法です。 TypeReferenceの記述ルールはStringBuilderの例のような記述です。
  • Type [ (Bound (, Bound ...)) ]は配列です。 int32[]、string[10]、int32[10, 20]などのような記述です。
配列などのように今までの回に説明していないものもありますが、 以降の回でいずれ扱うので、ここでは詳細には触れません。 (.NET Framework2.0で追加されたジェネリック関係の型についてもジェネリックを扱う回に紹介したいと思いますので、 今回は特に触れません。)

3. 型変換命令

型変換の命令には表2のようなものがあります。すべてを掲載すると量が多くなるので一部掲載を省略しています。 一覧のうちconv.i4〜な命令で確認できますが、addなどの算術命令のようにovfやunが付いているパターンの命令があります。 (unはovf付き命令のときのオーバーフローチェックするときの型の扱いに影響があります)

表2 型変換命令一覧
命令命令書式説明スタック遷移図例外
conv.iconv.inative intに変換し、native intとしてスタックに置く…, value → …, resultなし
conv.i1conv.i1int8に変換し、int32としてスタックに置く…, value → …, resultなし
conv.i2conv.i2int16に変換し、int32としてスタックに置く…, value → …, resultなし
conv.i4conv.i4int32に変換し、int32としてスタックに置く…, value → …, resultなし
conv.i8conv.i8int64に変換し、int64としてスタックに置く…, value → …, resultなし
conv.uconv.u符号なしnative intに変換し、native intとしてスタックに置く…, value → …, resultなし
conv.u1conv.u1符号なしint8に変換し、int32としてスタックに置く…, value → …, resultなし
conv.u2conv.u2符号なしint16に変換し、int32としてスタックに置く…, value → …, resultなし
conv.u4conv.u4符号なしint32に変換し、int32としてスタックに置く…, value → …, resultなし
conv.u8conv.u8符号なしint64に変換し、int64としてスタックに置く…, value → …, resultなし
conv.ovf.i4conv.ovf.i4int32に変換する。オーバーフロー時は例外をスローする…, value → …, resultSystem.OverflowException:
結果がその型で表現できない場合。
conv.ovf.i4.unconv.ovf.i4.unスタックの一番上の符号なしの値をint32に変換する。 オーバーフロー時は例外をスローする…, value → …, resultSystem.OverflowException:
結果がその型で表現できない場合。
conv.r.unconv.r.un符号なし整数をネイティブサイズの浮動小数点数(F型)に変換する。 Float32で精度を落とさず元の整数を表すことができる場合は float32、それ以外の場合は float64になる…, value → …, resultなし
conv.r4conv.r4float32に変換し、ネイティブサイズの浮動小数点数(F型)としてスタックに置く…, value → …, resultなし
conv.r8conv.r8float64に変換し、ネイティブサイズの浮動小数点数(F型)としてスタックに置く…, value → …, resultなし
※conv系命令にはまだありますが、代表的なものを掲載しています。 掲載しなかった命令は以下の通りです。 conv.ovf.i, conv.ovf.i.un, conv.ovf.i1, conv.ovf.i1.un, conv.ovf.i2, conv.ovf.i2.un, conv.ovf.i8, conv.ovf.i8.un, conv.ovf.u, conv.ovf.u.un, conv.ovf.u1, conv.ovf.u1.un, conv.ovf.u2, conv.ovf.u2.un, conv.ovf.u4, conv.ovf.u4.un, conv.ovf.u8, conv.ovf.u8.un

4. 型変換してみる

int32のデータとint64のデータを足し算することを考えて見ましょう。 計算する際には型を合わせる必要があるので、int32→int64かint64→int32への変換のどちらかが必要です。 そして、int32→int64は拡大変換、int64→int32は縮小変換といいます。 拡大変換ではサイズの大きな型に変換するので問題なく変換できます。それに対して、 縮小変換の場合、変換元のint64の値がint32におさまらない数値の場合、データの欠落か、オーバーフローが起きることになります。 このことを確認できる型変換命令サンプルプログラムを図1(Conv.il)に掲載しました。 実行結果も合わせて図2に掲載しています。

図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
.assembly Conv {}
.method public static void Main()
{
    .entrypoint
    .maxstack 2

    // 拡大変換
    ldc.i4 100              // 0x 0000 0064
    conv.i8                 // 0x 0000 0000 0000 0064 ← 変換後
    ldc.i8 10000000000000
    add
    call void [mscorlib]System.Console::WriteLine(int64)

    // 縮小変換 - 上位4バイトは無視される
    ldc.i4 100
    ldc.i8 10000000000000   // 0x 0000 0918 4E72 A000
    conv.i4                 // 0x           4E72 A000 ← 上位4バイト無視して変換
    add
    call void [mscorlib]System.Console::WriteLine(int32)

    // 縮小変換 - オーバーフローチェックあり
    ldc.i4 100
    ldc.i8 10000000000000   // 0x 0000 0918 4E72 A000
    conv.ovf.i4             // 4バイト以上の値の変換のためオーバーフロー
    add
    call void [mscorlib]System.Console::WriteLine(int32)
    ret
}

10000000000100
1316135012

ハンドルされていない例外 : System.OverflowException: 演算操作の結果オーバーフロ
ーが発生しました。
   at Main()
図2 型変換命令サンプルの実行結果

ソースコードを順に説明します。

  • このプログラムでは3つの計算を行った結果を表示します。 どの計算でも100 + 10000000000000(10兆)を行います。
  • 7〜12行目。後者の値はint32の範囲外の数値ですので、計算はint64で行う必要があります。 そこでint32の100をconv.i8命令でint64に変換(拡大変換)し足し算します。 結果はいたって普通に10000000000100となります。
  • 14〜19行目。こちらは後者の値をconv.i4でint32に変換(縮小変換)しようとします。 後者の値はint32の範囲におさまりませんが、オーバーフローをチェックしない型変換命令のため、 単に下位4バイト部分がint32のデータとして扱われます。 100(16進数で64) + 10000000000100(16進数で9184E72A000。これの下位4バイト=4E72A000) の計算を行うと1316135012(16進数で4E72A064)となるので、このように動作していることを確認できます。 (16進数⇔10進数の変換はWindows付属の電卓(calc.exe)で簡単に行えます。[表示]メニューで[関数電卓]モードを選択)
  • 21〜26行目は、14〜19行目とほぼ同じで型変換命令がconv.ovf.i4という点が異なります。 オーバーフローチェックを行いますので、後者の値はint32への変換でオーバーフローになります。

5. 学んだこと

  • 整数、浮動小数点数間の型変換にはconv.〜という命令が使える
  • 型変換命令には派生系としてovf付き命令、un付き命令がある
  • 型変換には拡大変換(データの欠損なし)、縮小変換(データの欠損、または、オーバーフローが起こりうる)がある
  • 縮小変換でオーバーフローなしの場合、変換元データから変換先の型のバイト数だけ最下位から切り出しデータに変換される

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


ホーム > KEN's .NET > [IL13] 型と型変換

[e-mail] yone_ken00@hotmail.com