C言語の型変換規則

C言語の型変換規則について解説します。この解説では、よく使う型変換規則を中心にして、実用の観点から解説します。

ビット幅の小さい符号あり整数からビット幅の大きい符号あり整数への変換(情報喪失なし)

ビット幅の小さい符号あり整数からビット幅の大きい符号あり整数への変換について解説します。

符号あり整数型として、int8_t, int16_t, int32_t, int64_tの場合を想定して解説します。

ビット幅の小さい符号あり整数からビット幅の大きい符号あり整数への変換は数値の絶対値と符号の情報は失われずに保持されます。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int8_tからint32_tへ変換
  int8_t num_i8 = -15;
  int32_t num_i32 = num_i8;
  
  printf("%d\n", num_i32);
}

出力結果です。

-15

2の補数表現で、マイナスを表現していると想定すると、次のようなビットレベルの変換が行われていることになります。

// -15 8bit符号あり整数
1001111

// -15 32bit符号あり整数
10000000 00000000 00000000 00001111

ビット幅の小さい符号なし整数からビット幅の大きい符号なし整数への変換(情報喪失なし)

ビット幅の小さい符号なし整数からビット幅の大きい符号なし整数への変換について解説します。

符号なし整数型として、uint8_t, uint16_t, uint32_t, uint64_tの場合を想定して解説します。

ビット幅の小さい符号なし整数からビット幅の大きい符号なし整数への変換は数値の情報は失われずに保持されます。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int8_tからint32_tへ変換
  uint8_t num_ui8 = 15;
  uint32_t num_ui32 = num_ui8;
  
  printf("%d\n", num_ui32);
}

出力結果です。

15

次のようなビットレベルの変換が行われていることになります。

// 15 8bit符号なし整数
0001111

// 15 32bit符号なし整数
00000000 00000000 00000000 00001111

ビット幅の大きい符号あり整数からビット幅の小さい符号あり整数への変換(情報喪失あり)

ビット幅の大きい符号あり整数からビット幅の小さい符号あり整数への変換について解説します。

符号あり整数型として、int8_t, int16_t, int32_t, int64_tの場合を想定して解説します。

ビット幅の大きい符号あり整数からビット幅の小さい符号あり整数への変換は、上位ビットが切り捨てられて解釈された値になります。

正しく変換できるのは、変換前の値が、変換後の型で表現できる数値の範囲に収まっていた場合です。

正しく変換できた場合のサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int16_tからint8_tへ変換
  int16_t num_i16 = -128;
  int8_t num_i8 = num_i16;
  
  printf("%d\n", num_i8);
}

出力結果です。

-128

2の補数表現で、マイナスを表現していると想定すると、次のようなビットレベルの変換が行われていることになります。

// -128 16bit符号あり整数
11111111 10000000

// -128 8bit符号あり整数
10000000

情報喪失ありのサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int16_tからint8_tへ変換
  int16_t num_i16 = -257;
  int8_t num_i8 = num_i16;
  
  printf("%d\n", num_i8);
}

出力結果です。

-1

2の補数表現で、マイナスを表現していると想定すると、次のようなビットレベルの変換が行われていることになります。

// -257 16bit符号あり整数
11111110 11111111

// -1 8bit符号あり整数
11111111

ビット幅の大きい符号なし整数からビット幅の小さい符号なし整数への変換(情報喪失あり)

ビット幅の大きい符号なし整数からビット幅の小さい符号なし整数への変換について解説します。

符号なし整数型として、uint8_t, uint16_t, uint32_t, uint64_tの場合を想定して解説します。

ビット幅の大きい符号なし整数からビット幅の小さい符号なし整数への変換は、上位ビットが切り捨てられて解釈された値になります。

正しく変換できるのは、変換前の値が、変換後の型で表現できる数値の範囲に収まっていた場合です。

正しく変換できた場合のサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int16_tからint8_tへ変換
  uint16_t num_ui16 = 255;
  uint8_t num_ui8 = num_ui16;
  
  printf("%d\n", num_ui8);
}

出力結果です。

255

次のようなビットレベルの変換が行われていることになります。

// 255 16bit符号なし整数
00000000 10000000

// -128 8bit符号なし整数
10000000

