C言語のデータ型まとめ 種類の違いと選び方、値の範囲を解説

C言語のプログラムは、変数に「どのくらいの大きさの値を入れるのか」「どの範囲と精度が必要なのか」を型で約束しながら書き進めます。

本稿では、整数・浮動小数点・文字・真偽値の基本から、サイズや範囲の確認方法、32bit/64bit差、printfの書式、型変換と安全な選び方まで、入門者向けに丁寧に解説します。

C言語のデータ型の基本とサイズ

データ型の役割とビット幅

C言語のデータ型は、メモリ上の値の解釈方法を決めます。

例えば整数型は2の補数表現(多くの処理系で採用)により負数も扱え、浮動小数点型はIEEE 754(多くの処理系)で小数を近似します。

ビット幅が大きくなるほど表現できる範囲は広がりますが、必要以上に大きい型はメモリやキャッシュ効率、演算速度に影響する場合があります。

従って、用途に応じて「十分な範囲」「必要な精度」を満たす最小限の型を選ぶのが基本です。

sizeofとlimits.h/float.hで範囲確認

コンパイル環境での実際のサイズや範囲は、sizeof演算子やヘッダ定数で確認できます。

次のサンプルは代表的な型のサイズと最小値・最大値を表示します。

C言語
// 型のサイズと範囲を調べるサンプル
// コンパイル例: gcc -std=c11 -Wall -Wextra -O2 types_info.c -o types_info
#include <stdio.h>
#include <limits.h>   // 整数の範囲(CHAR_MIN, INT_MAX など)
#include <float.h>    // 浮動小数点の範囲(FLT_MIN, DBL_MAX など)
#include <stdint.h>   // SIZE_MAX など
#include <stddef.h>   // ptrdiff_t

int main(void) {
    printf("== サイズ(バイト) ==\n");
    printf("char           : %zu (CHAR_BIT=%d)\n", sizeof(char), CHAR_BIT);
    printf("signed char    : %zu\n", sizeof(signed char));
    printf("unsigned char  : %zu\n", sizeof(unsigned char));
    printf("short          : %zu\n", sizeof(short));
    printf("int            : %zu\n", sizeof(int));
    printf("long           : %zu\n", sizeof(long));
    printf("long long      : %zu\n", sizeof(long long));
    printf("size_t         : %zu\n", sizeof(size_t));
    printf("ptrdiff_t      : %zu\n", sizeof(ptrdiff_t));
    printf("void*          : %zu\n", sizeof(void*));
    printf("float          : %zu\n", sizeof(float));
    printf("double         : %zu\n", sizeof(double));
    printf("long double    : %zu\n\n", sizeof(long double));

    printf("== 範囲(整数) ==\n");
    printf("char (plain)   : %d .. %d\n", CHAR_MIN, CHAR_MAX);             // 実装依存(符号付き/なし)
    printf("signed char    : %d .. %d\n", SCHAR_MIN, SCHAR_MAX);
    printf("unsigned char  : %u .. %u\n", 0u, UCHAR_MAX);
    printf("short          : %d .. %d\n", SHRT_MIN, SHRT_MAX);
    printf("unsigned short : %u .. %u\n", 0u, USHRT_MAX);
    printf("int            : %d .. %d\n", INT_MIN, INT_MAX);
    printf("unsigned int   : %u .. %u\n", 0u, UINT_MAX);
    printf("long           : %ld .. %ld\n", LONG_MIN, LONG_MAX);
    printf("unsigned long  : %lu .. %lu\n", 0ul, ULONG_MAX);
    printf("long long      : %lld .. %lld\n", LLONG_MIN, LLONG_MAX);
    printf("unsigned long long: %llu .. %llu\n", 0ull, ULLONG_MAX);
    printf("size_t (max)   : 0 .. %zu\n", SIZE_MAX);
    printf("ptrdiff_t      : %td .. %td\n\n", (ptrdiff_t)PTRDIFF_MIN, (ptrdiff_t)PTRDIFF_MAX);

    printf("== 範囲(浮動小数点, 正規化数の最小) ==\n");
    printf("float       : min=%.9g max=%.9g (digits=%d)\n", FLT_MIN, FLT_MAX, FLT_DIG);
    printf("double      : min=%.17g max=%.17g (digits=%d)\n", DBL_MIN, DBL_MAX, DBL_DIG);
    printf("long double : min=%Lg max=%Lg (digits=%d)\n", LDBL_MIN, LDBL_MAX, LDBL_DIG);
    printf("epsilon     : float=%g double=%g long double=%Lg\n", FLT_EPSILON, DBL_EPSILON, LDBL_EPSILON);
    return 0;
}
実行結果
== サイズ(バイト) ==
char           : 1 (CHAR_BIT=8)
signed char    : 1
unsigned char  : 1
short          : 2
int            : 4
long           : 4
long long      : 8
size_t         : 8
ptrdiff_t      : 8
void*          : 8
float          : 4
double         : 8
long double    : 16

