C言語では整数を扱う型としてint、short、longが用意されています。
同じ整数でもサイズや表せる範囲は環境によって異なり、選び方を誤るとオーバーフローなどの不具合を招きます。
この記事では違いと使い分けの基準、実例、確認方法を初心者向けに丁寧に解説します。
C言語の整数型(int/short/long)の基本
違いはサイズと表せる範囲
整数型の最も大きな違いはメモリ上のサイズと表現できる値の範囲です。
基本的にビット数が増えるほど表せる範囲が広がります。
符号あり整数(負の値も扱う)は2の補数
表現が一般的で、ビット数をNとすると最小値=-2^(N-1)
、最大値=2^(N-1)-1
がおおよその目安になります。
以下は初心者が把握しておくと良い目安です。
実際のサイズは環境で変わるため、後述のsizeof
で必ず確認してください。
型 | 規格での最低ビット数 | よくあるビット数(目安) | 符号ありの典型範囲(目安) | 備考 |
---|---|---|---|---|
short | 16 | 16 | -32768 〜 32767 | 配列でメモリを節約したいときに有用 |
int | 16 | 32 | -2147483648 〜 2147483647 | 最もよく使う基本整数型 |
long | 32 | 32 または 64 | 32ビット時はintと同等、64ビット時は大幅に広い | OSとコンパイラで差が出やすい |
intとlongが同じサイズの環境もあれば、longだけが64ビットになる環境もあります。
この差が使い分けの肝です。
サイズは環境で変わる(目安あり)
Cでは型のサイズは規格で固定されていません。
実装(データモデル)によって次のように分かれます。
データモデル | short | int | long | ポインタ | 主な環境の例 |
---|---|---|---|---|---|
ILP32 | 16 | 32 | 32 | 32 | 32ビットLinuxなど |
LP64 | 16 | 32 | 64 | 64 | 64ビットLinux、macOS(Clang/GCC) |
LLP64 | 16 | 32 | 32 | 64 | 64ビットWindows(MSVC/MinGW) |
64ビットWindowsではlongが32ビットのまま、一方でLinuxやmacOSの多くはlongが64ビットです。
この差をまたぐコードでは特に注意が必要です。
よく使うのはint
初心者のうちはまずはintを標準の整数型として使うのが安全です。
理由は次のとおりです。
- 演算の効率が良く、CPUにとって扱いやすい「自然なサイズ」であることが多いです。
- 標準ライブラリや多くのAPIが引数に
int
を使っています。 - コードの読みやすさと移植性のバランスが良いです。
整数型の使い分け(初心者向け)
迷ったらintを選ぶ
明確な理由がない限りまずはintで始めるのが基本です。
配列の添字、ループ回数、簡単なカウンタなどはintで十分です。
もちろん、扱う値がINT_MAX
(環境依存)を超える可能性があれば、より大きな型を検討します。
例: 配列の合計を求める(intで十分な場面)
#include <stdio.h>
// 配列の要素数や添字、簡単な合計にはintで十分なことが多い例
int main(void) {
int a[] = {1,2,3,4,5,6,7,8,9,10};
int n = (int)(sizeof(a) / sizeof(a[0])); // 要素数をintに収める
int sum = 0;
for (int i = 0; i < n; ++i) {
sum += a[i];
}
printf("要素数=%d, 合計=%d\n", n, sum);
return 0;
}
要素数=10, 合計=55
メモリ節約ならshort
大量のデータを持つ配列で各要素の値が小さい(例えば0〜30000程度)と分かっているなら、shortで半分のメモリに抑えられる可能性があります。
たとえば10万要素の配列なら、int(4バイト想定)で約400KB、short(2バイト)で約200KBです。
ただし、演算時にshortはintへ整数昇格されるため、節約できるのはあくまで「保管時のメモリ」だけです。
演算性能や中間結果の型にはあまり影響しません。
また、入出力ではprintf
の書式指定子に%hd
のような長さ修飾子が必要になります。
大きな値ならlong
扱う値がINT_MAX
を超える可能性がある場合はlongを検討します。
ただしWindowsの64ビット環境ではlongが32ビットのままなので、本当に大きな値(32ビットを超える)が必要なら環境に応じた検討が不可欠です。
LinuxやmacOSではlongが64ビットであることが多く、より大きな範囲を扱えます。
補足として、さらに大きな整数が必要で移植性を重視するならlong long
(少なくとも64ビット)もありますが、本記事では主にint/short/long
に焦点を当てます。
オーバーフローに注意
範囲外は値が壊れる
型が扱える範囲を超えるとオーバーフローが起きます。
符号あり整数(例えばint, long)のオーバーフローはC規格で未定義動作です。
つまり「どう壊れるか」も保証されません。
安全に書くには演算前に上限や下限をチェックします。
例: longの加算で事前に検査する
#include <stdio.h>
#include <limits.h>
#include <stdbool.h>
// a + b を安全に計算し、オーバーフロー/アンダーフローならfalseを返す
bool add_long_checked(long a, long b, long *out) {
// bが正なら上側の余裕を確認
if (b > 0 && a > LONG_MAX - b) return false;
// bが負なら下側の余裕を確認
if (b < 0 && a < LONG_MIN - b) return false;
*out = a + b;
return true;
}
int main(void) {
long a = LONG_MAX - 1;
long b = 1;
long r = 0;
if (add_long_checked(a, b, &r)) {
printf("OK: %ld + %ld = %ld\n", a, b, r);
} else {
printf("NG: オーバーフローを検出しました\n");
}
a = LONG_MAX;
b = 1; // ここは確実にオーバーフローする状況
if (add_long_checked(a, b, &r)) {
printf("OK: %ld + %ld = %ld\n", a, b, r);
} else {
printf("NG: オーバーフローを検出しました\n");
}
return 0;
}
OK: 9223372036854775806 + 1 = 9223372036854775807
NG: オーバーフローを検出しました
上の数値はlongが64ビットの環境(LP64)の例です。
32ビットやLLP64では数字は異なりますが、判定ロジックは同じです。
型を混ぜずにそろえる
異なる型を混ぜると暗黙の型変換(整数昇格、通常の算術変換)が起きます。
意図しない縮小変換や書式指定子の不一致はバグの温床です。
同じ「大きさの値」を扱う変数群は同じ型で統一しましょう。
定数にもLサフィックス(例:1000000L
)を付け、printf
の書式は%ld
を使います。
例: longをprintfで正しく表示する
#include <stdio.h>
// long値を正しく出力する例
int main(void) {
long n = 1000000L; // Lサフィックスでlong型の整数リテラル
long m = n * 2;
// longを表示するには%ldを使う(WindowsでもLinuxでも同じ書式)
printf("n=%ld, m=%ld\n", n, m);
// 間違い例: %dはint用。longに使うと未定義動作。
// printf("n=%d\n", n); // これはダメ
return 0;
}
n=1000000, m=2000000
実例とチェック方法
ループ回数や添字はint
実務でもループの回数や配列の添字はintで十分な場面が多いです。
配列サイズが非常に大きくなる特殊な状況を除けば、読みやすさと速度のバランスに優れます。
なお、標準関数が返すサイズにはsize_t
が用いられるため、将来大規模データを扱う際はsize_tも学ぶと良いでしょう。
例: 配列の最大値を求める
#include <stdio.h>
// 配列の最大値をintの添字で探索する例
int main(void) {
int a[] = {12, 7, 25, 3, 18, 42, 9};
int n = (int)(sizeof(a) / sizeof(a[0]));
int max = a[0];
for (int i = 1; i < n; ++i) {
if (a[i] > max) {
max = a[i];
}
}
printf("最大値=%d (要素数=%d)\n", max, n);
return 0;
}
最大値=42 (要素数=7)
大きなカウンタや時間はlong
大きめのカウント値や秒単位の時間差などはlongが向いています。
LinuxやmacOSではlongが64ビットのため、より大きな値を一つの型で扱えます。
Windows 64ビットではlongが32ビットである点に注意し、必要に応じて設計段階で上限を見積もるか、別の型選択を検討します。
例: バイト数をMBに換算する(大きめの数をlongで保持)
#include <stdio.h>
// 大きめのバイト数をlongに入れて単位変換する例
int main(void) {
// 2,000,000,000バイト(約1.86GB)。32ビットlongでも収まる値に設定
long bytes = 2000000000L;
double megabytes = bytes / (1024.0 * 1024.0); // 浮動小数点でMBへ換算
printf("サイズ: %ld bytes (%.2f MB)\n", bytes, megabytes);
return 0;
}
サイズ: 2000000000 bytes (1907.35 MB)
環境によりlong
の上限が異なるため、より大きいサイズを扱うならsizeof(long)
やLONG_MAX
で必ず事前確認してください。
sizeofで自分の環境のサイズを確認
最終的には自分の環境で確かめるのが確実です。
sizeof
とlimits.h
のマクロを使えば、サイズと範囲を簡単に表示できます。
例: 各整数型のサイズと範囲を表示する
#include <stdio.h>
#include <limits.h>
// 各型のバイト数(とビット数)と範囲を表示する
int main(void) {
printf("short: %zu バイト (%d ビット)\n", sizeof(short), (int)(sizeof(short) * CHAR_BIT));
printf(" SHRT_MIN=%d, SHRT_MAX=%d\n", SHRT_MIN, SHRT_MAX);
printf("int : %zu バイト (%d ビット)\n", sizeof(int), (int)(sizeof(int) * CHAR_BIT));
printf(" INT_MIN=%d, INT_MAX=%d\n", INT_MIN, INT_MAX);
printf("long : %zu バイト (%d ビット)\n", sizeof(long), (int)(sizeof(long) * CHAR_BIT));
printf(" LONG_MIN=%ld, LONG_MAX=%ld\n", LONG_MIN, LONG_MAX);
return 0;
}
実行結果例(Windows 64ビット/LLP64 の典型):
short: 2 バイト (16 ビット)
SHRT_MIN=-32768, SHRT_MAX=32767
int : 4 バイト (32 ビット)
INT_MIN=-2147483648, INT_MAX=2147483647
long : 4 バイト (32 ビット)
LONG_MIN=-2147483648, LONG_MAX=2147483647
実行結果例(LinuxやmacOS 64ビット/LP64 の典型):
short: 2 バイト (16 ビット)
SHRT_MIN=-32768, SHRT_MAX=32767
int : 4 バイト (32 ビット)
INT_MIN=-2147483648, INT_MAX=2147483647
long : 8 バイト (64 ビット)
LONG_MIN=-9223372036854775808, LONG_MAX=9223372036854775807
この違いが「同じlongでもOSによって範囲が違う」理由です。
移植性の高いコードでは、サイズ前提のロジックを避け、実行時にsizeof
やLONG_MAX
を用いた判定を取り入れると堅牢になります。
まとめ
本記事ではint/short/longの違いと使い分けを初心者向けに整理しました。
実務では、まず迷ったらint、配列でメモリを節約したいならshort、より大きな値を扱うならlongという順で考えると良いです。
ただしlongのサイズはOSやコンパイラで異なるため、sizeof
やLONG_MAX
で上限を確かめ、オーバーフローは未定義動作であることを常に意識しましょう。
用途がはっきりしているなら、値の上限を見積もって型を選ぶ、定数に適切なサフィックスを付ける、printf
で正しい書式指定子を使う、といった基本を丁寧に守るだけで、堅実で移植性の高いCコードが書けます。