如何标记C++函数会修改所有CPU寄存器,令调用方仅保存必要寄存器?
问题解答
关于你观察到的编译现象
你看到的结果完全符合x86平台主流调用约定的规则,以Linux/macOS默认的System V AMD64调用约定为例,寄存器分为两类:
- 调用者保存寄存器(易失寄存器):包括
rax、rcx、rdx、rsi、rdi、r8~r11以及所有xmm/ymm/zmm等SIMD/FPU寄存器。按照约定,被调函数可以直接修改这类寄存器,不需要恢复原值。如果调用方需要保留这些寄存器的值,必须自行在调用前保存、调用后恢复。 - 被调用者保存寄存器(非易失寄存器):包括
rbx、rbp、r12~r15。按照约定,被调函数如果要使用这类寄存器,必须先在函数入口压栈保存,返回前弹出恢复,这就是你实验里看到Clang在modify函数内部自动保存这几个寄存器的原因——你在汇编的clobber列表声明了会修改它们,编译器必须遵守调用约定处理。
核心需求的实现方案
你希望标记函数会修改所有寄存器、让调用方仅保存自身用到的寄存器,不需要被调函数做保存操作,目前主流编译器没有提供你假想的__attribute__((modifies_all_registers))这类直接属性,但可以通过以下方式实现:
- 默认约定已经覆盖易失寄存器场景:编译器默认所有非内联外部函数都会修改全部易失寄存器,如果调用方后续需要用到这类寄存器的值,会自动在调用前完成保存,不需要你额外添加标记。
- 内联包装调用实现全寄存器标记:如果你需要让调用方连非易失寄存器也自动按需保存,可以把函数调用包装为带完整clobber列表的内联汇编,示例如下:
// 你的外部函数声明 extern void your_func(); // 调用包装 __attribute__((always_inline)) void call_your_func() { asm volatile( "call %0" : : "i"(your_func) : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", "rbp", "rsp", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15", "ymm0", "ymm1", "ymm2", "ymm3", "ymm4", "ymm5", "ymm6","ymm7", "ymm8", "ymm9", "ymm10", "ymm11", "ymm12", "ymm13", "ymm14", "ymm15", "zmm0", "zmm1", "zmm2", "zmm3", "zmm4", "zmm5", "zmm6", "zmm7", "zmm8", "zmm9", "zmm10", "zmm11", "zmm12", "zmm13", "zmm14", "zmm15", "memory" ); }
编译器在展开这个内联汇编时,会自动判断当前上下文用到了哪些clobber列表里的寄存器,仅保存这部分寄存器,完全符合你要的性能最优的需求。
3. MSVC平台的对应方案:MSVC没有GCC/Clang的内联汇编clobber语法,你可以用_ReadWriteBarrier配合自定义调用约定,或者直接用汇编包裹调用逻辑手动处理寄存器保存。
内容的提问来源于stack exchange,提问作者Arty
相关产品推荐
相关产品推荐