== 範囲(整数) ==
char (plain)   : -128 .. 127
signed char    : -128 .. 127
unsigned char  : 0 .. 255
short          : -32768 .. 32767
unsigned short : 0 .. 65535
int            : -2147483648 .. 2147483647
unsigned int   : 0 .. 4294967295
long           : -2147483648 .. 2147483647
unsigned long  : 0 .. 4294967295
long long      : -9223372036854775808 .. 9223372036854775807
unsigned long long: 0 .. 18446744073709551615
size_t (max)   : 0 .. 18446744073709551615
ptrdiff_t      : -9223372036854775808 .. 9223372036854775807

== 範囲(浮動小数点, 正規化数の最小) ==
float       : min=1.17549435e-38 max=3.40282347e+38 (digits=6)
double      : min=2.2250738585072014e-308 max=1.7976931348623157e+308 (digits=15)
long double : min=1.06581e-312 max=1.06581e-312 (digits=18)
epsilon     : float=1.19209e-07 double=2.22045e-16 long double=1.06581e-312

32bit/64bitの違いと環境依存

Cの型サイズは処理系に依存します。

代表的なデータモデルは次の通りです。

  • ILP32(多くの32bit Unix系): int/long/ポインタのうち、int=32bit, long=32bit, ポインタ=32bit
  • LP64(多くの64bit Unix系: Linux, macOS): int=32bit, long=64bit, ポインタ=64bit
  • LLP64(64bit Windows/MSVC): int=32bit, long=32bit, long long=64bit, ポインタ=64bit
ILP32
LP64
LLP64

ILP32 では、intlong・ポインタはいずれも 32bit 幅を持ちます。

これは 32bit CPU 上の Unix 系 OS で一般的に使われるデータモデルです。

メモリ空間が 4GB に制限されるため、大規模データ処理には不利です。

LP64 では int は 32bit のままですが、long とポインタは 64bit に拡張されます。

これにより、大きなメモリ空間を扱える一方で、long のサイズが 64bit になる点に注意が必要です。

Linux や macOS の 64bit 環境で広く採用されています。

LLP64 では intlong は 32bit のままに保たれ、long long とポインタが 64bit になります。

これは Windows が C 言語の long の互換性を重視した設計によるものです。

64bit Windows 環境での Visual C++ で採用されています。

次の表は典型的なサイズ(バイト)の目安です。

データモデルshortintlonglong longvoid*size_t
ILP32(32bit)244844
LP64(Unix系64bit)248888
LLP64(Windows 64bit)244888

同じソースでも、longのサイズがOSで違うなどの差があり得ます。

実際の値は前節のプログラムやsizeofで必ず確認するのが安全です。

基本の型グループ(整数/浮動小数点/文字)

  • 整数型: signed/unsigned char, short, int, long, long long など。個数やインデックス、ビットフラグに向いています。
  • 浮動小数点型: float, double, long double。測定値、割合、統計量などの小数に使います。
  • 文字・文字列: char(1バイト文字の単体)、char[](ヌル終端文字列)。テキスト処理の基本です。

整数型の種類(int/short/long)と選び方

符号付きとunsignedの違い

符号付きは負の値を表現できますが、符号なしは0以上の値のみを表します。

表現範囲はビット数に比例し、符号なしの方が最大値は約2倍になります。

負数を取り得ないカウントやビット演算にはunsignedが適しますが、符号付きと符号なしを混在させた比較や演算は意図しない型変換を招くため注意が必要です。

特に符号付き整数のオーバーフローは未定義動作です。

long longや大きな整数の扱い

