一、C程序
int main(void) {
int a = 1; // 定义变量 a,值为 1
int *p = &a; // 定义指针 p,指向 a
*p = 2; // 修改指针 p 所指变量 a 的值为 2,即 a = 2
int **pp = &p; // 定义指针 pp,指向 p
**pp = 3; // 修改指针 pp 所指变量 p 的所指变量 a 的值为 3,即 a = 3
return 0;
}
二、对应的汇编代码
; C代码:int a = 1;
mov DWORD PTR [rbp-0xc],0x1 // 把数字 1 存放到内存单元[rbx-0xc]处(即变量 a 的内存单元)
; C代码:int *p = &a;
lea rax,[rbp-0xc] // 把内存单元[rbx-0xc]的地址存入rax寄存器
mov QWORD PTR [rbp-0x18],rax // 把rax寄存器中的值存放到内存单元[rbp-0x18]处
// 此时内存单元[rbp-0x18]里的值就是内存单元[rbx-0xc]的地址
; C代码:*p = 2;
mov rax,QWORD PTR [rbp-0x18] // 把内存单元[rbp-0x18]的值(即变量 a 的地址)存入rax寄存器
mov DWORD PTR [rax],0x2 // 把数字 2 存放到内存单元[rax]处(即变量 a 的内存单元)
// 此时 a 的值变成了 2
; C代码:int **pp = &p;
lea rax,[rbp-0x18] // 把内存单元[rbp-0x18]的地址存入rax寄存器
mov QWORD PTR [rbp-0x8],rax // 把rax寄存器中的值存放到内存单元[rbp-0x8]处
// 此时内存单元[rbp-0x8]里的值就是内存单元[rbx-0x18]的地址
// 而内存单元[rbp-0x18]里的值又是内存单元[rbx-0xc]的地址
// 而内存单元[rbx-0xc]的值即是变量 a 的值,是 2
; C代码:**pp = 3;
mov rax,QWORD PTR [rbp-0x8] // 把内存单元[rbp-0x8]的值(即变量 p 的地址)存入rax寄存器
mov rax,QWORD PTR [rax] // 把内存单元[rax]的值(即变量 a 的地址)存入rax寄存器
mov DWORD PTR [rax],0x3 // 把数字 3 存放到内存单元[rax]处(即变量 a 的内存单元)
// 此时 a 的值变成了 3
三、内存布局
1、执行完int a = 1
之后的内存布局
变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|
a | &a | 0x61fe14 | 0x01 |
2、执行完int *p = &a
之后的内存布局
变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|
p | &p | 0x61fe08 | 0x61fe14 |
a | &a | 0x61fe14 | 0x01 |
这里,变量p
的内存值是变量a
的内存地址。
3、执行完*p = 2
之后的内存布局
变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|
p | &p | 0x61fe08 | 0x61fe14 |
a | &a | 0x61fe14 | 0x02 |
与上一步的区别是,变量a
的内存值从1
变成了2
。
4、执行完int **pp = &p
之后的内存布局
变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|
pp | &pp | 0x61fe18 | 0x61fe08 |
p | &p | 0x61fe08 | 0x61fe14 |
a | &a | 0x61fe14 | 0x02 |
这里,变量pp
的内存值是变量p
的内存地址,变量p
的内存值是变量a
的内存地址。
5、执行完**pp = 3
之后的内存布局
变量 | 变量地址 | 内存地址 | 内存值 |
---|---|---|---|
pp | &pp | 0x61fe18 | 0x61fe08 |
p | &p | 0x61fe08 | 0x61fe14 |
a | &a | 0x61fe14 | 0x03 |
与上一步的区别是,变量a
的内存值从2
变成了3
。
四、执行*p = 2
时CPU干了些什么
准确来说,干的就是上面的汇编代码里面做的事情:
mov rax,QWORD PTR [rbp-0x18] // 把内存单元[rbp-0x18]的值(即变量 a 的地址)存入rax寄存器
mov DWORD PTR [rax],0x2 // 把数字 2 存放到内存单元[rax]处(即变量 a 的内存单元)
// 此时 a 的值变成了 2
与内存布局对应着来理解就是:
- CPU已经知道变量
p
的内存地址是0x61fe08
,于是去把它的内存值取出来,这个值是0x61fe14
,把这个值放到了rax
寄存器中。 - CPU发现
rax
寄存器中的值也是一个内存地址,接下来就把数字2
存放到了这个地址所在的内存单元处。
那CPU是怎么知道内存单元或寄存器中的值是内存地址的呢?这是我们汇编指令告诉它的。
[rbp-0x18]
和[rax]
中的那个[]
的作用就是:访问括号里面的内存地址。
也就是说,指令里面有[]
,CPU就知道是内存地址了。
五、执行**pp = 3
时CPU干了些什么
汇编代码:
mov rax,QWORD PTR [rbp-0x8] // 把内存单元[rbp-0x8]的值(即变量 p 的地址)存入rax寄存器
mov rax,QWORD PTR [rax] // 把内存单元[rax]的值(即变量 a 的地址)存入rax寄存器
mov DWORD PTR [rax],0x3 // 把数字 3 存放到内存单元[rax]处(即变量 a 的内存单元)
// 此时 a 的值变成了 3
与内存布局对应着来理解就是:
- CPU已经知道变量
pp
的内存地址是0x61fe18
,于是去把它的内存值取出来,这个值是0x61fe08
,把这个值放到了rax
寄存器中。 - CPU发现这时
rax
寄存器中的值也是一个内存地址0x61fe08
,于是去把这个地址对应的内存值取出来,这个值是0x61fe14
,这个值仍然存放到rax
寄存器中。 - CPU发现
rax
寄存器中的值还是一个内存地址,接下来就把数字3
存放到了这个地址所在的内存单元处。
六、总结
在CPU眼中是没有变量一说的,它不知道a
、p
、pp
,它只知道指令,比如:
指令让它从内存里取值放到寄存器;
指令让它把寄存器中的值当成一个地址,然后去另一块内存区域里取值,还是放到寄存器里面;
指令让它把数字3
存放到内存区域里面。