情報喪失ありのサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int16_tからint8_tへ変換
  uint16_t num_ui16 = 257;
  uint8_t num_ui8 = num_ui16;
  
  printf("%d\n", num_ui8);
}

出力結果です。

1

次のようなビットレベルの変換が行われていることになります。

// 257 16bit符号なし整数
00000001 00000001

// 1 8bit符号なし整数
00000001

ビット幅の小さい浮動小数点型からビット幅の大きい浮動小数点型への変換(情報喪失なし)

ビット幅の小さい浮動小数点型からビット幅の大きい浮動小数点型への変換について解説します。

浮動小数点型として、float,doubleの場合を想定して解説します。

ビット幅の小さい浮動小数点型からビット幅の大きい浮動小数点型への変換は数値の情報は失われずに保持されます。

#include <stdio.h>

int main(void) {
  
  // float型からdouble型へ変換
  float num_f = 2.5f;
  double num_d = num_f;
  
  printf("%f\n", num_d);
}

出力結果です。

2.500000

ビット幅の大きい浮動小数点型からビット幅の小さい浮動小数点型への変換(情報喪失あり)

ビット幅の大きい浮動小数点型からビット幅の小さい浮動小数点型への変換について解説します。

浮動小数点型として、float,doubleの場合を想定して解説します。

ビット幅の大きい浮動小数点型からビット幅の小さい浮動小数点型への変換は、変更前の値が、変更後の型が表現できる有効桁数を超えていた場合は、有効桁数が変更後の型が表現できる有効桁数の最大になります。

#include <stdio.h>

int main(void) {
  
  // double型からfloat型へ変換
  double num_d = 2.111111111111111111111111111111;
  float num_f = num_d;
  
  printf("double: %.50f\nfloat:  %.50f\n", num_d, num_f);
}

出力結果です。精度が落ちていることが確認できます。

double: 2.11111111111111116045435665000695735216140747070312
float:  2.11111116409301757812500000000000000000000000000000

符号あり整数型から浮動小数点型への変換(情報喪失あり)

符号あり整数型から浮動小数点型への変換について解説します。

符号あり整数型を浮動小数点型に変換する場合は、数値の情報が失われる場合があります。

double型は64bitです。double型で正しく表現可能な整数型は、最大int32_t型までです。int64_t型の場合は、数値の情報が失われる場合があります。

float型は64bitです。float型で正しく表現可能な整数型は、最大int16_t型まです。int64_t型、int32_t型の場合は、数値の情報が失われる可能性があります。

正しく変換できた場合のサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int32_t型からdouble型へ変換
  int32_t num_i32 = INT32_MAX;
  double num_d = num_i32;
  
  printf("%f\n", num_d);
}

出力結果です。int32_t型の最大値INT32_MAX(2147483647)を、double型で表現できています。

2147483647.000000

数値の情報が失われるサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int32_t型からfloat型へ変換
  int32_t num_i32 = INT32_MAX;
  float num_f = num_i32;
  
  printf("%f\n", num_f);
}

出力結果です。おや、精度が足りなかったため、よく見ると、値が変わっていますね。

2147483648.000000

浮動小数点型から符号あり整数型への変換(情報喪失あり)

浮動小数点型から符号あり整数型への変換について解説します。

浮動小数点型から符号あり整数型へ変換すると、小数点以下の情報が、削除されます。

doubleは、int64_t型への変換であれば、情報喪失はありません。それ以下のビット数の符号あり整数型の場合は、情報喪失が発生する可能性があります。

floatは、int32_t型への変換であれば、情報喪失はありません。それ以下のビット数の符号あり整数型の場合は、情報喪失が発生する可能性があります。

正しく変換できた場合のサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  
  // int32_t型からdouble型へ変換
  double num_d = 33.43;
  int64_t num_i64 = num_d;
  
  printf("%d\n", num_i64);
}

出力結果です。

33

同一サイズの符号あり整数型と符号なし整数型の相互の変換(情報喪失なし)

同一サイズの符号あり整数型と符号なし整数型の相互の変換について解説します。

符号あり整数型として、int8_t, int16_t, int32_t, int64_tの場合を想定して解説します。

符号なし整数型として、uint8_t, uint16_t, uint32_t, uint64_tの場合を想定して解説します。

