从汇编推导C语言指针
汇编推导C语言指针
指针,是C语言中的一个重要概念及其特点,而上一章中说到,汇编语言是为了让开发者更好的理解机器语言,并通过汇编语言来处理程序上的错误问题。
而执行机器语言的是CPU,而CPU内部有着各种各样的寄存器,其功能各有不同,目的就是为了推进程序正常运行,因此可以理解为汇编语言就在操作CPU。而C语言则是对汇编语言最纯粹的抽象(因为汇编虽然比机器语言更好理解,但也相对复杂),通俗的说C语言就是在翻译汇编语言,因此理解汇编语言能有助于我们更好的理解C语言的指针。
首先,我们有这样的一个需求,在第一个方法中调用另一个方法,同时需要将该方法中的变量传递过去,使得另一个方法修改该变量的同时,第一个方法能够感知到第二个方法修改了该变量,需要如何做呢?我们知道程序要想运行,必须先将程序加载到内存中,而操作程序中的指令方法,需要将其压入程序的栈中,而栈中保存的都是程序代码的私有数据,那么这些数据从哪里来呢?毫无疑问数据在内存中,保存在内存中的哪里?内存中有对应的地址,因此我们只要有了该数据对应的内存地址,就能操作该数据。所以对于上述的需求,我们只要把该变量的内存地址传递给第二个方法,那么,第二个方法对于该变量的操作,第一个方法也能感知到,因为第一个方法本来就有变量的内存地址!接下来介绍两个汇编指令分别为 leaf,和();
leaf指令:取变量地址
():将()中的地址送到地址总线中操作,简单理解为操作该地址中的变量
介绍完上述的需求和基础信息之后,我们来看一个C语言的例子
#include <stdio.h>
int sum(int *p){
*p = 10;
return 10;
}
int main()
{
/* 我的第一个 C 程序 */
int a = 3;
int f = sum(&a);
return 0;
}
这段代码很容易理解,设置了一个变量a,然后调用了sum方法,同时将a的地址传了进去。我们通过gcc -m32 -S demo.c 编译我们的C文件(已经省略了一些不需要的文件)
.file "demo.c"
.text
.globl sum
.type sum, @function
#函数sum
sum:
.LFB0:
pushl %ebp
movl %esp, %ebp
#调用方法之后,将刚才放入esp的值取出来放入eax中
movl 8(%ebp), %eax
然后将eax中的值当作地址送入地址总线,操作该地址所对应的变量
movl $10, (%eax)
movl $10, %eax
popl %ebp
ret
.cfi_endproc
.LFE0:
.size sum, .-sum
.globl main
.type main, @function
#程序入口main
main:
.LFB1:
pushl %ebp
movl %esp, %ebp
#开辟20个字节的空间
subl $20, %esp
将3压入离栈底-8个字节的位置(因为栈底在高地址,栈顶在低地址)
movl $3, -8(%ebp)
取-8(%ebp)中的地址,并将其放入到eax寄存器中,还记得上面说的吗,leal就是取地址操作
leal -8(%ebp), %eax
将eax的值放入到esp栈底寄存器中,还记得()这个操作码,就是将eax的值放入到esp所对应的内存地址中
movl %eax, (%esp)
#调用sum函数,然后在去看上面的sum函数做了什么
call sum
movl %eax, -4(%ebp)
movl $0, %eax
leave
ret
.LFE1:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
这段汇编代码中,我们重点关注.LFB1,在上面的代码中我做了简单注释,可以看到在eax寄存器中就是存的变量3的内存地址,然后通过leal,()等指令将地址传送到sum方法中,sum方法对变量3的内存地址进行操作,从而在main方法中也能感知到变量3的更改。
那么我们将其对照到C语言,C语言&不就是对leal指令的抽象吗? *不就是对于()的抽象吗?
通过上述的汇编以及C语言的例子,我们更好的理解了C语言的指针。