0
点赞
收藏
分享

微信扫一扫

C语言内嵌汇编

孟祥忠诗歌 15小时前 阅读 2

1.C语言内嵌汇编是汇编语言吗

    ​内嵌汇编使用的是真正的汇编指令,但它的编写方式和使用体验与独立的汇编源程序有很大不同​​。编译器在其中扮演了一个“翻译官”和“协调者”的角色。程序员可以把其中一部分工作交给编译器来实现,而不是像汇编一样什么都要自己做。

常见的使用场景: ​​性能极致优化​​:对极其关键的热点代码进行手动优化。 ​​访问特殊硬件功能​​:执行标准C语法没有直接对应的处理器指令,如管理中断、访问特定系统寄存器等

2.基本语法

__asm__ __volatile__ (
    "汇编指令模板"
    : "输出操作数列表"  /* 可选 */
    : "输入操作数列表"  /* 可选 */
    : "破坏列表"        /* 可选 */
);

2.1 汇编指令模板与操作数占位符​​

    在指令模板中,使用 %0、%1、%2等来引用后面的操作数。它们按顺序对应:​​输出操作数从 %0开始编号,然后是输入操作数依次编号​​。在AT&T语法中,寄存器名前需要加两个 %,如 %%eax,以区别于操作数占位符。汇编指令模板包含真正的汇编指令,用双引号括起。多条指令间常用 \n\t分隔以确保格式正确。

2.2 操作数约束​​:包括输入和输出

约束字符告诉编译器如何分配寄存器或内存: "r":允许编译器将变量分配在任何通用寄存器。 "a", "b", "c", "d"等:指定必须使用某个特定寄存器(如 eax, ebx, ecx, edx)。 "m":直接操作变量的内存地址。 "=":表示操作数是只写的(输出操作数)。 "+":表示操作数既是输入又是输出。 0-9(数字):指示该输入操作数必须与第n个输出操作数使用相同的存储位置(寄存器或内存)。如果某个输入参数选择与前面某个输出参数使用相同的存储位置,那么输入参数占用一个序号,不能跳过。例如下面的第二个输入参数input2序号是%2:

int result, input1 = 10, input2 = 20;
__asm__ volatile (
    "addl %2, %0"    // 汇编模板:将 %2 (input2) 加到 %0 (result/input1) 上
    : "=r"(result)   // 输出:%0 对应 result
    : "0"(input1),   // 输入1:匹配约束,与 %0 共享位置
      "r"(input2)    // 输入2:%2 对应 input2
);

2.3 ​​破坏列表(Clobber List)​​

    如果汇编指令修改了不在输入/输出操作数列表中指定的寄存器或内存,必须在破坏列表中声明。破坏列表就是你要告诉编译器,你的汇编代码“悄悄”改动了哪些资源,而这些资源并没有在你的输入/输出操作数列表中声明​​。这是为了防止编译器在不知情的情况下,依赖这些被“破坏”的资源原有值,导致程序出错。 寄存器:用寄存器名,如 "%eax", "%ebx"。 内存:如果指令修改了内存,需添加 "memory",这会告诉编译器不要假定内存中的值在汇编执行后保持不变。<br> 常见的几个例子 (1)下面的代码显式使用了 %eax寄存器

int value = 10;
__asm__ __volatile__ (
    "movl $1, %%eax\n\t"  // 显式将1放入eax寄存器
    "addl %%eax, %0"     // 将eax的值加到输出变量上
    : "+r"(value)        // value是读写操作数
    :                     // 无输入
    : "%eax"             // 必须声明!因为eax没在输入输出中指定
);

这里,%eax是我们主动使用的,不属于任何操作数约束,所以必须在破坏列表里声明 "%eax"。 (2)指令影响了标志位     很多算术和逻辑运算指令(如 addl, subl, cmpl)会改变 ​​EFLAGS(或ARM中的CPSR)标志寄存器​​ 中的条件码(Condition Code,简称 cc),比如溢出位、零标志位等。如果后续的C代码包含条件判断,编译器可能假设标志位未被改变。因此,任何影响标志位的汇编指令后都必须加上 "cc"。

int x = 5;
__asm__ __volatile__ (
    "addl $3, %0"  // 加法操作会影响标志位
    : "+r"(x)
    :
    : "cc"        // 声明标志位被修改
);

(3)汇编代码修改了内存     当你的汇编指令直接向某个内存地址写入数据,并且这个内存地址​​不是通过特定的输入/输出操作数绑定的​​(例如,通过指针修改了指向的内容),就需要声明 "memory"。

int data = 100;
int *ptr = &data;
__asm__ __volatile__ (
    "movl $5, (%0)"  // 将5写入ptr指向的内存地址
    :
    : "r"(ptr)       // 输入是ptr的值(一个地址)
    : "memory"       // 声明内存被修改
);
// 执行后,data的值变为5
举报

相关推荐

0 条评论