型変換
異なるデータ型同士の扱い方
char型やshort型などは、それぞれ記憶できる数値の幅に違いがありますが、中身は同じ整数です。
これらのデータ型は互いに変換することができます。
これを型変換といいます。
暗黙の型変換
型変換は、変換を特に指定せずに自動的に行われるものがあります。
これを暗黙の型変換(暗黙的型変換)といいます。
代入時の変換
変数への値の代入時、代入される値は自動的にその変数の型に変換されます。
小数型を整数型に代入した場合、小数点以下が切り捨てられます。
(四捨五入ではない)
double _double = 1234.56;
int _int = _double;
printf("%d", _int);
1234
以下のコードでは、char型からshort型への変換が行われます。
short型のほうが扱えるデータ量が大きいので、問題はありません。
char _char = 127;
short _short = _char;
以下のコードでは、char型のほうが小さいので、short型変数にchar型で扱える以上の数値が入っているとオーバーフローが発生します。
short _short = 1000;
char _char = _short;
細かいことを言うと、整数のリテラルはint型として扱われますから、int型以外の変数に代入するとこれも暗黙の型変換が行われます
//int型からshort型に変換
short _short = 0;
演算時の変換
暗黙の型変換は、計算時にも行われます。
char _char = 10;
short _short = 20;
long _long = 30;
long kekka = _char + _short + _long;
4行目では、まずchar型とshort型の計算が行われます。
この時の結果は、大きい方のデータ型であるshort型に合わせられます。
次はshort型とlong型との計算となり、結果は大きい方であるlong型に合わせられます。
つまり、データ型同士が異なる値の計算は、大きいデータ型の方に合わせられます。
ただし、整数型と小数型の場合は小数型に合わせられます。
long long _longlong = 10;
float _float = 20.0f;
double kekka = _longlong + _float ;
long long型とfloat型ではlong long型のほうがサイズが大きいですが、小数型であるfloat型に合わせられます。
最終的にはdouble型変数に代入しているので、double型となります。
符号付き型と符号なし型の演算
同じサイズのデータ型で、符号付き(signed)と符号なし(unsigned)同士の演算では、符号なし型のほうに合わせられます。
signed int si = 1;
unsigned int ui = 2;
//計算結果はunsigned int
si + ui;
このルールは割と重要で、知らないとバグを引き起こしやすいです。
int si = -1;
unsigned int ui = 1;
if(si < ui) {
printf("if文実行");
} else {
printf("else文実行");
}
else文実行
if文はまだ説明していませんが、条件分岐を行うための構文です。
普通に考えれば-1 < 1
は真(成立)なので、printf("if文実行");
が実行されるように思います。
しかし実際にはelse文が実行されます。
これは暗黙的な型変換により、int型がunsigned int型に変換される事により起こります。
同じサイズの符号付き整数から符号なし整数への変換は、正の数(プラス値)の場合は値は変化しません。
負の数(マイナス値)の場合はラップアラウンドが起こり、そのデータ型で表現できる範囲の値になるまで値が繰り返されます。
つまり、-1を符号なし型に変換すると、その型で表現できる最大の値になります。
そのため上記コードの比較は「unsigned int型の最大値」と「1」を比較していることになるので、意図した通りの動作にならないのです。
上記コードを期待通りに動作させるには、後述するキャストを使用します。
int si = -1;
unsigned int ui = 1;
if(si < (int)ui) {
printf("if文実行");
} else {
printf("else文実行");
}
if文実行
ただしこの方法は、unsigned int型の値がint型の正の値の上限を超えていた場合にオーバーフローが発生します。
どのようにエラーに対処するのが良いかはケースバイケースですが、そもそも異なるデータ型を使用しなければこのような問題は起こらないので、第一に検討すべきかもしれません。
明示的な型変換(キャスト)
型変換は自動で行われるもののほか、自分で変換を指定することもできます。
//暗黙の型変換
double kekka1 = 10 / 4;
//明示的な型変換
double kekka2 = (double)10 / 4;
最初のコードは、int型同士の計算なので結果もint型となります。
その結果、小数点以下が切り捨てられ数学的に正しい値は得られません。
二番目のコードは、片方の数値の前に(double)
という記述があります。
このとき、この10はdouble型として扱われることになります。
これを明示的な型変換(キャスト)と言います。
丸括弧の中にデータ型名を記述したものをキャスト演算子と言います。
10がdouble型として扱われることで、double型とint型の計算となり、結果はdouble型となるため正しい計算結果を得られます。
上のコードのように数値を直接書く場合(リテラル)は、10を10.0と書けばdouble型として扱うことはできます。
しかし変数の場合はこの書き方では対処できないため、明示的な型変換が必要になります。
int n1 = 10;
int n2 = 4;
double kekka = (double)n1 / n2;
この変数n
は一時的にdouble型として扱われるだけで、変数自体のデータ型が変換されるわけではありません。
以下のコードは、変数n1
もn2
もint型なので、int型同士の演算の結果はint型になります。
つまり丸括弧内の計算の時点で小数点以下の値は切り捨てられるので、数学的に正しい結果は得られません。
そのあとにdouble型へのキャストを行っていますが、double型変数への代入時に暗黙的に型変換されるため、このキャストは無くても結果は変わりません。
int n1 = 10;
int n2 = 4;
double kekka = (double)(n1 / n2);
//値は2
整数拡張
以下はやや上級向けの話です。
演算時の変換では、異なるデータ型同士の計算では大きいほうのデータ型に合わせられる、と説明しました。
実はこれは正確な表現ではありません。
int型よりも小さい整数型は、計算の前に暗黙の型変換が行われます。
対象のデータ型で表現できる値の範囲がint型で表現できる範囲である場合、int型に変換されます。
int型の範囲に収まらない場合はunsigned int型に変換されます。
これを整数拡張と言います。
以下の場合、4行目の処理は計算の前にchar型とshort型はint型に整数拡張されます。
long型は「int型より小さい」には該当しないため整数拡張は行われません。
最終的に「int型とlong型の計算」が行われ、大きいほうであるlong型に合わせられます。
char _char = 10;
short _short = 20;
long _long = 30;
long kekka = _char + _short + _long;
整数拡張はオーバーフローによる値の変化を防ぐための暗黙の型変換です。
signed char c1 = 100;
signed char c2 = 100;
signed char c3 = (c1 + c2) / 2;
signed char型は-128~127の範囲なので、c1 + c2
の計算時にchar型の範囲を超えます。
しかし整数拡張により暗黙的にint型に変換されるため、オーバーフローは起こらずに計算が可能です。
結果としてc3
にはsigned char型の範囲である「100」が保存されます。
もう一つ例を見てみます。
//このコードのuc1はオーバーフローが発生する
unsigned char uc1 = 100;
unsigned char uc2 = 200;
unsigned char uc3 = uc1 + uc2;
//オーバーフローを防ぐつもり
if (uc1 + uc2 < 255 && uc1 + uc2 < uc1)
{
//この処理が実行されることはない
uc1 = 255;
}
else {
//こちらが実行される
uc1 += uc2;
}
printf("%d", uc1);
44
if文はまだ説明していませんが、条件分岐を行うための構文です。
unsigned char型の最大値は255です。
unsigned char型変数同士の加算でオーバーフロー(ラップアラウンド)が発生すると、必ず255未満の値となります。
(255 + 255
の場合でも結果は254)
これを利用して、意図しない値が変数に格納されないように上記のようなコードを書いたとします。
しかしこのコードはuc1
もuc2
も整数拡張により暗黙的にint型に変換され、プログラム実行時にはint型同士の加算が行われます。
計算結果もint型で「300」となり、if文は実行されずelse文が実行されます。
uc1 += uc2
の処理が行われ、オーバーフローが発生し意図しない値がuc1
に格納されてしまいます。
暗黙の型変換はルールを知っておかないと思わぬトラブルとなる可能性があります。
上記コードでは省略していますが、より安全なコードにするには、整数値の最大値が格納されているlimits.h
をインクルードし、定数を使用すべきです。
#include <limits.h>
int main()
{
char a = CHAR_MAX;
signed char b = SCHAR_MAX;
unsigned char c = UCHAR_MAX;
signed short d = SHRT_MAX;
unsigned short e = USHRT_MAX;
signed int f = INT_MAX;
unsigned int g = UINT_MAX;
signed long h = LONG_MAX;
unsigned long i = ULONG_MAX;
signed long long j = LLONG_MAX;
unsigned long long k = ULLONG_MAX;
}