閱讀 x86-64 陣列、字串與結構迴圈
在真實迴圈中追蹤指標與長度契約、stride、sentinel 與邊界檢查
閱讀 x86-64 陣列、字串與結構迴圈
本文章以教學為目的。 範例使用 Intel 語法;pointer validity、object lifetime、aliasing 與 overflow 仍是 C/C++ 邊界的契約。
真實 binary 常見「從起始位址以固定 stride 前進,同時檢查或累積」的迴圈。陣列、字串、結構陣列共享這個骨架,但終止條件與 stride 不同。
找出四種狀態
- base/current pointer
- index、end pointer 或 remaining count
- accumulator 或搜尋結果
- 終止條件與 signedness
xor eax, eax
xor r8d, r8d
loop_test:
cmp r8, rdx
jae loop_done
add eax, dword ptr [rcx + r8*4]
inc r8
jmp loop_test
loop_done:
ret
rcx 是 base,r8 是 index,eax 是累積值,unsigned r8 >= rdx 結束迴圈。
index 與 end pointer
lea rdx, [rdi + rsi*4]
xor eax, eax
cmp rdi, rdx
je done
loop_begin:
add eax, dword ptr [rdi]
add rdi, 4
cmp rdi, rdx
jne loop_begin
done:
ret
雖然沒有明示 index,仍可從初始 pointer、4 位元組增量與 end 計算恢復範圍。機器加法能 wrap,但 C/C++ pointer arithmetic 只允許更窄的 object 範圍。
長度與 sentinel
C 字串用 0 位元組作 sentinel。
strlen_like:
mov rax, rcx
scan:
cmp byte ptr [rax], 0
je found
inc rax
jmp scan
found:
sub rax, rcx
ret
程式沒有最大長度,因此強烈假設可讀記憶體內一定有 NUL。不要混淆位元組數、UTF-8 code unit、Unicode code point 與 grapheme cluster。
若搜尋失敗以 -1 回傳,而回傳型別是 size_t,其 bit pattern 是 SIZE_MAX。caller 與 callee 必須對 signed -1 和 unsigned 最大值有同一契約。
stride 與欄位 offset
xorps xmm0, xmm0
test rdx, rdx
jz done
loop_begin:
cmp dword ptr [rcx + 12], 0
je skip
addss xmm0, dword ptr [rcx]
skip:
add rcx, 16
dec rdx
jne loop_begin
done:
ret
offset 12 是欄位候選,offset 0 是 float 候選,step 16 是結構大小候選。這是當前 binary layout 的證據,應以 static_assert 與 compiler layout 驗證。
AoS 常出現同一 base 的 stride 與多個固定 offset;SoA 常出現每個欄位的獨立 base。若只批次處理單一欄位,改變資料 layout 可能比 handwritten ASM 更有效。
unroll、vector 與 tail
loop4:
add eax, [rcx]
add eax, [rcx + 4]
add eax, [rcx + 8]
add eax, [rcx + 12]
add rcx, 16
sub rdx, 4
jae loop4
一次 machine loop 處理四個元素,通常另有 0~3 個元素的 remainder。不要在看到主迴圈後就假設長度一定是 4 的倍數。
movdqu xmm0, [rcx]
paddd xmm1, xmm0
paddd 表示四個 32 位元 lane,不是單一 128 位元整數。vectorized loop 常分成 prologue、SIMD main、scalar tail。
overlap 與 size overflow
memcpy 要求不重疊,memmove 必須處理重疊,typed loop 另有 lifetime、alignment、aliasing 契約。
bytes = count * sizeof(Item);
若乘法 wrap 成小 allocation,而迴圈仍以原 count 前進,就可能造成 heap overflow。要一起審查 allocation size 與 loop bound,並驗證 count <= SIZE_MAX / sizeof(Item)。
總結
資料迴圈可拆成 pointer、stride、accumulator、終止條件。sentinel 迴圈以 terminator 存在取代明示長度。必須連同空輸入、tail、overlap、byte/element 單位與 overflow 才能恢復完整 memory contract。