指针是一种保存变量地址的变量。下面代码声明了指针变量p ,它的值是x 的地址:
int x = 1;
int *p = &x;
一元运算符&可用于取一个对象的地址。一元运算符*是间接寻址或间接引用运算符,当它作用于指针时,将访问指针所指向的对象。
下面看一个示例代码:
/*
1.指针的声明与定义
*/
int z = 3;
int *q = &z;
main(){
int x = 1;
int y = 2;
int *ip = &x;
y = *ip; /* 现在 y=1 */
*ip = 0; /* 现在 x=0 */
}
编译后的汇编相关代码如下:
.globl _z
.data
_z:
.long 3
.globl _q
_q:
.long _z
.text
_main:
push ebp
mov ebp, esp
sub esp, 24
mov DWORD PTR [ebp-4], 1
mov DWORD PTR [ebp-8], 2
lea eax, [ebp-4]
mov DWORD PTR [ebp-12], eax
mov eax, DWORD PTR [ebp-12]
mov eax, DWORD PTR [eax]
mov DWORD PTR [ebp-8], eax
mov eax, DWORD PTR [ebp-12]
mov DWORD PTR [eax], 0
leave
ret
可以看到,外部变量z 被编译成了一个标号_z ,标号后面是它的值。而外部指针变量q 也被编译成一个标号_q ,它的值就是标号_z。也就是说,指针变量与普通变量编译后名字看起来没什么区别,都是一个标号或地址,只不过指针变量中存的是另一个地址。
lea 指令指的是加载有效地址(load effective address),与mov 指令进行对比:
lea eax, [ebp-4]
mov eax, [ebp-4]
第一行的lea 指令是将[ebp-4] 的地址也就是[ebp-4] 本身的值赋给了eax ;
第二行的mov 指令则是将[ebp-4] 地址的内存单元中存储的值赋给了eax。
所以以下两行代码即是原代码中的“int *ip = &x;” 语句:
lea eax, [ebp-4]
mov DWORD PTR [ebp-12], eax
这两行代码执行后,栈内存中各变量的值参考以下表格3:
表格3
接下来执行代码:
mov eax, DWORD PTR [ebp-12]
mov eax, DWORD PTR [eax]
mov DWORD PTR [ebp-8], eax
mov eax, DWORD PTR [ebp-12]
mov DWORD PTR [eax], 0
第1行代码执行后,寄存器eax 的值为[ebp-12] 地址中存的值,对照表格3,也就是ebp-4 ;
所以第2行代码中的[eax] 也就相当于[ebp-4] ,第2行代码执行后eax 的值为[ebp-4] 地址中存的值,即1;
第3行代码将eax 的值也就是1存入[ebp-8] 中,[ebp-8]之前存的是y 的值2,此行执行完值变为了1;
这3行代码完成了原代码中“y = * ip; ”语句的功能。
第4行代码与第1行相同,执行后eax 的值为ebp-4;
第5行代码中的[eax] 也就相当于[ebp-4],[ebp-4]之前存的是x 的值1,执行之后[ebp-4]的值变为0,所以变量x 的值也变成了0。
这2行代码完成了原代码中“*ip = 0;”语句的功能。
经过以上分析,明白了C语言的指针变量编译后汇编语言是如何实现的。因为汇编中也有lea 这种取地址的指令,所以通过汇编代码似乎并没有比直接理解C语言更深刻更本质,只是多加了一层翻译而已。就当是从另一个角度学习指针吧。
好了,这篇先写到这里,下一篇继续学习指针与数组的内容。
网友评论