sizeof 是編譯期運算符,但結構體大小受**內存對齊**規則影響,導致相同成員不同順序大小不同。本文對比 Windows/Linux/x64 三種環境規則,提供自動計算算法與成員排序優化策略。
| 平台 | 基本原則 | 示例:struct {int a; double b; int c;} |
|---|---|---|
| Windows (32/64位) | K 字節類型地址須為 K 的倍數 (K=1,2,4,8)。結構體總大小須為**最大成員對齊數**的倍數。 | a@0(4), **pad 4**, b@8(8), c@16(4), **pad 4** → 總 24 字節 |
| Linux 32位 (x86) | ≤4 字節類型按 4 字節對齊;>4 字節 (double, long long) 按 4 字節對齊 (寬鬆)。結構體大小按**最大對齊數**(4) 取整。 | a@0(4), b@4(8), c@12(4) → 總 16 字節 (無填充) |
| Linux 64位 (x86-64) / Windows 64位 | 同 Windows:K 字節類型 K 倍數對齊,結構體按最大對齊數取整。 | 同 Windows → 24 字節 |
保證陣列中每個元素滿足對齊要求。若 struct B {int a; double b; int c;} 大小 20 (非 8 倍數),則 B arr[2] 時 arr[1].b 地址 20+4=24 非 8 倍數 → 錯誤。故尾部填充至 24。
// 輸入:成員列表 (名字, 大小)、對齊基數 ALIGN
void calc_offset(int idx, int *cur_addr, int *total_size, int n) {
if (idx >= n) return;
start[idx] = *cur_addr;
*cur_addr += size[idx];
*total_size += size[idx];
if (*cur_addr % ALIGN == 0) { // 自然對齊
calc_offset(idx+1, cur_addr, total_size, n);
} else {
int pad = ALIGN - (*cur_addr % ALIGN);
if (idx == n-1) { *total_size += pad; return; } // 最後一個只填尾部
if (size[idx+1] > pad) { // 下一個太大,必須填充
*cur_addr += pad; *total_size += pad;
calc_offset(idx+1, cur_addr, total_size, n);
} else { // 嘗試將後續小成員塞入填充區 (緊湊優化)
int i = idx+1;
while (i < n && size[i] <= pad) {
start[i] = *cur_addr;
*cur_addr += size[i]; *total_size += size[i];
pad = ALIGN - (*cur_addr % ALIGN);
if (i == n-1) { *total_size += pad; return; }
i++;
}
calc_offset(i, cur_addr, total_size, n);
}
}
}
原則:按成員大小**遞減**排序 (大→小),或按對齊要求遞減。例如:
// 原順序: int(4), double(8), int(4) → 24 bytes (Win/Linux64)
// 優化順序: double(8), int(4), int(4) → 16 bytes (無填充)
struct Opt { double b; int a; int c; }; // sizeof = 16
練習:已知結構體定義,如何排序成員使大小最小?→ 這是 NP-hard 的背包變體,貪心按大小降序通常近似最優。
pragma pack(n) 或 __attribute__((packed)) 強制壓縮 (犧牲訪問速度、可能觸發總線錯誤)。