同一サイズの符号あり整数型と符号なし整数型の相互の変換は、内部のビット表現を変えません。内部のビット表現を、符号ありとして解釈するか、符号なしとして解釈するかという意味の変換のみになります。

同一サイズの符号あり整数型と符号なし整数型の相互の変換を行うサンプルです。

#include <stdio.h>
#include <stdint.h>

int main(void) {
  int8_t num_i8 = -1;
  uint8_t num_ui8= num_i8;
  
  printf("num_ui8: %d\n", num_ui8);
  
  int8_t num_i8_again = num_ui8;
  
  printf("num_i8_again: %d\n", num_i8_again);
}

出力結果。

num_ui8: 255
num_i8_again: -1

ビットレベルでは、何の変換も行われていません。負の値は、2の補数で表現されていると想定します。

// -1 8bit符号あり整数
11111111

// 255 8bit符号なし整数
11111111

異なるポインタ型の相互の変換

異なるポインタ型の相互の変換は、一般的なCコンパイラでは、警告が発生します。ほぼすべての場合において、これは作成者が意図していない動作で、バグだと考えます。

#include <stdint.h>

int main (void) {
  int32_t* nums_i32_ptr;
  int8_t* nums_i8_ptr = nums_i32_ptr;
}

gccにおける警告の内容です。

ポインタについては、以下の記事を参考にしてください。

汎用ポインタ型「void*」とポインタ型の相互の変換

汎用ポインタ型「void*」とポインタ型の相互の変換は、C言語では、どちらの方向でも、仕様上許可されます。C言語が期待する動作ですので、型キャストしなくても、警告はでません。これは、型チェックが厳密なプログラミング言語に慣れ親しんでいると「あれっ、これ大丈夫なんか?」と感じるので、書いておきます。

ただし、汎用ポインタ型が指しているデータが、意図していないポインタ型に代入された場合には、プログラムとしては、間違いですので、気をつけましょう。

#include <stdint.h>

int main (void) {
  int32_t* nums_i32_ptr;
  
  // 他のポインタ型から汎用ポインタ型への型変換
  void* void_ptr = nums_i32_ptr;
  
  // 汎用ポインタ型から他のポインタ型への型変換
  int32_t* nums_i32_ptr2 = void_ptr;
}

ポインタ型を警告を出さずに整数型に変換する、また、その逆の変換

ポインタ型を警告を出さずに整数型に変換する方法について記しておきます。また、警告を出さずに、逆の変換をする方法について書いておきます。

汎用ポインタ型「void*」を、符号あり整数型、または、符号なし整数型に、変換すると一般的な処理系では、警告が発生します。

また、その逆の変換においても、警告が発生します。

このような変換は、一般的には、間違いですが、データのコンテナを、オブジェクト「void*」と整数「int32_t」(あるいは、32bit浮動小数点 float)で、共有したいという、理想的には、嫌だが、実用的には、重複したコードを書きたくないがために、やりたいような場合が、ときどきあります。

そのような場合に、警告を出さずに、型変換を行う方法を記しておきます。intptr_tという、特別な型を使います。intptr_tは、ポインタ型のサイズと同じであることが保証された符号あり整数型で、ポインタ型と符号あり整数型の変換を許可します。符号なし整数型の場合はuintptr_tという型もあります。

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>

int main(void) {
  // 汎用コンテナ
  void** elements = calloc(sizeof(void*), 10);
  
  // ここに、32bit整数型を保存したいんだ。だって、コンテナを32bit整数用に実装するの二重実装になっちゃうし
  // とりあえず、間に合わせたいよー
  int32_t num = 100;
  elements[0] = (void*)(intptr_t)num;
  
  // 取り出し
  int32_t num_again = (intptr_t)elements[0];
  
  printf("%d\n", num_again);
}

出力結果です。

100

これは、厳密には間違い(16bitのCPU処理系ではアウト)ですが、32bit以上のCPU処理系がが、世界のすべてである、と仮定した場合は、正しいプログラムです。

上記の解説はC言語仕様上の型変換規則の解説ではないのですか?

はい。上記の解説はC言語仕様上の型変換規則の解説ではありません。実用でよく使うである型変換を、例示的に示して、C言語仕様上の型変換規則と矛盾がないように解説したものです。

関連情報