某技術博客提出「用 `long*` 掃描 4K buffer 判斷全零比 `memcmp` 快 3 倍」,實測確實更快。但**直接將任意 `char*` 強轉為 `long*` 解引用在嚴格對齊架構 (SPARC, ARMv7, MIPS) 上會觸發 SIGBUS 崩潰**。本文分析對齊陷阱,給出安全、高效、可移植的解決方案。
long* wp = (long*)buffer; // ⚠️ buffer 可能非 4/8 字節對齊
while (*wp++ == 0 && wp < buffer + 4096); // 可能 SIGBUS
if (wp >= buffer + 4096) { /* 全零 */ }
long zero_buf[24] = {0};
long* wp = (long*)((char*)zero_buf + 3); // 故意錯位 3 字節
while (*wp++ == 0); // 💥 EXCEPTION_DATATYPE_MISALIGNMENT
| 架構 | 對齊要求 | 非對齊行為 | ||
|---|---|---|---|---|
| x86/x64 | 無 (支援非對齊) | 較慢,跨緩存行極慢 | ||
| ARMv7 (舊) | 4/8 字節 | Data Abort (SIGBUS) | ||
| ARMv8 (AArch64) | 無 (LSE 指令) | 高效 | ||
| SPARC | 嚴格 | SIGBUS | ||
| MIPS | 嚴格 | SIGBUS | ||
| PowerPC | 建議對齊 | 效能罰分 |
| 方案 | 代碼 | 安全性 | 效能 | 適用場景 |
|---|---|---|---|---|
| 1. memcpy 到對齊變量 | `long v; memcpy(&v, p, sizeof v);` | ✅ 完全安全 | 極高 (編譯器優化為單指令) | 所有架構首選 |
| 2. 逐字節檢查 (memcmp 風格) | `for (i=0;i<4096;i++) if (buf[i]) break;` | ✅ 安全 | 較慢 | 極短 buffer / 教學 |
| 3. 手動對齊後字長掃描 | 見下文 | ✅ 安全 | 高 | 自定義掃描邏輯 |
| 4. 標準庫 memcmp | `memcmp(buf, zero_buf, 4096)==0` | ✅ 安全 | 高 (內部向量化) | 生產代碼推薦 |
bool is_zero_buffer(const void* buf, size_t len) {
const uint8_t* p = (const uint8_t*)buf;
const uint8_t* end = p + len;
// 1. 逐字節處理頭部直到對齊
while ((uintptr_t)p % sizeof(uintptr_t) && p < end) {
if (*p++) return false;
}
// 2. 字長級掃描中間段
const uintptr_t* lp = (const uintptr_t*)p;
const uintptr_t* lend = (const uintptr_t*)(end - sizeof(uintptr_t) + 1);
while (lp < lend) {
if (*lp++) return false; // 任意字節非零 → 字長非零
}
// 3. 逐字節處理尾部
p = (const uint8_t*)lp;
while (p < end) {
if (*p++) return false;
}
return true;
}
若需**定位首個非零字節**,可在字長循環中引入魔數檢測:
uintptr_t word = *lp++;
if (word != 0) {
// 含非零字節,逐字節定位
const uint8_t* cp = (const uint8_t*)(lp - 1);
for (size_t i = 0; i < sizeof(uintptr_t); ++i)
if (cp[i]) return cp + i - (const uint8_t*)buf;
}
「快 3 倍」的前提是 x86 寬容非對齊訪問。追求極致效能不應犧牲可移植性與正確性。**`memcpy` 讀取字長 + 標準庫優化** 才是工業級解法。