32bit環境ではlongが32bitのことが多く、64bitの整数が必要ならlong longを使います。

リテラルにはLL(例: 1234567890123LL)を付け、printfでは%lld(符号付き)や%llu(符号なし)を使います。

環境差を避ける目的で、正確なビット幅が必要な場合は後述のstdint.hの固定幅整数型を優先します。

固定幅整数型(stdint.h)の利点

stdint.hはビット幅が明確な型を提供します。

例として、int32_tは常に32bitの符号付き整数、uint64_tは64bitの符号なし整数です。

これにより、ファイル形式や通信プロトコルなどで「何bitか」が仕様の一部である場合に安全です。

printfにはinttypes.hのマクロ(例: PRId32, PRIu64)を使うと移植性が高まります。

C言語
// 固定幅整数とprintfマクロの例
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    int32_t a = INT32_MAX;
    uint64_t b = 12345678901234567890ull;
    printf("a(int32_t) = %" PRId32 "\n", a);
    printf("b(uint64_t) = %" PRIu64 "\n", b);
    return 0;
}
実行結果
出力例:
a(int32_t) = 2147483647
b(uint64_t) = 12345678901234567890

size_t/ptrdiff_tの使いどころ

メモリサイズや配列の添字にはsize_t(非負の大きな整数)を使います。

ポインタ同士の減算結果にはptrdiff_t(符号付き)が用いられます。

これらはアーキテクチャに合わせて十分な幅を持つため、intで代用しない方が安全です。

C言語
// size_t と ptrdiff_t の基本
#include <stdio.h>
#include <stddef.h>

int main(void) {
    int a[10] = {0};
    int *p = &a[2];
    int *q = &a[8];
    size_t n = sizeof(a) / sizeof(a[0]); // 要素数
    ptrdiff_t d = q - p;                  // 要素単位の距離

    printf("配列の要素数(size_t): %zu\n", n);
    printf("q - p の距離(ptrdiff_t): %td\n", d);
    return 0;
}
実行結果
出力例:
配列の要素数(size_t): 10
q - p の距離(ptrdiff_t): 6

printfの書式(%d/%u/%ld)と整数リテラル

整数の主な書式指定子は次の通りです。

型に合わない指定子は未定義動作になり得るため、必ず合わせます。

書式
int%dprintf(“%d”, (int)x);
unsigned int%uprintf(“%u”, (unsigned)x);
long%ld / %luprintf(“%ld”, l);
long long%lld / %lluprintf(“%lld”, ll);
short%hd / %huprintf(“%hd”, s);
size_t%zuprintf(“%zu”, n);
ptrdiff_t%tdprintf(“%td”, d);
固定幅(int32_t等)%”PRId32″ 等printf(“%” PRId32, v);

リテラルの接尾辞も重要です。

意味
42Uunsigned int
3000000000ULunsigned long
10LL / 10ULLlong long / unsigned long long
0xFFu16進のunsigned int
‘A’文字リテラル(型はint)
C言語
// 整数のリテラルとprintfの組み合わせ例
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>

int main(void) {
    int i = 42;
    unsigned u = 42U;
    long l = 100000L;
    long long ll = 10000000000LL;
    int32_t i32 = -123;
    uint64_t u64 = 18446744073709551615ull;

    printf("int=%d, unsigned=%u, long=%ld, long long=%lld\n", i, u, l, ll);
    printf("i32=%" PRId32 ", u64=%" PRIu64 "\n", i32, u64);
    return 0;
}
実行結果
int=42, unsigned=42, long=100000, long long=10000000000
i32=-123, u64=18446744073709551615

浮動小数点・文字・真偽値のデータ型

float/double/long doubleの選び方

  • doubleはデフォルトの小数型として推奨されます。精度(約15~17桁)と速度のバランスが良好です。
  • floatはメモリや帯域が厳しい場合や大量配列での省メモリ化に有効ですが、精度(約6~9桁)は低下します。
  • long doubleは更に高精度ですが、処理系によりdoubleと同じ場合もあります(MSVC)。必要性が明確なときに選びます。

リテラルでは1.23ffloat1.23double1.23Llong doubleです。

charとsigned/unsigned charの違い

charは文字の格納に使いますが、charが符号付きかどうかは実装依存です。

一方、signed charunsigned charは明確に別型で、バイト列処理で符号問題を避けるにはunsigned charが便利です。

