随笔-内联汇编

[内联汇编很可怕吗?看完这篇文章,终结它!]weixin cnblogs


    __asm__ __volatile__
    (
        "mov %1,    %%rbx\n\t"              // dest值传入rbx 
        "mov %2,    %%rcx\n\t"              // src传入rcx
        "_LOOP:\n\t"                    // 循环开始
            "mov (%%rbx),   %%r8\n\t"       // load 源地址数据到 r8
            "mov %%r8,      (%%rcx)\n\t"    // r8 store 到目标地址
            "add $8,   %%rbx\n\t"           // 源地址向后加8字节
            "add $8,   %%rcx\n\t"           // 目标地址向后加8字节
            "sub $8,   %0\n\t"              // size 减8字节
            "jne _LOOP\n\t"             // size 不为0向上跳转至循环开始处
        :"=r"(size)
        :"r"(dest),"r"(src),"0"(size)
        :"rbx","rcx","r8"
    );

使用的是AT&T语法,mov S,D,和intel汇编风格正好相反

"0"(size) 这里0是什么意思,因为size同时是输出参数,序号是从输出参数开始编号,所以size是%0,然后依次dest、src、size,size已经是输出参数了,所以用对应的输出参数作为下标
"rbx","rcx","r8" 定义被修改的寄存器,rbx、rcx 和 r8 在汇编代码中被修改,需要在输出列表中声明

+++

使用编号还是麻烦,容易出错,还有另一个更方便的操作:扩展 asm 格式还允许给这些占位符重命名,也就是给每一个寄存器起一个别名,然后在内联汇编代码中使用别名来操作寄存器。

    __asm__ __volatile__
    (
        "mov %[v1], %%rbx\n\t"              
        "mov %[v2], %%rcx\n\t"              
        "VAGE_LOOP:\n\t"                    
        "mov (%%rbx), %%r8\n\t"             
        "mov %%r8, (%%rcx)\n\t"             
        "add $8, %%rbx\n\t"                 
        "add $8, %%rcx\n\t"                 
        "sub $8, %[v3]\n\t"                 
        "jne VAGE_LOOP\n\t"                 
        : [v3]"=r"(size)                     
        : [v1]"r"(dest),[v2]"r"(src),"[v3]"(size)
        : "rbx","rcx","r8"                   
    );

"[v3]"(size) 不写或者写成 "r"(size) 都是错误的写法


内容摘录

二、扩展 asm 格式

1. 指令格式

   asm [volatile] ("汇编指令" : "输出操作数列表" : "输入操作数列表" : "改动的寄存器")  

格式说明

1.汇编指令:与基本asm格式相同;
2.输出操作数列表:汇编代码如何把处理结果传递到 C 代码中;
3.输入操作数列表:C 代码如何把数据传递给内联汇编代码;
4.改动的寄存器:告诉编译器,在内联汇编代码中,我们使用了哪些寄存器;“改动的寄存器”可以省略,此时最后一个冒号可以不要,但是前面的冒号必须保留,即使输出/输入操作数列表为空。

关于“改动的寄存器”再解释一下:gcc 在编译 C 代码的时候,需要使用一系列寄存器;我们手写的内联汇编代码中,也使用了一些寄存器。为了通知编译器,让它知道:在内联汇编代码中有哪些寄存器被我们用户使用了,可以在这里列举出来,这样的话,gcc 就会避免使用这些列举出的寄存器

2. 输出和输入操作数列表的格式

在系统中,存储变量的地方就2个:寄存器和内存。因此,告诉内联汇编代码输出和输入操作数,其实就是告诉它:向哪些寄存器或内存地址输出结果;从哪些寄存器或内存地址读取输入数据;

这个过程也要满足一定的格式:

"[输出修饰符]约束"(寄存器或内存地址)

(1)约束

就是通过不同的字符,来告诉编译器
使用哪些寄存器,或者内存地址。包括下面这些字符:

a: 使用 eax/ax/al 寄存器;
b: 使用 ebx/bx/bl 寄存器;
c: 使用 ecx/cx/cl 寄存器;
d: 使用 edx/dx/dl 寄存器;
r: 使用任何可用的通用寄存器;
m: 使用变量的内存位置;

先记住这几个就够用了,其他的约束选项还有:D, S, q, A, f, t, u等等,需要的时候再查看文档。

(2)输出修饰符

顾名思义,它使用来修饰输出的,对输出寄存器或内存地址提供额外的说明,包括下面4个修饰符:

+:被修饰的操作数可以读取,可以写入;
=:被修饰的操作数只能写入;
%:被修饰的操作数可以和下一个操作数互换;
&:在内联函数完成之前,可以删除或者重新使用被修饰的操作数;

posted @   LiYanbin  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
点击右上角即可分享
微信分享提示