はい、今回は理論になります。ILでは幾分特殊な数値型の扱い方します。これからも重要となる項目になると思います。
もっとも基本的な型の一つです。まず、基本的な数値型についてここに書きましょう。
これらのうち、「native int」についてはまた後で解説します。これは普通の整数演算には使わないものです。
話を戻します。ここに書かれた型は「.locals」の中では通用する型です。これらについてはILは専用の命令を持っており一例としては先の「add」などです。
で、重要なのはここからです。CLR/CTSの実行スタックは整数型としては「4バイトの符号付き整数」「8バイトの符号付き整数」「ネイティブ型の整数」しか区別しません。ネイティブ型については後回しにするとして、どういう事か説明します。
ILにおける「add」という命令は整数型の足し算であろうと、浮動小数点型の足し算であろうと通用します。これは実行時のコンパイルにおいて、スタックの型をコンパイラが追跡してコードを生成しているためです。
この追跡時には、整数型は上の3種類にしか分類されない事をしっかりと覚えておいてください。
しかしながら、これはあくまでスタック上のデータのタグ付け(そのデータがどの方かを示す付加情報)問題です。命令では符号なし演算を行う事が出来るものもありますし、そのような振る舞いをサポートする命令も充実してます。
つまり、ILコードでは1~2バイトの足し算は行うことが出来なくて、それらはスタックにロードした時に自動的にint32の符号付きのデータ型となり、演算されるということです。これは、ILの実行スタックの特性です。ですが、そこででた演算結果に対して、それを1~2バイトの整数として振る舞わせる命令を噛ませることによってそのように操作することも可能というわけです。
高級言語、たとえばC#などではこの操作は隠蔽されており何一つ考えることなく扱う事が出来るようになっているというわけです。これはコンパイラがそのような操作に適したようにコードを生成しているためです。
この挙動はなかなか面白いと思いませんか?Javaではどうなのか誰か教えていただけるとありがたいです。
1~2バイトの整数型はロード時にint32へ「0拡張」又は「符号拡張」がなされます。また、ストア時には切り捨てされます。この動作は決定事項です。ただし、ストアされる前に然るべき型への変換や、オーバーフローの検知はあらかじめ用意されている命令で保証されています。その命令についてもそのうちにちゃんと説明します。
ILでは2種類の型が用意されています。
しかし、スタック上ではこの二つはFと表現される内部の浮動小数点数として扱われます。ロードされたfloat32や、float64は自動的にFに変換されます。このFはそれぞれの型について十分な表現サイズを持っています。このFのサイズと扱いは実装依存ですが、色々な制約が掛けられており、計算上不都合は生じないはずです。
また、特に明示しなくても、Fはfloat32やfloat64にストアできます。
このFはメソッド間のデータの受け渡しや変数への保存は出来ず、コードに書かれるデータ型はfloat32またはfloat64になります。しかし、ストアしたデータが変更されることなくロードされる場合は内部表現が維持される可能性もあります。ただし、明示的な変換が行われた場合はそのデータ型へとなります。
と、結構特殊な扱いになっています。ちょっと気になる点は一体どちらの演算の方が効率的かと言うことでしょうか?まあ、完璧に実装依存です。内部的なFはfloat32とfloat64で違うサイズの内部表現になっている実装も考えられます。
とりあえず、浮動小数点数については同じFとして、サイズにかかわらず演算できます。
色々と話足りないですが、頭が混乱してきたので今回はこの辺で。次回は・・・何にしましょうか?