テキストとして扱うときはchar(プレーン)を使い、数値として計算するならsigned/unsigned charを明示します。

ビット数はCHAR_BITで取得します(典型的には8)。

文字列(char配列)の基本

Cの文字列は'\0'(ヌル文字)で終端されたchar配列です。

配列の長さは終端を含めて十分に確保し、書き込みは境界を超えないように注意します。

C言語
// 文字・文字列の基本
#include <stdio.h>
#include <string.h>

int main(void) {
    char c = 'C';                 // 文字(型はcharだが、文字リテラルはintとして扱われる場面もある)
    const char *p = "C language"; // 文字列リテラルは静的領域(書き換え禁止)
    char s[16];                   // 十分な大きさのバッファを用意

    // 安全なコピー: snprintfは終端も保証
    snprintf(s, sizeof(s), "%s", p);

    printf("char: %c, 文字コード: %d\n", c, (int)c);
    printf("文字列: %s (長さ: %zu)\n", s, strlen(s));
    return 0;
}
実行結果
出力例:
char: C, 文字コード: 67
文字列: C language (長さ: 10)

_Boolとstdbool.hのbool

C99では_Boolが導入され、stdbool.hをインクルードするとbool/true/falseを使えるようになります。

boolは整数型の一種で、printfでは%dで出力します。

C言語
#include <stdio.h>
#include <stdbool.h>

int main(void) {
    bool a = true;
    bool b = (5 > 10);
    printf("a=%d, b=%d\n", a, b); // 1と0で表示
    return 0;
}
実行結果
出力例:
a=1, b=0

printfの書式(%f/%c/%s)

浮動小数点の主な書式は%f(固定小数)、%e(指数)、%g(適応)です。

long doubleには%Lf等を使います。

%cは文字、%sは文字列です。

C言語
// 浮動小数点と文字/文字列のprintf書式
#include <stdio.h>

int main(void) {
    float f = 3.1415926f;
    double d = 2.718281828459045;
    long double ld = 1.234567890123456789L;
    char ch = 'A';
    const char *str = "hello";

    printf("float  %%f=%.3f %%e=%.3e %%g=%.3g\n", f, f, f);
    printf("double %%f=%.6f %%e=%.6e %%g=%.6g\n", d, d, d);
    printf("long double %%Lf=%.9Lf\n", ld);
    printf("char= %c, string= %s\n", ch, str);
    return 0;
}
実行結果
出力例:
float  %f=3.142 %e=3.142e+00 %g=3.14
double %f=2.718282 %e=2.718282e+00 %g=2.71828
long double %Lf=1.234567890
char= A, string= hello

型変換と安全な選び方ガイド

代入と演算の型変換(整数昇格)

Cでは、charshortで演算すると自動でintに昇格(整数昇格)し、両辺の型に応じて「通常の算術変換」が行われます。

符号付きと符号なしを混ぜると、符号なし側に引っ張られて意図しない結果になることがあるため注意します。

C言語
// 符号混在と整数昇格の落とし穴
#include <stdio.h>

int main(void) {
    int i = -1;
    unsigned int u = 1u;

    // 比較ではuがunsignedのため、iがunsignedに変換されて非常に大きな値になる
    printf("i < u ? %d\n", (i < u)); // 多くの処理系で0(false)

    signed char sc1 = 100, sc2 = 50;
    printf("sc1 + sc2 = %d\n", sc1 + sc2); // intに昇格して計算

    unsigned char uc = 250;
    signed char sc = 10;
    // uc(250) + sc(10)=260 -> 昇格後intで計算。格納時の型で再び丸め/オーバーフローに注意
    int sum = uc + sc;
    printf("uc + sc = %d\n", sum);
    return 0;
}
実行結果
出力例:
i < u ? 0
sc1 + sc2 = 150
uc + sc = 260

明示的キャストの使い方

キャストは型を強制変換しますが、情報が失われたり未定義動作を見えにくくしたりするため、必要最低限にします。

安全なキャストは「境界チェック」を伴います。

C言語
// double -> int への安全なキャスト(飽和)
#include <stdio.h>
#include <limits.h>

