gcc 内嵌汇编的学习笔记 I

来源:互联网 发布:知行网络 编辑:程序博客网 时间:2024/06/05 11:23

gcc 内嵌汇编的学习笔记 I
                           --第一个混合编码的加法计算器
  作者:ShellEx. 
  ShellEx.cn && blog.csdn.net/shellex 版权所有
  
写了一段简单的代码:

#include <stdio.h>
int main() {
 int in1 = 0, in2 = 0, out = 0;
 printf("PLZ input 2 Number like this: (x1 + x2) /n");
 scanf("%d + %d", &in1, &in2);
 asm volatile(
  "add %1, %0/n/t"
  "add %2, %0/n/t"
  "nop/n/t"
  :"=r"(out)
  :"r"(in1),"r"(in2)
  :
 );
 printf("%d + %d = %d./n", in1, in2, out);
 return 0;
}
///////////////////////////////////////////////////////////////
输入两数,求他们的和。心想估计没有问题,就编译运行。
运行结果:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 10.
晕一个,和我想的不一样啊。去看看汇编代码吧(gcc加上-S参数可以生
成汇编源文件):

...
 movl $0, -4(%ebp)
 movl $0, -8(%ebp)
 movl $0, -12(%ebp)
...
 movl -4(%ebp), %edx
 movl -8(%ebp), %eax
/APP
 add %edx, %eax
 add %eax, %eax
 nop
 
/NO_APP
 movl %eax, -12(%ebp)
 movl -12(%ebp), %eax
...
///////////////////////////////////////////////////////////////
以上代码仅仅关键部分,前后有省略。在/APP和/NO_APP之间的是内嵌汇
编部分。可以看到,gcc先把-4(%ebp)和-8(%ebp)(分别是in1和in2)
放入%edx和%eax寄存器,然后执行第一个add使得eax保存有两个数的和,
第二个add又把eax加在自己身上,最后把eax的值赋给-12(%ebp)也就是
out.功能大约相当于下面的C++代码:
tmp1 = in1;
tmp2 = in2;
tmp1 += tmp2;
tmp1 += tmp1;
out = tmp1;
结果当然不对啦。这是由于gcc不知道错误地使用eax寄存器造成的。他并
不知道%2和%0用了同一个寄存器呢。所以我在输出部分加入"&"限制符。

 asm volatile(
  "add %1, %0/n/t"
  "add %2, %0/n/t"
  "nop/n/t"
  :"=&r"(out)
  :"r"(in1),"r"(in2)
  :
 );
 
///////////////////////////////////////////////////////////////
编译执行:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 2009143897.
又出现了奇怪的问题。再看看汇编代码:

 movl -4(%ebp), %edx
 movl -8(%ebp), %eax
/APP
 add %edx, %ecx
 add %eax, %ecx
 nop
 
/NO_APP
 movl %ecx, %eax
 movl %eax, -12(%ebp)
///////////////////////////////////////////////////////////////
貌似没问题,其实不然。存放结果的ecx寄存器并没有一开始就把out
的值读入(不像in1 和 in2,被读入edx和eax)。说明gcc对于使用"r"
限制符的输出变量,并没有像对待输入变量那样分配完寄存器后就读入
值。资料表明,这个行为可能是由于AT&T汇编源于CISC架构处理器的汇
编语言,在CISC架构处理器中大部分指令输入输出明显分开,而不像
RISC架构处理器那样一个操作数可以输出也可以输入。
所以我应该让gcc知道,我的out也应该享受同等待遇。

#include <stdio.h>
int main() {
 int in1 = 0, in2 = 0, out = 0;
 printf("PLZ input 2 Number like this: (x1 + x2) /n");
 scanf("%d + %d", &in1, &in2);
 asm volatile(
  "add %1, %0/n/t"
  "add %2, %0/n/t"
  "nop/n/t"
  :"=&r"(out)
  :"r"(in1),"r"(in2),"0"(out)
  :
 );
 printf("%d + %d = %d./n", in1, in2, out);
 return 0;
}
///////////////////////////////////////////////////////////////
在输入部分添加对out的描述,并且用限定符"0"指定out就是输入部分的
那个%0.因为gcc是不会去判断%1和%3是否关联自同一个变量的。所以需要
显性指定。
汇编代码如下。

 movl -4(%ebp), %ecx
 movl -8(%ebp), %edx
 movl -12(%ebp), %eax
/APP
 add %ecx, %eax
 add %edx, %eax
 nop
 
/NO_APP
 movl %eax, -12(%ebp)
///////////////////////////////////////////////////////////////
编译运行的输出也是正确的了:
PLZ input 2 Number like this: (x1 + x2)
2+3
2 + 3 = 5. 
但是这么写真是麻烦。其实有个更简单的方法,就是使用"+"限定符。表
示操作数可以读和写。
 
 asm volatile(
  "add %1, %0/n/t"
  "add %2, %0/n/t"
  "nop/n/t"
  :"+r"(out)
  :"r"(in1),"r"(in2)
  :
 );
/////////////////////////////////////////////////////////////// 
汇编代码如下:


 movl -12(%ebp), %edx
 movl -4(%ebp), %ecx
 movl -8(%ebp), %eax
/APP
 add %ecx, %edx
 add %eax, %edx
 nop
 
/NO_APP
 movl %edx, -12(%ebp)
 
/////////////////////////////////////////////////////////////// 
没想到才刚刚开始就遇到这么多问题,记录下来,就算是学习笔记。既然
是笔记,如果看官们发现错误,请一定不吝赐教。谢谢观赏。

shellex.cn && blog.csdn.net/shellex 版权所有 

原创粉丝点击