今天看了两段有关指针的代码,有点疑惑,就想到汇编是怎么实现的,这样就可以清楚本质了,所以反汇编了一下,看了下结果。
先看这段比较简单的
``` c++
int i = 0x11223344;
int* p1 = &i;
int c = *(p1 + 1); // C有越界问题,这里不讨论这个
```
代码很简单,给i赋值一个地址,然后用int指针取出i的地址,把地址+1后,再取出新地址的值给c。 我们看下汇编代码
``` asm
mov DWORD PTR [rbp-16], 287454020 # 1
lea rax, [rbp-16] # 2
mov QWORD PTR [rbp-8], rax # 3
mov rax, QWORD PTR [rbp-8] # 4
mov eax, DWORD PTR [rax+4] # 5
mov DWORD PTR [rbp-12], eax # 6
```
下面来逐句解释下,先说明下基本的一些参数,这个系统是64位环境的,DWORD表示取2个word,QWORD取4个world,一个word是2个字节,即16位。
1. 把int类型的数存到rbp-16这个开始的内存地址中,分配内存大小为DWORD,4字节即32位<br>
以上对应C++代码: int i = 0x11223344;
2. 把rbp-16的地址值赋值给rax寄存器
3. 从上面赋值的rax寄存器中取出值,赋值给以rbp-8地址开始的内存空间,分配的大小为QWORD,8字节即64位。由于赋值给的是一个指针,指针分配的内存空间是根据系统环境来的,本文的测试环境是64位,所以这里分配也就是64位。<br>
以上对应C++代码: int* p1 = &i;
4.从以rbp-8开始的内存地址中取出QWORD,即8字节,64位数据赋值给rax寄存器。
5.把rax寄存器的值加4个字节(因为int类型的大小是4个字节),然后取出这个地址中的数据赋值给eax.
6.把eax寄存器的值赋值给以rbp-12为起始的内存空间,分配大小是DWORD,即4个字节,32位。<br>
以上对应C++代码: int c = *(p1 + 1);
以上就是这3行C++代码对应的汇编,下面再来看另一段根据以上变形的代码。
``` c++
int i = 0x11223344;
char* p1 = (char*)(&i);
char c = *(p1 + 1); // c的值是0x33
```
同样是3行代码,看下对应的汇编
```asm
mov DWORD PTR [rbp-16], 287454020 # 1
lea rax, [rbp-16] # 2
mov QWORD PTR [rbp-8], rax # 3
mov rax, QWORD PTR [rbp-8] # 4
movzx eax, BYTE PTR [rax+1] # 5
mov BYTE PTR [rbp-9], al # 6
```
前2句的汇编代码和前面是一样的,这里关于第三句说一下,可以看出,虽然汇编代码一样,但是第二句代码一个是int指针,一个是char指针,虽然类型不同,但是如果赋值给一个指针的话,就和类型无关了,因为指针分配的内存大小都是一样的。
4. 和上面一样,也不多说了
5. 这里movzx和mov指令类似,只不过涉及到赋值过程中两边的位数不同,如果一个小位的数赋值给大位的寄存器,用这个指令会用0去填充多余的位数,这里由于eax寄存器是32位,后面赋值给它的是8位的数据,所以两者位数不同,会用movzx进行赋值,即放入eax寄存器的低8位。
这句代码的意思是先把rax寄存器+1个字节(由于p1是char*,char的大小是一个字节),然后从这个地址开始取出BYTE PTR位数的数据,BYTE PTR就是一个字节,即8位。
6.al寄存器是前面eax寄存器是低8位寄存器,同样还有ah,代表高8位,ax低16位等等。这里取出al,即eax寄存器的低8位,即上面用movzx指令传入的低8位数据,然后赋值给rbp-9这地址开始的内存空间,分配大小是BYTE,即一个字节8位,也就是C++代码中的char c = *(p1 + 1);
这句代码最后取出的是int数据,跳过最低的一个字节后,取出8位,即33这个值了。
2段C++代码转汇编小记