在 C语言中,如何通过 asm 关键字嵌入汇编语言代码?

道哥分享
关注

4. test5.c 声明改动的寄存器

在 test4.c 中,我们没有声明改动的寄存器,所以编译器可以任意选择使用哪些寄存器。从生成的汇编代码 test4.s 中可以看到,gcc 使用了  %edx 寄存器。

那么我们来测试一下:告诉 gcc 不要使用 %edx 寄存器。

#include <stdio.h>
int main()

   int data1 = 1;
   int data2 = 2;
   int data3;
   asm("movl %%ebx, %%eax "
       "addl %%ecx, %%eax"
       : "=a"(data3)
       : "b"(data1),"c"(data2)
       : "%edx");
   printf("data3 = %d ", data3);
   return 0;

代码中,asm 指令最后部分 "%edx" ,就是用来告诉 gcc 编译器:在内联汇编代码中,我们会使用到 %edx 寄存器,你就不要用它了。

生成汇编代码指令:

gcc -m32 -S -o test5.s test5.c

来看一下生成的汇编代码 test5.s:

movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl -20(%ebp), %eax
movl -16(%ebp), %ecx
movl %eax, %ebx
#APP
# 10 "test5.c" 1
movl %ebx, %eax
addl %ecx, %eax
# 0 "" 2
#NO_APP
movl %eax, -12(%ebp)

可以看到,在内联汇编代码之前,gcc 没有选择使用寄存器 %edx。

三、使用占位符来代替寄存器名称

在上面的示例中,只使用了 2 个寄存器来操作 2 个局部变量,如果操作数有很多,那么在内联汇编代码中去写每个寄存器的名称,就显得很不方便。

因此,扩展 asm 格式为我们提供了另一种偷懒的方法,来使用输出和输入操作数列表中的寄存器:占位符!

占位符有点类似于批处理脚本中,利用 2...来引用输入参数一样,内联汇编代码中的占位符,从输出操作数列表中的寄存器开始从 0 编号,一直编号到输入操作数列表中的所有寄存器。

还是看例子比较直接!

1. test6.c 使用占位符代替寄存器#include <stdio.h>
int main()

   int data1 = 1;
   int data2 = 2;
   int data3;
   asm("addl %1, %2 "
       "movl %2, %0"
       : "=r"(data3)
       : "r"(data1),"r"(data2));
   printf("data3 = %d ", data3);
   return 0;

代码说明:

输出操作数列表"=r"(data3):约束使用字符 r, 也就是说不指定寄存器,由编译器来选择使用哪个寄存器来存储结果,最后复制到局部变量 data3中;

输入操作数列表"r"(data1),"r"(data2):约束字符r, 不指定寄存器,由编译器来选择使用哪 2 个寄存器来接收局部变量 data1 和 data2;

输出操作数列表中只需要一个寄存器,因此在内联汇编代码中的 %0 就代表这个寄存器(即:从 0 开始计数);

输入操作数列表中有 2 个寄存器,因此在内联汇编代码中的 %1 和 %2 就代表这 2 个寄存器(即:从输出操作数列表的最后一个寄存器开始顺序计数);

生成汇编代码指令:

gcc -m32 -S -o test6.s test6.c

汇编代码如下 test6.s:

movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl -20(%ebp), %eax
movl -16(%ebp), %edx
#APP
# 10 "test6.c" 1
addl %eax, %edx
movl %edx, %eax
# 0 "" 2
#NO_APP
movl %eax, -12(%ebp)

可以看到,gcc 编译器选择了 %eax 来存储局部变量 data1,%edx 来存储局部变量 data2 ,然后操作结果也存储在 %eax 寄存器中。

是不是感觉这样操作就方便多了?不用我们来指定使用哪些寄存器,直接交给编译器来选择。

在内联汇编代码中,使用 %0、%1 、%2 这样的占位符来使用寄存器。

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

还是看代码!

2. test7.c 给寄存器起别名#include <stdio.h>
int main()

   int data1 = 1;
   int data2 = 2;
   int data3;
   asm("addl %[v1], %[v2] "
       "movl %[v2], %[v3]"
       : [v3]"=r"(data3)
       : [v1]"r"(data1),[v2]"r"(data2));
   printf("data3 = %d ", data3);
   return 0;

代码说明:

输出操作数列表:给寄存器(gcc 编译器选择的)取了一个别名 v3;

输入操作数列表:给寄存器(gcc 编译器选择的)取了一个别名 v1 和 v2;

起立别名之后,在内联汇编代码中就可以直接使用这些别名( %[v1], %[v2],  %[v3])来操作数据了。

生成汇编代码指令:

gcc -m32 -S -o test7.s test7.c

再来看一下生成的汇编代码 test7.s:

movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl -20(%ebp), %eax
movl -16(%ebp), %edx
#APP
# 10 "test7.c" 1
addl %eax, %edx
movl %edx, %eax
# 0 "" 2
#NO_APP
movl %eax, -12(%ebp)

这部分的汇编代码与 test6.s 中完全一样!

四、使用内存位置

在以上的示例中,输出操作数列表和输入操作数列表部分,使用的都是寄存器(约束字符:a, b, c, d, r等等)。

我们可以指定使用哪个寄存器,也可以交给编译器来选择使用哪些寄存器,通过寄存器来操作数据,速度会更快一些。

如果我们愿意的话,也可以直接使用变量的内存地址来操作变量,此时就需要使用约束字符 m。

1. test8.c 使用内存地址来操作数据#include <stdio.h>
int main()

   int data1 = 1;
   int data2 = 2;
   int data3;
   asm("movl %1, %%eax "
       "addl %2, %%eax "
       "movl %%eax, %0"
       : "=m"(data3)
       : "m"(data1),"m"(data2));
   printf("data3 = %d ", data3);
   return 0;

代码说明:

输出操作数列表 "=m"(data3):直接使用变量 data3 的内存地址;

输入操作数列表 "m"(data1),"m"(data2):直接使用变量 data1, data2 的内存地址;

在内联汇编代码中,因为需要进行相加计算,因此需要使用一个寄存器(%eax),计算这个环节是肯定需要寄存器的。

在操作那些内存地址中的数据时,使用的仍然是按顺序编号的占位符。

生成汇编代码指令:

gcc -m32 -S -o test8.s test8.c

生成的汇编代码如下 test8.s:

movl $1, -24(%ebp)
movl $2, -20(%ebp)
#APP
# 10 "test8.c" 1
movl -24(%ebp), %eax
addl -20(%ebp), %eax
movl %eax, -16(%ebp)
# 0 "" 2
#NO_APP
movl -16(%ebp), %eax

可以看到:在进入内联汇编代码之前,把 data1 和 data2 的值放在了栈中,然后直接把栈中的数据与寄存器 %eax 进行操作,最后再把操作结果(%eax),复制到栈中 data3 的位置(-16(%ebp))。

五、总结

通过以上 8 个示例,我们把内联汇编代码中的关键语法规则进行了讲解,有了这个基础,就可以在内联汇编代码中编写更加复杂的指令了。

希望以上内容对您能有所帮助!谢谢!

声明: 本文由入驻OFweek维科号的作者撰写,观点仅代表作者本人,不代表OFweek立场。如有侵权或其他问题,请联系举报。
侵权投诉

下载OFweek,一手掌握高科技全行业资讯

还不是OFweek会员,马上注册
打开app,查看更多精彩资讯 >
  • 长按识别二维码
  • 进入OFweek阅读全文
长按图片进行保存