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 型の変数 xdouble 型に変換することが可能です。

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オプションだと出力されない・・・。

オススメの参考書(PR)

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示