C言語のキャストについて解説!「符号あり」と「符号なし」の比較・計算は特に危険!

キャスト解説ページのアイキャッチ

ここではC言語における変数の型変換とキャストについて説明します。

型変換

まず型変換について簡単に解説します。型変換とは変数の型を異なる型に変換するものです。C言語には2つの型変換方法があります。

暗黙の型変換

暗黙の型変換とは、ルールに従い、コンパイラが勝手に変数の型を異なる型に変換する変換です。

C言語プログラムは異なる型の変数の演算や代入ができません。

ですので、異なる型同士の変数を用いて演算や代入が行われる場合は、コンパイラがコンパイル時に勝手に一方の変数の型を他方の型に変換します。この型変換が暗黙の型変換です。

暗黙の型変換のルール

暗黙の型変換ではルールに則って型変換が行われます。

代入

異なる型の変数への代入は下記のルールに則って暗黙の型変換が行われます。

代入時のルール
右辺の変数を左辺の変数の型に変換する

演算

異なる型の変数の演算時は下記のルールに則って暗黙の型変換が行われます。

演算時のルール
型のサイズが小さい方の変数を、型のサイズが大きい方の変数に変換する

演算結果も暗黙の型変換後の型となります。例えばint型のサイズは4バイト、double型のサイズは8バイトです(使用PC等の環境によっては異なる場合があります)。

ですので、下記ではint型の変数xがdouble型に変換されてから演算が行われることになります。また演算結果はdouble型となります。

int x = 100;
double y = 2.5;
double z;
z = x * y;

キャスト(明示的型変換)

キャスト(明示的型変換)とは、変数の型を違う型に意図的に・強制的に変換する命令です。例えば下記のようにソースコードを書くと、int型の変数xをdouble型に変換することが可能です。

int x = 100;
double d;
d = (double)x;

キャストの必要性

暗黙の型変換以外に型変換が必要な場合はキャストを使用する必要があります。

分数計算

C言語を始めて最初にキャストの必要性を感じるのがこの分数計算だと思います。

例えば下記のようにプログラムを組むと、結果が0になってしまいます。これは変数aも変数bもint型のため、暗黙の型変換は行われず、計算結果もint型となるためです。そのため分数計算で生じた小数点以下の値は切り捨てされます。変数cに値を代入するときはdouble型に暗黙の型変換が行われますが、すでに小数点以下は切り捨てされてるので、結果は0となります。

int a = 5;
int b = 10;
double c;
c = a / b;

これを解決するのが明示的型変換であるキャストです。下記のように変数aと変数bをdouble型にキャストしてやれば計算結果もdouble型になります。

int a = 5;
int b = 10;
double c;
c = (double)a / (double)b;

ちなみに一方の変数のみdouble型にキャストするのでもうまく動作してくれます。これは一方の変数がdouble型に変換されたので、他方の変数もそれに合わせて暗黙の型変換が行われるためです。

int a = 5;
int b = 10;
double c;
c = a / (double)b;

大きな数の掛け算

大きな数の掛け算を含む計算式はオーバーフローが発生して意図した計算結果が得られない可能性があります。

例えば下記のソースコードを実行すると計算結果は100000になりません。

int a = 100000;
int b = 100000;
int c;
c = a * b / a;

上記の計算はint型同士の計算なので計算結果もint型となりますが、掛け算時にint型の最大値を超える値になるのでオーバーフローが発生します。

これもキャストで解決できます。下記のようにlong long型にキャストしてから演算を行うことで計算結果もlong long型となりオーバーフローが発生しません。

int a = 100000;
int b = 100000;
int c;
c = (long long)a * (long long)b / (long long)a;

ちなみに下記だとオーバーフローが発生します。なぜ発生するのか考えてみてください。

int a = 100000;
int b = 100000;
int c;
c = (long long)(a * b) / (long long)a;

暗黙の型変換の危険性

暗黙の型変換は便利な機能なのですが、これに頼っていると時々罠にハマります。特にハマりやすい罠が、符号ありと符号なしの比較・計算です。

符号ありと符号なしの比較

「符号あり」と「符号なし」の比較の場合、「符号あり」は符号なしに暗黙の型変換が行われて比較が行われます。

例えば下記のプログラムの出力を考えてみてください。

#include <stdio.h>

int main(void){
  int s = -10;
  unsigned int u = 20;

  if(s > u){
    printf("s > u\n");
  } else {
    printf("s <= u\n");
  }

  if(s > (int)u){
    printf("s > (int)u\n");
  } else {
    printf("s <= (int)u\n");
  }

  return 0;
}

出力結果は以下のようになります。

s > u
s <= (int)u

一つ目の比較はuの値20よりもsの値-10の方が大きいと判断されています。これは、比較時に符号ありの変数sが符号なし型に暗黙の型変換が行われたのちに比較が行われるからです。-10をunsigned int型に置き換えると4294967286ですので20よりもかなり大きい数字になり上記のような結果になります。異なる型の比較を行うと暗黙の型変換が行われるので知識がないとハマりやすい罠になります。

これを解決してくれるのが明示的型変換であるキャストです。この暗黙の型変換が行われないように明示的にキャストしてやることが重要です。

2つ目の比較はuをint型(signed int)にキャストしています。これによりint型とint型の比較になりますので、単純に-10と20の比較を行うことができ、意図した結果が得られるようになります。

符号ありと符号なしの計算

こちらも基本的に比較時と同じ考えです。

例えば下のようなプログラムを見てみてください。

#include <stdio.h>

int main(void){
  int s = 10;
  unsigned int u = 20;
  double d;

  printf("s = %d\n", s);
  printf("u = %d\n", u);
  printf("s - u = %d\n", s - u);
  printf("(double)(s - u) = %f\n", (double)(s - u));
  printf("(double)(s - (int)u) = %f\n", (double)(s - (int)u));
  
  return 0;
}

 

実行結果は以下のような表示になります。

s = 10
u = 20
s - u = -10
(double)(s - u) = 4294967286.000000
(double)(s - (int)u) = -10.000000

s – uが-10なのに、それをdouble型にキャストしてやると-10.0000000になるのではなく、4294967286.000000になってしまっていることに注目です。

これも符号ありと符号なしの計算による暗黙の型変換が行われていることが原因です。s – uを計算すると普通に考えれば-10ですが、プログラム上はunsigned int型に暗黙の型変換が行われてしまうので結果は4294967286となっています。さらにこれをdouble型にキャストしているため結果は4294967286.000000となってしまいます。

これについてもunsigned int型の変数uをint型に明示的にキャストし、同じ型に合わせてから計算することで問題を解決することができます

スポンサーリンク

まとめ

暗黙の型変換は便利ですが、これに頼りすぎるとバグが発生する可能性があります。特に、符号ありと符号なし(signedとunsigned)の比較・計算を行うと暗黙の型変換が行われて意図通りの結果が得られなくなります。

型を意識し、異なる型での比較・計算・代入を行う際には明示的にキャストを行う癖をつけましょう。

またこの異なる型での比較はgccのオプションに-Wを付けることでコンパイル時に警告を出すようにすることができます。なぜか-Wallオプションだと出力されない・・・。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です