int safe_cast_double_to_int(double x, int *out) {
    if (x > (double)INT_MAX) { *out = INT_MAX; return 1; } // 1: 飽和
    if (x < (double)INT_MIN) { *out = INT_MIN; return 1; }
    *out = (int)x; // 範囲内ならキャスト
    return 0;      // 0: 正常
}

int main(void) {
    double v1 = 1e20, v2 = -1e20, v3 = 123.9;
    int r;
    safe_cast_double_to_int(v1, &r); printf("v1->%d\n", r);
    safe_cast_double_to_int(v2, &r); printf("v2->%d\n", r);
    safe_cast_double_to_int(v3, &r); printf("v3->%d\n", r);
    return 0;
}
実行結果
出力例:
v1->2147483647
v2->-2147483648
v3->123

オーバーフロー/丸めの回避

符号付き整数のオーバーフローは未定義動作です。

加算・乗算の前に限界をチェックするか、より広い型で計算し、結果が範囲内か確かめてから狭い型に格納します。

C言語
// intの安全な加算チェック
#include <stdio.h>
#include <limits.h>

int add_int_safe(int a, int b, int *out) {
    if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
        return 1; // オーバーフロー
    }
    *out = a + b;
    return 0;
}

int main(void) {
    int r;
    if (add_int_safe(INT_MAX, 1, &r)) {
        printf("オーバーフロー検出\n");
    }
    if (!add_int_safe(100, 23, &r)) {
        printf("100 + 23 = %d\n", r);
    }
    return 0;
}
実行結果
出力例:
オーバーフロー検出
100 + 23 = 123

浮動小数点では丸め誤差が避けられません。

比較は「許容誤差(epsilon)」を設けます。

C言語
// 浮動小数点の近似比較
#include <stdio.h>
#include <float.h>
#include <math.h>

int nearly_equal(double a, double b, double eps) {
    double diff = fabs(a - b);
    double scale = fmax(1.0, fmax(fabs(a), fabs(b)));
    return diff <= eps * scale;
}

int main(void) {
    double x = 0.1 + 0.2; // 0.30000000000000004 になることが多い
    printf("x=%.17f, 0.3=%.17f, equal? %d\n", x, 0.3, nearly_equal(x, 0.3, 10 * DBL_EPSILON));
    return 0;
}
実行結果
出力例:
x=0.30000000000000004, 0.3=0.29999999999999999, equal? 1

精度・速度・メモリのバランス

  • 範囲が明確なら固定幅整数(int32_t等)で仕様を固定化します。汎用計算にはintが最適化されやすい傾向があります。
  • 大規模データの配列ではfloatuint16_tなどの軽量型でメモリ帯域を節約できますが、精度や上限に余裕があるかを事前に検証します。
  • 64bit環境ではlongが64bit(LP64)の場合、インデックスやサイズはsize_tのまま扱い、longに安易にキャストしない方が安全です。
  • 性能が問題になる箇所は、型選択がキャッシュ効率やSIMDに与える影響も考慮します。

初心者向けチェックリスト

  • 値が負になる可能性があるかどうかでsignedunsignedを決めます。
  • 配列の長さやメモリサイズはsize_t、ポインタ差はptrdiff_tを使います。
  • 仕様でビット幅が決まるデータはstdint.hの固定幅整数を使います。
  • printfの書式指定子は型に合わせ、固定幅にはinttypes.hのマクロを使います。
  • 32bit/64bit差を意識し、longのサイズ差に注意します。
  • 浮動小数点の比較は許容誤差を使います。
  • キャストは必要最小限に留め、境界チェックを伴います。
  • 範囲やサイズに迷ったらsizeoflimits.h/float.hで確認します。

まとめ

C言語の型は、プログラムの正しさと効率を左右する重要な設計要素です。

まずは実行環境ごとのサイズと範囲をsizeoflimits.h/float.hで押さえ、整数・浮動小数点・文字の基本特性を理解しましょう。

整数は符号とオーバーフロー、浮動小数点は丸め誤差、文字列は終端とバッファサイズが肝要です。

移植性が求められる場面では固定幅整数とinttypes.hの書式マクロを活用し、サイズやポインタ関連はsize_t/ptrdiff_tを用いて安全に扱います。

最後に、型変換の自動規則を理解し、必要に応じて明示的なチェックとキャストで守りを固めることで、環境に依存せず堅牢なCプログラムを組み立てられるようになります。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!