C++において、型変換(以降:キャスト)は頻繁に使用される操作の一つです。
しかし、キャストがコンパイラ、または実行時にどのように動作しているのか、知らなくても何とかなることが多いですが、キャストの内部処理に興味を惹かれたのでこの記事を作成しました。
この記事では特に、static_cast
についてどのようなどうなになっているのかを、実行時のレジスタ上のデータ変換を中心に解説し、さらにアセンブリコードも交えて詳しく説明していきます。
ここでは、なぜstatic_castのことを調べようと思ったのかについての話なので、興味がない方は読み飛ばしてください
- 疑問1:
static_cast
はよく使うが、どこでどのようにデータを変換しているのか気になる - 疑問2: キャストによってデータが失われる(切り落とされる)可能性がある場合、そのキャストはどのように扱われるのか?
これらの疑問点が解決できることを目標に今回の記事を作成しました。
まず、キャストがどのように動作するかを理解するためには、メモリ
とレジスタ
の関係を知る必要があります。
例: int
からchar
へのキャスト
int i = 300;
char c = static_cast<char>(i);
この例では、int
型の300をchar
型にキャストしています。
int
型は通常4バイト(32ビット)、char
型は1バイト(8ビット)です。
このような場合、char
型の範囲(通常は-128
から127
)に収まらない値をキャストすると、データの上位ビットが切り落とされ、このキャストの実行結果は、44になります。
上位ビットが切り落とされるというのは、以下のようなイメージです。
300(10進) = 1 0010 1100(2進)
int型は32ビットなので、それを8ビットにするために下位ビットから8ビットまでで切り離します
char c = 0010 1100(2進)
char c = 32 + 8 + 4 = 44(10進)
なぜレジスタにロードするのか
キャストが実行されるとき、データはまずメモリからレジスタにロードされ、そこで必要な型変換が行われます。
これをアセンブリコードで詳しく見てみましょう。
アセンブリコードで見る型変換の実際
以下のコードをコンパイルし、生成されたアセンブリコードを確認します。
int main() {
int i = 300;
char c = static_cast<char>(i);
return 0;
}
コンパイルされたアセンブリコードの一部は次のようになります。
mov dword ptr [i], 12Ch ; i に 300 (12Ch) を格納
mov eax, dword ptr [i] ; i の値を eax レジスタにロード
mov byte ptr [c], al ; eax の下位8ビットを c に格納
アセンブリコードの解説
- メモリに
i
の値(300、16進数で12C
)を格納しています。mov dword ptr [i], 12Ch
- メモリ上の
i
の値をeax
レジスタにロードします。この時点で、eax
には300
が格納されます。mov eax, dword ptr [i]
eax
レジスタの下位8ビットをc
に格納します。al
はeax
の下位8ビットを指します。ここでは、300
の下位8ビット(16進数で44
、10進数で68
、ASCIIコードでD
)がc
に格納されます。mov byte ptr [c], al
キャストは基本的にメモリ
から計算対象のデータをレジスタ
に読み込むときに変換します。
なので厳密にはデータのやり取りの時に型変換を行っていることになります。
プログラムの原則として、元のデータ(メモリ上のデータ)を変更するのはあまりいい方法ではないとされています。
なので、メモリからデータを読み込む際に型変換を行い、レジスタ内では指定した型としてデータを扱うことで計算結果を型変換後の型で計算したものとして扱うことができるということでした。
今回は、書籍にはあまり乗っていないようなニッチな部分を調べてみました。
static_cast以外に、dynamic_castなどほかにもキャストの種類がありますが、これらはもう少し知識がついてきたらまた調べてみようと思っています。
個人的にはこのような根本的な考え方を学ぶのが好きなので、また気になったことが出てきたら調べてまとめてみようと思います。