型変換

異なるデータ型同士の扱い方

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 _int = 10;
double kekka = (double)_int / 4;

変数_intは一時的にdouble型として扱われるだけで、変数自体のデータ型が変換されるわけではありません。

以下のように書けば計算式の結果をdouble型にキャストすることができますが、括弧内の計算の時点でその結果がint型になっているので、正しい値とはなりません。
(変数への代入時は暗黙の型変換が行われるため、無駄な記述となります)


int _int = 10;
double kekka = (double)(_int / 4);

整数拡張

以下はやや上級向けの話です。

計算時の変換では、異なるデータ型同士の計算では大きいほうのデータ型に合わせられる、と説明しました。
実はこれは正確な表現ではありません。

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;

//オーバーフローを防ぐつもり
if (uc1 + uc2 < 255) {
	//この処理が実行されることはない
	uc1 = 255;
} else {
	//こちらが実行される
	uc1 += uc2;
}

if文はまだ説明していませんが、条件分岐を行うための構文です。

unsigned char型の最大値は255です。
unsigned char型変数同士の加算でオーバーフローが発生すると、必ず255未満の値となります。
(255 + 255の場合でも結果は254)
これを利用して、意図しない値が変数に格納されないように上記のようなコードを書いたとします。

しかしこのコードはuc1もuc2も整数拡張により暗黙的にint型に変換され、実際にはint型同士の加算が行われます。
計算結果もint型で「300」となり、if文は実行されずelse文が実行されます。
uc1 += uc2の処理が行われ、オーバーフローが発生し意図しない値がuc1に格納されてしまいます。

暗黙の型変換はルールを知っておかないと思わぬトラブルとなる可能性があります。

上記コードでは省略していますが、より安全なコードにするには、整数値の最大値が格納されているlimit.hをインクルードし、定数を使用すべきです。


#include <stdio.h>
#include <limit.h>

//UCHAR_MAXはunsigned char型の最大値を格納する定数

//省略

unsigned char uc1 = 100;
unsigned char uc2 = 200;

if (uc1 + uc2 < UCHAR_MAX) {
	uc1 = UCHAR_MAX;
} else {
	uc1 += uc2;
}