You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何C内联汇编需同时使用earlyclobber与匹配约束?

关于Linux内核__copy_user中GCC汇编约束的疑问

我在Linux内核的arch/x86/lib/usercopy_32.c文件中遇到如下代码:

#define __copy_user(to, from, size)                 
do {                                    
    int __d0, __d1, __d2;                       
    __asm__ __volatile__(                       
        "   cmp  $7,%0\n"                   
        "   jbe  1f\n"                  
        "   movl %1,%0\n"                   
        "   negl %0\n"                  
        "   andl $7,%0\n"                   
        "   subl %0,%3\n"                   
        "4: rep; movsb\n"                   
        "   movl %3,%0\n"                   
        "   shrl $2,%0\n"                   
        "   andl $3,%3\n"                   
        "   .align 2,0x90\n"                
        "0: rep; movsl\n"                   
        "   movl %3,%0\n"                   
        "1: rep; movsb\n"                   
        "2:\n"                          
        ".section .fixup,\"ax\"\n"              
        "5: addl %3,%0\n"                   
        "   jmp 2b\n"                   
        "3: lea 0(%3,%0,4),%0\n"                
        "   jmp 2b\n"                   
        ".previous\n"                       
        _ASM_EXTABLE(4b,5b)                 
        _ASM_EXTABLE(0b,3b)                 
        _ASM_EXTABLE(1b,2b)                 
        : "=&c"(size), "=&D" (__d0), "=&S" (__d1), "=r"(__d2)   
        : "3"(size), "0"(size), "1"(to), "2"(from)      
        : "memory");                        
} while (0)

这段代码本质是将size字节的数据从from地址复制到to地址。我的疑问是:earlyclobber的作用是避免输入与输出使用同一寄存器,而匹配约束要求二者使用同一寄存器,为何要同时使用这两种约束?这二者看似矛盾,其背后原因是什么?


这其实是GCC汇编约束里的常见技巧,核心逻辑是先读取输入值,再覆盖对应寄存器,拆解来看:

  • 匹配约束(如"3"(size)、"0"(size)):告诉编译器,输出约束里的第N个位置和输入约束里的第N个位置共用同一个寄存器。比如"0"(size)是让输出的%0(对应ecx寄存器)预先加载输入的size值,这样汇编代码一开始就能直接用这个寄存器里的输入值,省去了额外的寄存器拷贝操作。

  • earlyclobber(=&c、=&D、=&S):告诉编译器,这些输出寄存器会在汇编代码执行过程中被提前修改——也就是在所有输入操作完成前,寄存器的值就会被覆盖。但这里的关键是:输入值已经在汇编代码启动前被加载到这些寄存器里了,且汇编一开始就读取了这些输入值,后续修改寄存器不会影响输入的使用,因为输入已经被读取完毕。

拿size对应的%0举例:

  1. 输入约束"0"(size)让编译器把size的值放到ecx(即%0)中;
  2. 汇编第一行cmp $7,%0就读取了这个值;
  3. 紧接着movl %1,%0就修改了%0的值,这时候=&c的作用就是提醒编译器:别把其他输入值放到ecx里,我马上要改这个寄存器,不然会破坏还没用到的输入数据。

总结来说,匹配约束是让输入值提前进驻输出寄存器,earlyclobber是禁止编译器把其他输入放到这个即将被修改的寄存器里,二者配合既节省了寄存器拷贝的开销,又避免了输入值被意外覆盖,完全不存在矛盾。

内容的提问来源于stack exchange,提问作者huangjl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 01:04:55