← SEARCHABLE ARCHIVE
Assembly · 2026-08-15

閱讀 x86-64 陣列、字串與結構迴圈

在真實迴圈中追蹤指標與長度契約、stride、sentinel 與邊界檢查

INTERACTIVE READING MODE →

閱讀 x86-64 陣列、字串與結構迴圈

本文章以教學為目的。 範例使用 Intel 語法;pointer validity、object lifetime、aliasing 與 overflow 仍是 C/C++ 邊界的契約。

真實 binary 常見「從起始位址以固定 stride 前進,同時檢查或累積」的迴圈。陣列、字串、結構陣列共享這個骨架,但終止條件與 stride 不同。

找出四種狀態

  1. base/current pointer
  2. index、end pointer 或 remaining count
  3. accumulator 或搜尋結果
  4. 終止條件與 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。

官方資料