Contents
はじめに
「int のサイズは本当に 4 バイトなのか」や「32bit と 64bit で型サイズが変わるとコードが壊れないか」といった疑問は、C 言語を扱うすべてのエンジニアが一度は抱えるものです。
本稿では次のことを実践的に解説します。
- 標準データ型と固定幅整数型のサイズ比較表(環境別)
sizeofによるランタイム確認コード例- コンパイル時にサイズを保証する
_Static_assertの使い方 - 移植性確保のためのコーディング指針
最後に、オフラインでも参照できる PDF 版比較表 をダウンロードできるリンクをご用意しています。ぜひ開発環境に合わせて活用してください。
標準データ型サイズ比較(ILP32/LP64/LLP64)
データモデルとは
| モデル | int | long | ポインタ | 代表的 OS / コンパイラ |
|---|---|---|---|---|
| ILP32 | 4 バイト | 4 バイト | 4 バイト | 32bit Linux、組み込み系 GCC |
| LP64 | 4 バイト | 8 バイト | 8 バイト | 64bit Linux / macOS (glibc, clang) |
| LLP64 | 4 バイト | 4 バイト | 8 バイト | Windows (MSVC) |
ポイント
-intはどのモデルでも 4 バイト(32 ビット)に固定されていることが多いです。
-longとポインタだけがモデルごとに変わります。
標準データ型サイズ表
| 型 (C 標準) | ILP32 (バイト) | LP64 (バイト) | LLP64 (バイト) |
|---|---|---|---|
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 4 | 4 | 4 |
long | 4 | 8 | 4 |
long long | 8 | 8 | 8 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | 12* | 16 | 16 |
void * | 4 | 8 | 8 |
* GCC がデフォルトで採用する x86 の long double は 12 バイト、Clang/MSVC は 16 バイトです。
解説
- ILP32 – すべての整数型が 4 バイト以下に収まるシンプルなモデル。組み込み系やレガシー OS でよく見られます。
- LP64 – Unix 系 OS のデファクトスタンダード。
longとポインタが 8 バイトになるため、ファイルフォーマットやネットワークプロトコルではint64_t等の固定幅型を使うことが推奨されます。 - LLP64 – Windows の独自モデル。
longが 4 バイトに留まる点が他 OS と最大の相違です。そのため、Windows 向けコードではsizeof(long)に依存しない設計が必須です。
まとめ
同一ソースでもビルド対象が LP64 か LLP64 かでsizeof(long)が異なる点に注意してください。バイナリ互換や外部データとのやり取りは、必ず固定幅型 (int*_t/uint*_t) を利用するのが安全です。
符号付き・符号なし整数の値域と注意点
代表的な型の範囲(十進表記)
| 型 | バイト数 | 符号あり最小値 | 符号あり最大値 | 符号なし最小値 | 符号なし最大値 |
|---|---|---|---|---|---|
char | 1 | -128 | 127 | 0 | 255 |
unsigned char | 1 | — | — | 0 | 255 |
short | 2 | -32 768 | 32 767 | 0 | 65 535 |
unsigned short | 2 | — | — | 0 | 65 535 |
int | 4 | -2 147 483 648 | 2 147 483 647 | 0 | 4 294 967 295 |
unsigned int | 4 | — | — | 0 | 4 294 967 295 |
long (LP64) | 8 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | 0 | 18 446 744 073 709 551 615 |
unsigned long (LLP64) | 4 | — | — | 0 | 4 294 967 295 |
ポイント
符号付き整数は最上位ビットが符号に使われるため、同じバイト数でも正の最大値は2^(N-1)-1に制限されます(Nはビット幅)。
実務でよくある落とし穴
| ケース | 問題点 | 回避策 |
|---|---|---|
配列インデックスに int を使用 | 負のインデックスが計算される可能性 | インデックスは必ず size_t または unsigned にする |
ファイルへ書き出す際に int → unsigned int の変換を忘れる | 32bit 環境では問題なくても、64bit 環境で符号拡張が起こりデータが破損 | 書き込み前に明示的にキャストし、範囲チェックを入れる |
ビット演算で int を用いる | 演算結果が符号付きとして扱われ、予期せぬシフトが発生 | ビット単位の処理は必ず固定幅型 (uint*_t) で行う |
まとめ
型サイズだけでなく「符号属性」も設計時に明示し、外部インターフェースや入出力チェックを徹底しましょう。
ポインタサイズの変化がもたらす実務上の影響
32bit ↔︎ 64bit のポインタ差
| 項目 | 32bit 環境 | 64bit 環境 |
|---|---|---|
| ポインタサイズ | 4 バイト (32 ビット) | 8 バイト (64 ビット) |
| アラインメント(典型的な規則) | 4 バイト単位 | 8 バイト単位 |
uintptr_t のサイズ | 4 バイト | 8 バイト |
実務での具体例
1. 構造体サイズの膨張
|
1 2 3 4 5 |
/* 例: リンクリストノード */ struct Node { int value; /* 4 バイト */ struct Node *next; /* ポインタ 4→8 バイト増 */ }; |
| コンパイラ / 環境 | sizeof(struct Node) |
|---|---|
| GCC (ILP32) | 8 バイト |
| GCC (LP64) | 16 バイト |
注意:ネットワークプロトコルやファイルフォーマットで構造体をそのままシリアライズすると、ポインタサイズの違いによりデータがずれます。必ず明示的なパディングと固定幅型でレイアウトを定義してください。
2. メモリマッピング(組み込み系)
c
define REG_BASE ((volatile uint32_t *)0x40021000U)
uint32_t value = REG_BASE; / ポインタ自体は 32bit 固定 */
ポインタサイズが変わってもレジスタ幅は 32 ビットです。キャストミス に注意し、uintptr_t 経由で安全に整数化することを推奨します。
3. API 設計の落とし穴
|
1 2 3 4 |
/* 悪い例:ハンドラを void* のまま構造体に格納 */ typedef struct { void *handle; } api_context_t; |
64bit 環境で sizeof(void*) が 8 バイトになるため、32bit 用のバイナリと互換性がなくなります。代替案としては:
- opaque ハンドラ(ポインタだけを外部に公開)
- 固定幅整数 (
uint64_t) に変換して保持
4. offsetof とパディングの検証
|
1 2 |
include printf("offset of next: %zu\n", offsetof(struct Node, next)); |
上記をビルドごとに実行し、期待通り 8 バイト(LP64)になっているか確認しましょう。
まとめ
ポインタサイズの変化は構造体レイアウト・シリアライズ・API の互換性に直結します。固定幅型とパディングチェックを必ず組み込むことが安全な移植の鍵です。
<stdint.h> の固定幅型と MSVC の実装詳細
固定幅整数型の保証内容
| 型 | ビット幅 | 符号 | 標準上の保証 |
|---|---|---|---|
int8_t | 8 | 符号付き | 常に 8 ビット符号付き整数 |
uint8_t | 8 | 符号なし | 常に 8 ビット符号なし整数 |
int16_t | 16 | 符号付き | 常に 16 ビット符号付き整数 |
uint16_t | 16 | 符号なし | 常に 16 ビット符号なし整数 |
int32_t | 32 | 符号付き | 常に 32 ビット符号付き整数 |
uint32_t | 32 | 符号なし | 常に 32 ビット符号なし整数 |
int64_t | 64 | 符号付き | 常に 64 ビット符号付き整数 |
uint64_t | 64 | 符号なし | 常に 64 ビット符号なし整数 |
MSVC(2025 年版)での実装例
c
typedef signed __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef short int16_t; // 2 バイト保証 (MSVC は常に 16 ビット)
typedef unsigned short uint16_t;
typedef int int32_t; // 4 バイト保証
typedef unsigned int uint32_t;
typedef __int64 int64_t;
typedef unsigned __int64 uint64_t;
|
1 2 3 4 5 6 7 8 |
typedef signed __int8 int8_t; typedef unsigned __int8 uint8_t; typedef short int16_t; // 2 バイト保証 (MSVC は常に 16 ビット typedef unsigned short uint16_t; typedef int int32_t; // 4 バイト保証 typedef unsigned int uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; |
longは 常に 4 バイト(LLP64)で、int64_tにはマッピングされません。/Zc:__cplusplusオプションを有効にすると、C++ ヘッダーでも同様の型定義が利用できます。
size_t, ptrdiff_t, intptr_t の扱い
| 型 | 32bit 環境 | 64bit 環境 |
|---|---|---|
size_t | 4 バイト | 8 バイト |
ptrdiff_t | 4 バイト | 8 バイト |
intptr_t | 4 バイト | 8 バイト |
ポイント
ポインタと整数の相互変換が必要な場面では、必ず上記型を利用してください。直接unsigned longやlongにキャストすると、LLP64 環境でサイズ不一致が発生します。
移植性確保のための推奨事項
- 整数演算はすべて固定幅型 (
int*_t,uint*_t) を使用。 - ポインタを整数に変換するときは
uintptr_t/intptr_tを介す。 - Windows 固有のサイズ依存コードは
#if defined(_WIN64)/#elif defined(__linux__)で分岐させ、テストケースを必ず両環境で走らせる。
まとめ
<stdint.h>が提供する型は「ビット幅保証」そのものです。MSVC の実装差異に惑わされず、常に固定幅型でコードを書くことが最も安全な移植戦略です。
sizeof と _Static_assert で自環境を検証する方法
ランタイム確認コード(例)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
int main(void) { printf("=== 標準整数型サイズ ===\n"); printf("char : %zu byte(s)\n", sizeof(char)); printf("short : %zu byte(s)\n", sizeof(short)); printf("int : %zu byte(s)\n", sizeof(int)); printf("long : %zu byte(s)\n", sizeof(long)); printf("long long : %zu byte(s)\n", sizeof(long long)); printf("void * : %zu byte(s)\n", sizeof(void*)); printf("\n=== 固定幅整数型サイズ ===\n"); printf("int8_t : %zu byte(s)\n", sizeof(int8_t)); printf("uint32_t : %zu byte(s)\n", sizeof(uint32_t)); printf("int64_t : %zu byte(s)\n", sizeof(int64_t)); return 0; |
ビルド手順
| 環境 | コマンド |
|---|---|
| GCC / Clang (Linux/macOS) | gcc -std=c11 -Wall -Wextra size_check.c -o size_check && ./size_check |
| MSVC (Developer Command Prompt) | cl /std:c11 /Wall size_check.c && size_check.exe |
実行結果は環境に合わせて変わりますが、自分のコンパイラが採用しているサイズをその場で確認できるので、移植前チェックに最適です。
コンパイル時検証(C11 の _Static_assert)
|
1 2 3 4 |
_Static_assert(sizeof(int) == 4, "int は必ず 4 バイトであるべきです"); _Static_assert(sizeof(void*) == 8, "64bit 環境ではポインタは 8 バイトです"); _Static_assert(sizeof(int64_t) * CHAR_BIT == 64, "int64_t は 64 ビットでなければなりません"); int main(void) { return 0; } |
コンパイルエラーが出たらすぐにサイズ不一致を検出できるので、CI パイプラインや組み込み向けビルドスクリプトに組み込むことを強く推奨します。
CI での自動チェック例(GitHub Actions)
yaml
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
name: Size Check on: push: branches: [ main ] jobs: build-linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build and check sizes run: | gcc -std=c11 -Wall -Wextra compile_time_check.c -o check && ./check || exit 1 ``` |
まとめ
sizeofによるランタイム出力と_Static_assertによるコンパイル時検証を組み合わせれば、開発途中でもサイズズレを即座に捕捉できます。これが移植性確保の最も基本的な手段です。
移植性ベストプラクティスまとめ
| 手法 | 具体例 | 効果 |
|---|---|---|
固定幅型使用 (int*_t, uint*_t) | uint32_t id; | バイナリ互換・レジスタマッピングが保証される |
コンパイル時サイズ検証 (_Static_assert) | _Static_assert(sizeof(long) == 8, "LP64 前提"); | ビルドエラーで即座に問題を発覚 |
ポインタ⇔整数変換は uintptr_t / intptr_t | uintptr_t addr = (uintptr_t)p; | アーキテクチャ間のサイズ差に安全に対応 |
環境依存マクロで分岐 (#if defined(_WIN64)) | #ifdef _WIN64 // LLP64 用コード #else // LP64 用コード #endif | ソースが一目でどのプラットフォーム向けか判別可能 |
テストコードにサイズ出力を組み込む (printf("%zu", sizeof(...))) | CI に size_check を走らせる | ビルドごとに実環境を自動検証 |
構造体レイアウトの明示的定義 (#pragma pack, static_assert(sizeof(struct) == ...)) | static_assert(offsetof(Node, next) == 8, "unexpected padding"); | パディングやアラインメントのずれを防止 |
| 外部データフォーマットは固定幅型で定義 | プロトコルヘッダ: uint16_t length; uint32_t crc; | 異なる環境でも同一バイト列になる |
最終的な指針
1. 設計段階で「サイズが変わっても問題ない」かを明確に判断する。
2. 固定幅型以外は必ずsizeof/_Static_assertで検証。
3. プラットフォームごとの差異はマクロ分岐と CI テストで網羅。
おわりに
本ガイドでは、データモデルごとのサイズ差、符号付き・符号なし整数の値域, ポインタサイズが構造体や API に与える影響、そして <stdint.h> の固定幅型と MSVC の実装ポイント を網羅的に解説しました。
- 標準データ型は環境依存で変化するため、必ず比較表を参照してください。
- 移植性が求められるコードでは
int*_t/uint*_tのみ を使用し、ポインタはuintptr_t経由で扱うのが安全です。 - ビルド時・実行時ともにサイズ検証を自動化すれば、環境差異によるバグはほぼ防げます。
「int のサイズが変わっても壊れない」コードを書く ことは、今日のマルチプラットフォーム開発において必須スキルです。ぜひ本記事と PDF 資料を手元に置き、日々の開発プロセスに組み込んでください。