阅读 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。