C言語のプログラムでは、データを「どう表現するか」を決めるデータ型がとても重要です。
本記事では基本の型の違い、値の範囲、選び方の指針を、初心者でも迷わないよう順序立てて解説します。
実装依存の落とし穴や、範囲・精度・可搬性の観点も押さえ、安全に扱える実用的なサンプルコードを多数用意しました。
C言語のデータ型の基本
データ型とは何か
データ型とは、変数が取りうる値の種類(集合)と、その表現方法、演算の意味を定める約束です。
例えばint
は整数、double
は実数(浮動小数点)を扱います。
型はメモリサイズ・値の範囲・丸めの規則・演算結果に影響します。
正しく型を選ぶことは、オーバーフローや精度不足といった不具合の防止につながります。
C言語の型の分類(整数/浮動小数点/文字/論理/列挙)
C言語の基本カテゴリは次の通りです。
実装が提供する拡張を除けば、この分類で十分網羅できます。
- 整数型:
short
、int
、long
、long long
、およびunsigned
系 - 文字型:
char
、signed char
、unsigned char
- 浮動小数点型:
float
、double
、long double
- 論理型:
_Bool
(stdbool.h
でbool
) - 列挙型:
enum
で定義する離散値 - 固定幅整数:
stdint.h
のint32_t
など(可搬性重視) - サイズ関連:
size_t
、ptrdiff_t
分類だけでなくどういう用途に適しているかが重要です。
後半の選び方ガイドで具体的な判断基準を示します。
サイズと値の範囲は実装依存
型のサイズや値の範囲は処理系(コンパイラとCPU/OS)に依存します。
標準が保証するのは最低限の範囲で、実際のビット幅は環境により異なります。
次は標準が保証する下限の一例です。
型 | 最低ビット数(標準規定) | 備考 |
---|---|---|
char | 8以上 | CHAR_BIT で決まる |
short | 16以上 | |
int | 16以上 | |
long | 32以上 | |
long long | 64以上 | C99以降 |
float | 実装依存 | 多くはIEEE 754単精度 |
double | 実装依存 | 多くはIEEE 754倍精度 |
long double | 実装依存 | x86拡張80bitや倍精度同等など |
正確なサイズや範囲はsizeof
やlimits.h
/float.h
で毎回確認しましょう。
基本データ型の種類と値の範囲
整数型(int/short/long/long long)の違いと範囲
整数型は符号ありと符号なしがあり、前者は負数を扱えます。
後者は0以上のみですが、同じビット幅なら上限値が約2倍になります。
よくある環境での典型サイズ(参考)
環境 | short | int | long | long long |
---|---|---|---|---|
32bit(ILP32) | 16 | 32 | 32 | 64 |
64bit(LP64, Linux/macOS) | 16 | 32 | 64 | 64 |
64bit(LLP64, Windows) | 16 | 32 | 32 | 64 |
単位はビットです。
実際にはsizeof
で確認します。
範囲とサイズを調べるサンプル
#include <stdio.h>
#include <limits.h> // INT_MAX, LONG_MAX など
int main(void) {
// サイズはバイト、範囲はlimits.hのマクロで取得します
printf("short: %zu bytes, [%d, %d]\n", sizeof(short), SHRT_MIN, SHRT_MAX);
printf("int: %zu bytes, [%d, %d]\n", sizeof(int), INT_MIN, INT_MAX);
printf("long: %zu bytes, [%ld, %ld]\n", sizeof(long), LONG_MIN, LONG_MAX);
printf("long long: %zu bytes, [%lld, %lld]\n", sizeof(long long), LLONG_MIN, LLONG_MAX);
// 符号なしの上限も確認
printf("unsigned int max: %u\n", UINT_MAX);
return 0;
}
実行結果(一例):
short: 2 bytes, [-32768, 32767]
int: 4 bytes, [-2147483648, 2147483647]
long: 8 bytes, [-9223372036854775808, 9223372036854775807]
long long: 8 bytes, [-9223372036854775808, 9223372036854775807]
unsigned int max: 4294967295
この出力はLP64(多くのLinux/macOS)の例です。
Windows(LLP64)ではlong
は4バイトになります。
注意: 符号あり整数のオーバーフローは未定義動作です。
例えばINT_MAX + 1
は未定義になります。
加算前にif (a > INT_MAX - b)
のようにチェックしましょう。
文字型(char)の役割と範囲
char
は1バイト単位の文字/バイト格納に使いますが、符号付きか符号なしであるかは実装依存です。
文字コードは環境により異なります(UTF-8の各バイトを扱う場合はunsigned char
が安全です)。
型 | 用途 | 符号 | 備考 |
---|---|---|---|
char | 文字/バイト | 実装依存 | signed またはunsigned |
signed char | 明示的に負数ありの1バイト | 符号あり | 範囲はSCHAR_MIN..SCHAR_MAX |
unsigned char | 生データ/バッファ | 符号なし | 0..UCHAR_MAX |
バイナリ処理や文字列の各バイトを扱うときはunsigned char
を使うと予期せぬ負値化を避けられます。
浮動小数点型(float/double/long double)の精度と範囲
多くの実装はIEEE 754に準拠します。
代表的な有効桁数はfloat
で約7桁、double
で約15〜16桁です。
long double
は実装により倍精度と同等または80bit拡張精度等になります。
#include <stdio.h>
#include <float.h> // FLT_MAX, DBL_DIG など
#include <math.h>
#include <stdbool.h>
int main(void) {
printf("float: digits=%d, range=[%e, %e]\n", FLT_DIG, FLT_MIN, FLT_MAX);
printf("double: digits=%d, range=[%e, %e]\n", DBL_DIG, DBL_MIN, DBL_MAX);
printf("long double: digits=%d, range=[%Le, %Le]\n", LDBL_DIG, LDBL_MIN, LDBL_MAX);
// 浮動小数点の丸め誤差例
double x = 0.1 + 0.2;
printf("0.1 + 0.2 = %.17f\n", x);
printf("x == 0.3 ? %s\n", (x == 0.3) ? "true" : "false");
return 0;
}
実行結果(一例):
float: digits=6, range=[1.175494e-38, 3.402823e+38]
double: digits=15, range=[2.225074e-308, 1.797693e+308]
long double: digits=18, range=[3.362103e-4932, 1.189731e+4932]
0.1 + 0.2 = 0.30000000000000004
x == 0.3 ? false
浮動小数点では「等しい」比較を避け、許容誤差(イプシロン)を用いた比較を検討してください。
論理型(_Boolとstdbool.hのbool)
C99以降、_Bool
が導入されました。
stdbool.h
をインクルードするとbool
/true
/false
が使えます。
論理値の表示は整数(0/1)として行います。
#include <stdio.h>
#include <stdbool.h>
int main(void) {
bool a = true;
bool b = (5 > 3); // 比較演算子の結果はint(0または1)だがboolに自動変換される
printf("a=%d, b=%d, a && b=%d\n", a, b, a && b);
return 0;
}
a=1, b=1, a && b=1
列挙型(enum)の使いどころと注意点
enum
は関連する離散値(状態や曜日など)を名前付きで表現できます。
基になる型は整数型で、値の範囲チェックは行われません。
型安全性は限定的なので、外部とのデータ交換やABIでは整数幅に注意します。
#include <stdio.h>
enum Status {
STATUS_OK = 0,
STATUS_WARN = 1,
STATUS_ERR = 2,
// 途中から値を指定しないと前の+1が自動付与されます
STATUS_FATAL // 3
};
int main(void) {
enum Status st = STATUS_WARN;
printf("Status=%d\n", st); // %dで印字(基になる型は実装依存だが多くはint)
// 注意: 列挙型に範囲外の整数を代入できてしまう実装もあります
st = 100; // コンパイラは警告する場合あり
printf("Status(after invalid)=%d\n", st);
return 0;
}
実行結果(一例):
Status=1
Status(after invalid)=100
使いどころは状態管理や分岐の可読性向上です。
外部I/Oやシリアライズでは固定幅整数に写像するのが無難です。
固定幅整数(stdint.hのint32_tなど)の範囲
stdint.h
はビット幅が明確な整数を提供します。
可搬性と外部仕様との整合に優れ、ネットワーク/ファイルフォーマットに適します。
- 厳密幅:
int8_t
、int16_t
、int32_t
、int64_t
とそのuint*_t
- 最小幅:
int_least32_t
など(少なくともNビット) - 高速幅:
int_fast32_t
など(その環境で最速)
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h> // PRIu32 などの書式マクロ
int main(void) {
printf("int32_t: size=%zu, min=%" PRId32 ", max=%" PRId32 "\n",
sizeof(int32_t), INT32_MIN, INT32_MAX);
printf("uint64_t: size=%zu, max=%" PRIu64 "\n",
sizeof(uint64_t), UINT64_MAX);
return 0;
}
実行結果(一例):
int32_t: size=4, min=-2147483648, max=2147483647
uint64_t: size=8, max=18446744073709551615
サイズ関連の型(size_t/ptrdiff_t)の用途
size_t
はオブジェクトの大きさや配列の添字に使われる符号なし整数です。
ptrdiff_t
は同じ配列内のポインタ差を表す符号あり整数です。
どちらも環境によりビット幅が変わります。
#include <stdio.h>
#include <stddef.h> // size_t, ptrdiff_t
int main(void) {
int a[10];
size_t n = sizeof(a) / sizeof(a[0]); // 配列要素数
ptrdiff_t diff = &a[9] - &a[2]; // 要素間の距離(単位はint要素)
printf("size_t: %zu (array length)\n", n); // %zuはsize_t用
printf("ptrdiff_t: %td (elements apart)\n", diff); // %tdはptrdiff_t用
return 0;
}
size_t: 10 (array length)
ptrdiff_t: 7 (elements apart)
データ型の選び方ガイド
初心者の基本方針(intとdoubleから始める)
最初は整数はint
、小数はdouble
を原則にすると混乱が少ないです。
必要になったらlong long
やuint32_t
等へ置き換えるとよいでしょう。
範囲で選ぶ(最大値/最小値とオーバーフロー回避)
扱う値の最大最小を見積もり、上限に十分な余裕を持つ型を選びます。
計算ではオーバーフロー前にチェックします。
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
bool safe_add_int(int a, int b, int *out) {
if ((b > 0 && a > INT_MAX - b) || (b < 0 && a < INT_MIN - b)) {
return false; // オーバーフロー
}
*out = a + b;
return true;
}
int main(void) {
int r;
if (safe_add_int(INT_MAX, 1, &r)) {
printf("sum=%d\n", r);
} else {
printf("overflow detected\n");
}
return 0;
}
overflow detected
精度で選ぶ(小数はdouble、金額は整数)
小数の計算は原則double
を使います。
一方、金額は小数で持たず、最小通貨単位(例: 円、または1/100ドル)の整数で扱うのが安全です。
税計算や累積誤差を避けられます。
パフォーマンスとメモリのバランス
現代のCPUではネイティブ幅(多くはint
やint32_t
)が最速で、short
は必ずしも高速ではありません。
大量データではメモリ節約が利くuint16_t
等が有効な場合もありますが、演算は一旦広い型に昇格してから行うなどの工夫が必要です。
可搬性を優先するなら固定幅整数を使う
外部仕様(ネットワーク/ファイル/プロトコル)やABI整合が必要な場面ではint32_t
/uint64_t
など固定幅整数を選び、入出力は<inttypes.h>
の書式マクロ(PRId32
等)を使います。
APIや外部仕様の型に合わせる
標準ライブラリ/OSのAPIに合わせて要求される型をそのまま使うのが基本です。
例としてmalloc
のサイズはsize_t
、ポインタ差はptrdiff_t
、時間はtime_t
、ソケット/プロトコルはuint32_t
等が指定されます。
値の範囲とサイズの調べ方
sizeofでバイト数を確認する
sizeof
は型や式のバイト数を返します。
配列の要素数計算にもよく使います。
#include <stdio.h>
#include <stdint.h>
#define COUNT_OF(a) (sizeof(a) / sizeof((a)[0])) // 静的配列の要素数
int main(void) {
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(long) = %zu\n", sizeof(long));
printf("sizeof(void*) = %zu\n", sizeof(void*));
printf("sizeof(int32_t) = %zu\n", sizeof(int32_t));
int arr[100];
printf("elements in arr = %zu\n", COUNT_OF(arr));
return 0;
}
実行結果(一例):
sizeof(int) = 4
sizeof(long) = 8
sizeof(void*) = 8
sizeof(int32_t) = 4
elements in arr = 100
limits.hとfloat.hで最大値/最小値を得る
整数はlimits.h
、浮動小数点はfloat.h
が公式の情報源です。
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void) {
printf("INT_MIN=%d, INT_MAX=%d, UINT_MAX=%u\n", INT_MIN, INT_MAX, UINT_MAX);
printf("DBL_MIN=%e, DBL_MAX=%e, DBL_DIG=%d\n", DBL_MIN, DBL_MAX, DBL_DIG);
return 0;
}
実行結果(一例):
INT_MIN=-2147483648, INT_MAX=2147483647, UINT_MAX=4294967295
DBL_MIN=2.225074e-308, DBL_MAX=1.797693e+308, DBL_DIG=15
_Static_assertで前提条件をチェックする
コンパイル時に前提(例えばビット幅)を満たすか検証できます。
C11の_Static_assert
、またはassert.h
のstatic_assert
(C11)を使います。
#include <stdint.h>
#include <assert.h> // C11以降でstatic_assertマクロが利用可能
// いずれかを使用: _Static_assert(条件, "メッセージ");
_Static_assert(sizeof(int32_t) == 4, "int32_t must be 4 bytes");
// または
static_assert(sizeof(void*) == 8, "This code assumes 64-bit pointers");
int main(void) {
return 0;
}
このコードは条件を満たさない環境ではコンパイルエラーになります。
実行環境(32bit/64bit)の違いに注意
同じ「64bit OS」でもデータモデルが異なります。
LP64とLLP64の差は特に重要です。
データモデル | int | long | void* | 主な環境 |
---|---|---|---|---|
ILP32 | 32 | 32 | 32 | 32bit各種 |
LP64 | 32 | 64 | 64 | Linux, macOS(64bit) |
LLP64 | 32 | 32 | 64 | Windows(64bit) |
環境検出の例です。
#include <stdio.h>
#include <stdint.h>
int main(void) {
printf("sizeof(void*)=%zu\n", sizeof(void*));
#if INTPTR_MAX == INT64_MAX
printf("Pointer: 64-bit\n");
#elif INTPTR_MAX == INT32_MAX
printf("Pointer: 32-bit\n");
#else
printf("Pointer: unknown width\n");
#endif
// データモデルの一端を表示
printf("sizeof(int) = %zu, sizeof(long) = %zu, sizeof(long long) = %zu\n",
sizeof(int), sizeof(long), sizeof(long long));
return 0;
}
実行結果(一例):
sizeof(void*)=8
Pointer: 64-bit
sizeof(int) = 4, sizeof(long) = 8, sizeof(long long) = 8
Windows 64bitではlong
が4バイトである点に注意してください。
まとめ
本記事では、C言語のデータ型を分類から実装依存の注意、範囲・精度・可搬性の観点、そして目的に応じた選び方まで体系的に解説しました。
基本は整数はint
、小数はdouble
、外部仕様や可搬性重視ならstdint.h
の固定幅整数、サイズやポインタ差にはsize_t
/ptrdiff_t
を用いるのが堅実です。
実装依存の違いはsizeof
やlimits.h
/float.h
、_Static_assert
で常に確認し、オーバーフローや丸め誤差を前提に安全なコードを書く習慣を身につけましょう。