C/C++ 跨平台開發中,32 位(ILP32)與 64 位(LP64)數據模型差異是移植 Bug 的高發區。本文系統對比基本類型、標準庫類型、POSIX 類型在兩種模型下的長度差異,並提供 stdint.h 固定寬度類型、_FILE_OFFSET_BITS 大文件支援等移植最佳實踐。
| 類型 | C 標準最小長度 | 32 位 (ILP32) | 64 位 (LP64) | 備註 |
|---|---|---|---|---|
| char | 8 bit | 8 bit (1B) | 8 bit (1B) | 有符號性實現定義 |
| short | 16 bit | 16 bit (2B) | 16 bit (2B) | |
| int | 16 bit | 32 bit (4B) | 32 bit (4B) | 兩者相同 |
| long | 32 bit | 32 bit (4B) | 64 bit (8B) | 核心差異點 |
| long long | 64 bit | 64 bit (8B) | 64 bit (8B) | C99 標準化 |
| pointer | — | 32 bit (4B) | 64 bit (8B) | 地址總線寬度 |
| float | — | 32 bit (4B) | 32 bit (4B) | IEEE 754 single |
| double | — | 64 bit (8B) | 64 bit (8B) | IEEE 754 double |
| long double | — | 80/96/128 bit | 80/128/160 bit | 實現相關,x86 常 80bit |
<stdint.h> (C99) — 移植首選| 類型 | 長度 | 用途 |
|---|---|---|
| int8_t / uint8_t | 8 bit | 精確位寬需求 |
| int16_t / uint16_t | 16 bit | |
| int32_t / uint32_t | 32 bit | 網絡協議、文件格式 |
| int64_t / uint64_t | 64 bit | 大數據、時間戳 |
| intptr_t / uintptr_t | 指針寬度 | 指針與整數轉換 |
| intmax_t / uintmax_t | 最大寬度 | 通用整數運算 |
鐵律: 除非與 ABI 交互(如系統調用、網絡協議),業務代碼一律使用
int32_t/int64_t等固定寬度類型,禁用long、long long作為存儲類型。
| 類型 | 32 位 | 64 位 | 說明 |
|---|---|---|---|
| size_t | 32 bit | 64 bit | sizeof 結果類型,跟隨指針寬度 |
| ssize_t | 32 bit | 64 bit | 帶符號 size_t,read/write 返回值 |
| off_t | 32 bit (默認) | 64 bit | 文件偏移量,32 位需 _FILE_OFFSET_BITS=64 |
| ino_t | 32 bit | 64 bit | inode 編號,跟隨指針寬度 |
| time_t | 32 bit (Y2038 問題) | 64 bit | 現代系統均遷移至 64 bit |
| pid_t | 32 bit | 32 bit | 進程 ID,通常 32 bit 足夠 |
| uid_t/gid_t | 32 bit | 32 bit | 用戶/組 ID |
# 編譯旗標
CFLAGS += -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE
# 或在代碼最前面(包含頭文件前)
#define _FILE_OFFSET_BITS 64
#define _LARGEFILE_SOURCE
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
off_t 變為 64 位(即 off64_t)struct stat 變為 struct stat64lseek/ftruncate/mmap 等自動映射到 64 位版本fopen 模式字串可用 "rb" 而非 "r"#include <stdio.h>
#include <unistd.h>
int main() {
printf("sizeof(off_t) = %zu\n", sizeof(off_t));
printf("_FILE_OFFSET_BITS = %d\n", _FILE_OFFSET_BITS);
return 0;
}
// ❌ 錯誤:64 位下截斷指針高 32 位
long ptr_val = (long)ptr;
void *ptr2 = (void*)ptr_val;
// ✅ 正確:使用 intptr_t/uintptr_t
uintptr_t ptr_val = (uintptr_t)ptr;
void *ptr2 = (void*)ptr_val;
// 打印指針
printf("ptr = %p\n", (void*)ptr); // 標準方式
printf("ptr = 0x%lx\n", (unsigned long)ptr); // 兼容方式(需匹配長度)
struct Example {
char c; // 1B
// 3B/7B padding
int i; // 4B
// 0B/4B padding
long l; // 4B/8B
// 0B/4B padding
void *p; // 4B/8B
};
// 32 位:1+3+4+0+4+0+4 = 16B (或 20B 視對齊)
// 64 位:1+7+4+4+8+0+8 = 32B
建議:
#pragma pack 或 __attribute__((packed)) 僅在網絡/存儲協議必要時printf("%d", long_var) 64 位下未定義行為 → 用 %ld 或 PRId64malloc(n * sizeof(int)) 整數溢出 → 用 calloc 或檢查 n < SIZE_MAX/sizeof(int)htonl/ntohl 僅處理 32 位,64 位用 htonll/ntohll (非標準) 或手動移位long/size_t總結: 32/64 位核心差異在
long、pointer、size_t、off_t。現代開發默認 64 位,32 位僅作兼容。代碼中全面採用stdint.h固定寬度類型,編譯開啟-D_FILE_OFFSET_BITS=64,即可消除 90% 移植問題。