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 | ブール値を表す |
| float32 | 32ビット浮動小数点数 |
| float64 | 62ビット浮動小数点数 |
| char | 16ビットUnicodeの文字コード番号 |
| object | System.Object |
| string | System.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 | ローカル変数に対してのみ適用可。 ガベージコレクタが参照値を移動しない |
| typedref | System.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.i | conv.i | native intに変換し、native intとしてスタックに置く | …, value → …, result | なし |
| conv.i1 | conv.i1 | int8に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.i2 | conv.i2 | int16に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.i4 | conv.i4 | int32に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.i8 | conv.i8 | int64に変換し、int64としてスタックに置く | …, value → …, result | なし |
| conv.u | conv.u | 符号なしnative intに変換し、native intとしてスタックに置く | …, value → …, result | なし |
| conv.u1 | conv.u1 | 符号なしint8に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.u2 | conv.u2 | 符号なしint16に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.u4 | conv.u4 | 符号なしint32に変換し、int32としてスタックに置く | …, value → …, result | なし |
| conv.u8 | conv.u8 | 符号なしint64に変換し、int64としてスタックに置く | …, value → …, result | なし |
| conv.ovf.i4 | conv.ovf.i4 | int32に変換する。オーバーフロー時は例外をスローする | …, value → …, result | System.OverflowException: 結果がその型で表現できない場合。 |
| conv.ovf.i4.un | conv.ovf.i4.un | スタックの一番上の符号なしの値をint32に変換する。 オーバーフロー時は例外をスローする | …, value → …, result | System.OverflowException: 結果がその型で表現できない場合。 |
| conv.r.un | conv.r.un | 符号なし整数をネイティブサイズの浮動小数点数(F型)に変換する。 Float32で精度を落とさず元の整数を表すことができる場合は float32、それ以外の場合は float64になる | …, value → …, result | なし |
| conv.r4 | conv.r4 | float32に変換し、ネイティブサイズの浮動小数点数(F型)としてスタックに置く | …, value → …, result | なし |
| conv.r8 | conv.r8 | float64に変換し、ネイティブサイズの浮動小数点数(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
conv.i8
ldc.i8 10000000000000
add
call void [mscorlib]System.Console::WriteLine(int64)
ldc.i4 100
ldc.i8 10000000000000
conv.i4
add
call void [mscorlib]System.Console::WriteLine(int32)
ldc.i4 100
ldc.i8 10000000000000
conv.ovf.i4
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. サンプルダウンロード
|