C言語のキャスト動作について自分の中で混乱があったので、RXマイコン用コンパイラ(CC-RX)を使って実動作を確認してみました。
注意:処理系依存の内容を含むため、他のコンパイラでは異なる可能性があります
分かったこと
CC-RXコンパイラで、キャストの実動作を確認したところ、下記でした
- キャスト前後で「ビットパターン」は維持される。「符号付き・なし整数としての値」は変化する事がある
- 小さいサイズの整数にキャスト → 上位ビットを切り捨てる
- 大きいサイズの整数にキャスト → 元データが符号付き整数なら符号拡張、符号なし整数ならゼロ拡張
キャスト動作の例
16bit符号付き整数 8000h(-32768) を、同じサイズの16bit符号なし整数にキャスト
→ ビットパターンを維持して 8000h になり、16bit符号なし整数としての値は +32768 に変化16bit符号なし整数 7FFFh(32767) を、より小さな8bit符号付き整数にキャスト
→ 上位ビットを切り捨てて FFh(-1) になるa) 16bit符号付き整数 8000h(-32768) を、より大きな32bit符号なし整数にキャスト
→ 符号拡張して FFFF8000h(+4294967167) になる
b) 16bit符号なし整数 8000h(+32768) を、より大きな32bit符号付き整数にキャスト
→ ゼロ拡張して 00008000h(+32768) になる
以前、自分は「符号付き・なし整数としての値」に着目して混乱したのですが、「ビットパターン」で見るとシンプルな動作でした
確認内容
1. 方法
次のように、16bit符号付き・なしの整数を、8/16/32bit符号付き・なし整数にキャストして表示します。
// input -------------------------- int16_t i16_input = -128; // cast result -------------------- int8_t i8 = (int8_t) i16_input; int32_t i16 = (int16_t) i16_input; int32_t i32 = (int32_t) i16_input; uint8_t u8 = (uint8_t) i16_input; uint16_t u16 = (uint16_t) i16_input; uint32_t u32 = (uint32_t) i16_input;
2. 環境
- IDE: e2studio V7.4.0
- Compiler: CC-RX V3.01.00 (C99規格を選択)
上記環境でビルドしたプログラムをRXシミュレータ上で動作させ、Renesas Debug Virtual Consoleにprintf出力して確認します。以前のこの投稿とほぼ同じ環境です。
3. 結果
結果から、①元データ(16bit)のビットパターンが維持される、②小さい型にキャストすると上位ビットが切り捨てられる、③大きい型にキャストした時の上位ビットの埋め方(符号拡張・ゼロ拡張)が読み取れます。
符号付き整数からキャスト | 符号なし整数からキャスト |
---|---|
4. テストプログラム
#include <stdio.h> #include <inttypes.h> void main(void); void print_cast_result(int8_t i8, int16_t i16, int32_t i32, uint8_t u8, uint16_t u16, uint32_t u32); void main(void) { // 符号付き変数からのキャスト確認 printf("Cast from signed integer.\n"); int16_t i16_input[] = {-128, -129, -32768, 127, 128, 32767}; for(int n = 0; n < sizeof(i16_input)/sizeof(i16_input[0]); n++) { int8_t i8 = (int8_t) i16_input[n]; int32_t i16 = (int16_t) i16_input[n]; int32_t i32 = (int32_t) i16_input[n]; uint8_t u8 = (uint8_t) i16_input[n]; uint16_t u16 = (uint16_t) i16_input[n]; uint32_t u32 = (uint32_t) i16_input[n]; printf("input---------------------\n"); printf("i16: %04"PRIX16"h(%"PRId16")\n", i16_input[n], i16_input[n]); printf("cast result---------------\n"); print_cast_result(i8, i16, i32, u8, u16, u32); printf("\n"); } printf("\n"); // 符号なし変数からのキャスト確認 printf("Cast from unsigned integer.\n"); uint16_t u16_input[] = {127, 128, 32767, 32768, 65535}; for(int n = 0; n < sizeof(u16_input)/sizeof(u16_input[0]); n++) { int8_t i8 = (int8_t) u16_input[n]; int16_t i16 = (int16_t) u16_input[n]; int32_t i32 = (int32_t) u16_input[n]; uint8_t u8 = (uint8_t) u16_input[n]; uint16_t u16 = (uint16_t) u16_input[n]; uint32_t u32 = (uint32_t) u16_input[n]; printf("input---------------------\n"); printf("u16: %04"PRIX16"h(%"PRIu16")\n", u16_input[n], u16_input[n]); printf("cast result---------------\n"); print_cast_result(i8, i16, i32, u8, u16, u32); printf("\n"); } while(1); } void print_cast_result(int8_t i8, int16_t i16, int32_t i32, uint8_t u8, uint16_t u16, uint32_t u32) { // PRI???は、int32_t型などをprintfで指定するためのマクロ(inttypes.hで定義されている) // https://qiita.com/hnw/items/0f0bbb943bad40cd0784 printf("i8 : %02"PRIX8"h(%"PRId8")\n", i8, i8 ); printf("i16: %04"PRIX16"h(%"PRId16")\n", i16, i16); printf("i32:%08"PRIX32"h(%"PRId32")\n", i32, i32); printf("u8 : %02"PRIX8"h(%"PRIu8")\n", u8, u8 ); printf("u16: %04"PRIX16"h(%"PRIu16")\n", u16, u16); printf("u32:%08"PRIX32"h(%"PRIu32")\n", u32, u32); }
最後に
CC-RX コンパイラのマニュアル「4.1.4 C99 の処理系定義」に上位ビット切り捨ては記載されていました。 しかし、C言語の規格 6.3.1.3 の部分がよく分からず、全ては理解できていない状態です。ただ、実動作としては把握できたので、今回はそれで良しとしてます。
CC-RX コンパイラ ユーザーズマニュアル
(30) 整数型の値を符号付き整数型に変換する際、値が変換先の型で表現できない場合の結果、あるいは生成されるシグナル。(6.3.1.3)
変換先の型の幅でマスクした(上位ビットを切り捨てた)ビット列とします。
JISX3010:2003 プログラム言語C
6.3.1.3 ・・・ 新しい型で表現できない場合,新しい型が符号無し整数型であれば,新しい型で表現しうる最大の数に1加えた数を加えること又は減じることを,新しい型の範囲に入るまで繰り返すことによって得られる値に変換する(49)。
参考にした情報
- 初心者のためのポイント学習C言語 - 少し詳しい型変換の説明
- Qiita @sigma_signature さん - キャスト演算子を理解する
- Qiita @hnw さん - C99のint32_t型などをprintfに渡す方法
- Renesas - CC-RX コンパイラ ユーザーズマニュアル
- kikakurui.com- JISX3010:2003 プログラム言語C
- Microsoft Docs