C言語のきほん|型変換の落とし穴

同じ式なのに答えが変わる!?C言語の型変換を味方にして、切り捨て・あふれ・ズレを防ごう。

C言語は、型(int や double など)をとても大切にする言語です。
でも実は、私たちが意識しないところで「型を合わせる変換」が勝手に行われています。これが 暗黙の型変換 です。

さらに、開発者が自分で指定して変換する 明示的な型変換(キャスト) もあります。

ここで落とし穴になりやすいのが、

  • 計算は int のまま行われて、最後に double に入っただけだった
  • double を int に入れたら小数が消えた
  • 符号付きと符号なしが混ざって、予想外に大きな値になった
  • 小さい型(char, short)で計算したつもりが、実は int に拡張されていた

みたいな「知らないとハマる系」です。

この記事では、型変換がどこで起きるか(演算時/代入時)を整理しながら、よくあるミスを防ぐ考え方を丁寧に説明します。

型変換は2種類ある(暗黙と明示)

種類だれが変換する?目的
暗黙の型変換コンパイラが自動でやるint と double を足すと int が double に寄せられる演算を成立させる
明示的な型変換(キャスト)開発者が指定する(double)a / b意図した型で計算する

大事なのは「変換は一時的に行われることが多く、変数自体の型は変わらない」という点です。

暗黙の型変換(演算時に起きる)

小さい整数型はまず int に広げられる(整数拡張)

char や short のような小さい整数型は、式の中ではまず int(または unsigned int)に広げられることが多いです。これを 整数拡張 と呼びます。

「え、char で計算してるのに?」と思うんですが、式の中では int にしてから計算してくれるイメージです。

サイズ確認のイメージ

ファイル名:6_8_1.c

#include <stdio.h>

int main(void)
{
    char c = 1;     /* charは小さい型 */
    short s = 1;    /* shortもintより小さいことが多い */

    printf("sizeof(char) = %zu\n", sizeof(c));
    printf("sizeof(short) = %zu\n", sizeof(s));

    /* 演算するときはintに拡張されることが多い */
    printf("sizeof(c + c) = %zu\n", sizeof(c + c));
    printf("sizeof(s + s) = %zu\n", sizeof(s + s));
    printf("sizeof(c + s) = %zu\n", sizeof(c + s));

    printf("メッセージ: 小さい型でも式の中ではintに広がることがあるよ。\n");
    return 0;
}

ここで確認したいのは、c や s 自体の sizeof は小さいのに、c + c の sizeof が大きくなることがある、という点です。

さらに大きい型へ寄せられる(通常の算術型変換)

整数拡張のあと、混ざっている型の中で「より広い型」に合わせて計算されます。

よくある流れ(ざっくりのイメージ)はこんな感じです。

int < unsigned < long < unsigned long < float < double < long double

たとえば int と double を足すと、int が double に変換されて計算されます。

ファイル名:6_8_2.c

#include <stdio.h>

int main(void)
{
    int a = 10;
    double b = 2.5;

    printf("a + b = %.1f\n", a + b);
    printf("メッセージ: intはdoubleに寄せられて計算されるよ。\n");

    return 0;
}

暗黙の型変換(代入時に起きる)

小さい型へ入れると情報が失われることがある

代入は「左辺の型に合わせて右辺が変換される」ので、ここが落とし穴になりやすいです。

小さい型へ代入(危険が出やすい)

  • double を int に入れると小数が切り捨てられる
  • 大きな整数を小さな型へ入れると、範囲外になって変な値になることがある
  • 符号付きから符号なしに入れると、見た目が大きく変わることがある

ファイル名:6_8_3.c

#include <stdio.h>

int main(void)
{
    double price = 398.75;  /* 本当は小数も大事な値 */
    int rounded;

    rounded = price;  /* 暗黙変換:小数点以下が切り捨てられる */
    printf("元の値: %.2f\n", price);
    printf("intに入れると: %d\n", rounded);

    printf("メッセージ: 代入で勝手に切り捨てが起きることがあるよ。\n");
    return 0;
}

この例では 398.75 を int に入れるので、398 になります。
「四捨五入」ではなく「切り捨て」なのがポイントです。

大きい型へ代入(比較的安全)

int を double に入れるような「広げる方向」は、値や符号が保たれやすく安全です。

ファイル名:6_8_4.c

#include <stdio.h>

int main(void)
{
    int i = -3;
    double d = i;

    printf("i = %d\n", i);
    printf("d = %.1f\n", d);
    printf("メッセージ: 小さい型から大きい型は安全に代入されやすいよ。\n");

    return 0;
}

明示的な型変換(キャスト)で意図をはっきりさせる

いちばん多い落とし穴:割り算が int のまま終わっていた

文書にある落とし穴は、超重要なのでしっかり押さえます。

たとえば、a と b が int だと、a / b は 整数の割り算 になります。
その結果が 2 になって、あとから double に入れても 2.0 になります。

ファイル名:6_8_4.c

#include <stdio.h>

int main(void)
{
    int total_apples = 5;
    int people = 2;

    double per1;
    double per2;

    /* まずint同士で割り算してしまう(小数が消える) */
    per1 = total_apples / people;

    /* 先にdoubleにしてから割る(小数が出る) */
    per2 = (double)total_apples / people;

    printf("そのまま割ると: %.1f個ずつ\n", per1);
    printf("キャストして割ると: %.1f個ずつ\n", per2);

    printf("メッセージ: どの段階でdoubleにするかが超大事!\n");
    return 0;
}

どこでキャストするのが正解?

コツは「計算が始まる前」に型を変えることです。

書き方中身の流れ期待に合う?
x = a / b;intで割る → 結果をdoubleへ小数が欲しいときは合わない
x = (double)a / b;aをdouble化 → doubleで割る合う
x = (double)(a / b);intで割る → 結果をdoubleへ合わない(手遅れ)

最後のパターンが特に罠です。「キャストしてるのに…」ってなりやすいので、計算の順序を意識するとスッキリします。

もう一つの落とし穴:符号付きと符号なしが混ざる

ここは処理系や型の幅にも影響されやすいので、まず考え方だけやさしく押さえます。

  • signed(符号付き)と unsigned(符号なし)が混ざると、unsigned 側に寄せられて比較されることがあります
  • その結果、負の値がとんでもなく大きい正の値として扱われることがあります

「比較したら当然こうなるはず」が崩れる代表例なので、慣れてきたら必ず一度は確認しておく分野です。

型変換の落とし穴を避ける考え方

まずはこの3つを意識すると安全

意識することありがちな事故
計算が行われる型は何か?int同士の割り算で小数が消える
代入先の型は何か?double→intで切り捨て、範囲外代入
混ざっている型は何か?signed/unsigned混在で比較が崩れる