为何用uint64_t而非uint32_t作数组索引可简化编译器输出?
为何用uint64_t作为数组索引会减少mov指令?
我有一个uint32_t类型的数组(或数组指针),它的长度远小于uint32_t能索引的最大值。但我发现,用uint64_t而非uint32_t作为索引时,编译器生成的汇编代码里少了一条mov指令。
示例源代码
#include <cstdint> void set_value(uint32_t* __restrict array, const uint32_t idx, const uint32_t value) { array[idx] = value; } void set_value(uint32_t* __restrict array, const uint64_t idx, const uint32_t value) { array[idx] = value; }
编译参数与生成的汇编代码
使用Clang 14.0.0,编译参数为-O3 -march=skylake -std=c++17 -mno-vzeroupper,生成的汇编代码如下:
set_value(unsigned int*, unsigned int, unsigned int): mov eax, esi mov dword ptr [rdi + 4*rax], edx ret set_value(unsigned int*, unsigned long, unsigned int): mov dword ptr [rdi + 4*rsi], edx ret
原因分析
这是x86-64架构的调用约定和寻址模式规则导致的:
- 在x86-64的System V调用约定中,前几个函数参数会存入
rdi、rsi、rdx等64位寄存器;如果参数是32位类型(比如uint32_t),则会存入对应的32位子寄存器(比如esi是rsi的低32位)。 - x86-64的内存寻址模式中,若使用32位寄存器作为变址,CPU会自动对其进行符号扩展(而非零扩展)到64位。但
uint32_t是无符号类型,一旦索引的最高位(第31位)为1,符号扩展会让rsi的高32位全变为1,直接导致地址计算错误。 - 针对
uint32_t索引的版本,编译器必须先执行mov eax, esi,把32位的esi零扩展到64位的rax寄存器,确保后续的地址计算[rdi + 4*rax]是正确的无符号偏移。 - 而
uint64_t索引的版本中,参数直接存入64位寄存器rsi,本身就是完整的无符号64位值,无需额外的扩展操作,直接就能用于地址计算,因此省去了那条mov指令。
哪怕你的数组长度远小于uint32_t的最大值,编译器也必须遵守架构规则以避免潜在的地址计算错误,因此会保留这条看似多余的mov指令。
内容的提问来源于stack exchange,提问作者Mark Theis
相关产品推荐
相关产品推荐

