带你学习《深入理解计算机系统》程序语言的底层描述(5)——数组、指针的汇编实现以及C程序嵌入汇编

这一节我讨论下数组和指针的汇编实现,以及在C程序中嵌入简单汇编代码的内容,至于条件判断、循环、goto等内容,资料很多且比较程式化,就不再涉及了。

 

一:数组的汇编实现

        对指针和数组理解的深入程度是衡量人C语言水平的分水岭,其概念略显抽象,现在要讨论汇编的实现,当然就更抽象,所以在看这节之前,先确保自己对C语言指针和数组的基本概念的明确是很重要的。

        汇编语言没有指针和数组的概念,汇编某种程度上可以看成是寄存器语言,而c更像虚存语言。寄存器里面存的值,代表的是地址还是常数值,CPU在执行汇编时本身是不清楚的,这些都由编译器根据上层语言来确定的。某个空间存的值,不能确定是地址还是常数,人理解起来当然觉得抽象,因此C才有了指针变量这种特殊的对象;同理,内存空间地址的随意跳变和间接引用取值也会让人深感心里无底,因此C又有了数组这种类似“变量串”的对象。

        下面是一个数组引用的例子和对应的汇编代码,分析完此例,数组的汇编实现应该就比较清楚了。

 

int decimal(int *x)
{
    int i;
    int val = 0;


    for (i = 0; i < 5; i++)
        val = (10 * val) + *(&x[i]+i);  //先别管程序执行的目的,因为编译器也不知道你想干嘛


    return val;
}
 

00000000 <decimal5>:
   0:   55                        push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   53                        push   %ebx
   4:   8b 5d 08              mov    0x8(%ebp),%ebx
   7:   b8 00 00 00 00    mov    $0x0,%eax
   c:   b9 00 00 00 00    mov    $0x0,%ecx
  11:   8d 14 80             lea    (%eax,%eax,4),%edx
  14:   8b 04 cb             mov    (%ebx,%ecx,8),%eax
  17:   8d 04 50             lea    (%eax,%edx,2),%eax
  1a:   41                       inc    %ecx
  1b:   83 f9 04              cmp    $0x4,%ecx
  1e:   7e f1                   jle    11 <decimal+0x11>
  20:   5b                       pop    %ebx
  21:   c9                       leave  
  22:   c3                       ret      

        程序计数器3的压栈是实现“被调用者保存”寄存器ebx,接下来计数器4,获取参数x的值,传送给ebx,注意x是指针,里面存了整型变量的地址,因此这个地址值就顺利的被ebx保存鸟,既然都存地址,因此指针变量x和%ebx可以看成是等价的了(如果把mov替换成lea,那ebx得到的可就是参数x本身的地址了,而不是它存的地址O(∩_∩)O~)。

        计数器7和c明显是初始化局部变量,eax和ecx明显是“调用者保存”寄存器,所以decimal作为被调用者就随便用啦,从后面的语句就能得知eax对应val,ecx对应i,可千万别仅仅依赖顺序哦!计数器11的操作很奇妙,实际运算效果是%eax+4*%eax=5*%eax,将5*val赋值给edx,不管先往下走,计数器14,%ebx+8*%ecx,就是*(x+8*i)赋值给eax,加*是因为这里是mov了不是上面的lea!是不是很高端?为什么要把x移动8倍的i呢?这就要从*(&x[i]+i)说起。

        &x[i]是某个int型变量的地址,由于它的这一特殊属性,使得做加法运算时,你必须按照int型的步进来计算。因此&x[i]+1就是下一个int型变量的地址,也就是&x[i+1],同理&x[i]+i就是&x[2i],而既然int型是4字节的步进,因此我们很轻易的得出x+2i*4 的结论。

        好了,%eax现在存储了*(&x[i]+i); 下一步计数器17,计算的是%eax+2*edx,上面我们知道edx存的是5*val,很自然就推出%eax+2*edx计算的是*(&x[i]+i) + 10*val的值,并将其传送给eax保存。好了,计数器1a是让i做自加,1b和1e是循环的判断和跳转,代码的分析暂告一段落。

        这里有个问题,为啥要先计算5*val,然后再计算10*val呢?编译器有个很明显的倾向就是,不到万不得已不会用乘法指令imul,因为早期的CPU处理乘法非常费时,而加法运算要快上很多倍,所以才会使用lea的性质代替乘法和加法得出数值,事实上现在的CPU做乘法的速度已经非常接近加法了,不过目前GCC还没有为此作出修改的动作。

        上面的例子我们看到,编译器利用lea和mov很好的实现了数组的功能,不过注意这只是一级数组,多级数组的情况又如何呢?看下面的例子。

typedef  int row34_t[3][4];  

………

int decimal_m(row34_t *x)
{
        return

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在C语言中,数组指针指针数组是两个重要的概念。数组指针是指一个指针,该指针指向一个有多个元素的数组。例如,`int (*parr)`表示一个指针parr,它指向一个有5个整数元素的数组数组指针的本质是一个指针指针数组是指一个数组,该数组的元素都是指针。例如,`int* p2`表示一个包含4个整型指针数组指针数组的本质是数组。 在使用数组指针指针数组时,我们必须清楚它们的本质和如何使用。数组指针通过指针的方式来访问二维数组,可以使用指针指针运算符`*`和数组的下标运算符`[]`来访问数组元素。 指针数组通过数组的方式来访问二维数组,需要使用两层循环来遍历数组元素,并通过指针数组的下标运算符`[]`和间接访问运算符`*`来访问数组元素。 在C语言中,表示二维数组每行起始地址的方式有多种,可以使用数组名和下标、数组名和指针运算符`*`、数组名和指针运算符`&`来表示。不同的表示方式都可以得到相同的结果,即二维数组每行的起始地址。 综上所述,数组指针指针数组是C语言中的重要概念,它们分别表示一个指针和一个数组,用于访问二维数组。使用数组指针的方式可以通过指针运算符`*`和数组下标运算符`[]`来访问数组元素,而使用指针数组的方式需要使用两层循环,并结合指针数组的下标运算符`[]`和间接访问运算符`*`来访问数组元素。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值