指针三
通过先前指针的学习,了解了指针和地址以及数据的关系,现在结合先前的知识继续学习巩固
指针遍历数组
有了先前的基础,再来看看如何用指针遍历数组
代码
#include "stdafx.h"
void function(){
short arr[5]={1,2,3,4,5};
short* p=&arr[0];
short* p2=arr;
if(p==p2){
printf("equal\n");
}else{
printf("not equal\n");
}
int i;
for(i=0;i<5;i++){
int j=*(p+i);
printf("addr:%x value:%d\n",p+i,j);
}
}
int main(int argc, char* argv[])
{
function();
return 0;
}
代码说明
稍微说明的一下代码部分
主要是声明了一个数组,然后用两种方法取得数组的首地址,一种是&arr[0],另一种则直接是arr
后面则是通过循环配合指针遍历数组成员并输出
按顺序依次涉及先前指针笔记的知识点:
- 取变量地址
- 指针赋值
- 指针之间的比较
- 取地址中存储数据
- 指针的加减
运行结果
可以看到通过&arr[0]和arr取数组首地址得到的结果是一致的
并且能够通过指针来输出数组成员的地址和对应的数据
这里还会观察到数组里的每个地址都相差2(short类型的数据宽度),和先前数组的学习又匹配上了
反汇编代码
11: short arr[5]={1,2,3,4,5};
0040D7A8 mov word ptr [ebp-0Ch],offset function+1Ch (0040d7ac)
0040D7AE mov word ptr [ebp-0Ah],offset function+22h (0040d7b2)
0040D7B4 mov word ptr [ebp-8],offset function+28h (0040d7b8)
0040D7BA mov word ptr [ebp-6],offset function+2Eh (0040d7be)
0040D7C0 mov word ptr [ebp-4],offset function+34h (0040d7c4)
12:
13: short* p=&arr[0];
0040D7C6 lea eax,[ebp-0Ch]
0040D7C9 mov dword ptr [ebp-10h],eax
14: short* p2=arr;
0040D7CC lea ecx,[ebp-0Ch]
0040D7CF mov dword ptr [ebp-14h],ecx
15:
16: if(p==p2){
0040D7D2 mov edx,dword ptr [ebp-10h]
0040D7D5 cmp edx,dword ptr [ebp-14h]
0040D7D8 jne function+59h (0040d7e9)
17: printf("equal\n");
0040D7DA push offset string "equal\n" (00422fbc)
0040D7DF call printf (0040d710)
0040D7E4 add esp,4
18: }else{
0040D7E7 jmp function+66h (0040d7f6)
19: printf("not equal\n");
0040D7E9 push offset string "not equal\n" (00422fb0)
0040D7EE call printf (0040d710)
0040D7F3 add esp,4
20: }
21:
22: int i;
23:
24: for(i=0;i<5;i++){
0040D7F6 mov dword ptr [ebp-18h],0
0040D7FD jmp function+78h (0040d808)
0040D7FF mov eax,dword ptr [ebp-18h]
0040D802 add eax,1
0040D805 mov dword ptr [ebp-18h],eax
0040D808 cmp dword ptr [ebp-18h],5
0040D80C jge function+0A8h (0040d838)
25: int j=*(p+i);
0040D80E mov ecx,dword ptr [ebp-18h]
0040D811 mov edx,dword ptr [ebp-10h]
0040D814 movsx eax,word ptr [edx+ecx*2]
0040D818 mov dword ptr [j],eax
26: printf("addr:%x value:%d\n",p+i,j);
0040D81B mov ecx,dword ptr [j]
0040D81E push ecx
0040D81F mov edx,dword ptr [ebp-18h]
0040D822 mov eax,dword ptr [ebp-10h]
0040D825 lea ecx,[eax+edx*2]
0040D828 push ecx
0040D829 push offset string "addr:%x value%d:\n" (00422f9c)
0040D82E call printf (0040d710)
0040D833 add esp,0Ch
27: }
0040D836 jmp function+6Fh (0040d7ff)
28:
29: }
反汇编分析
由于循环和数组等相关的知识在先前的笔记已经详细学习过了,这里就直接看指针相关的代码
25: int j=*(p+i);
0040D80E mov ecx,dword ptr [ebp-18h]
0040D811 mov edx,dword ptr [ebp-10h]
0040D814 movsx eax,word ptr [edx+ecx*2]
0040D818 mov dword ptr [j],eax
1.将ebp-18h里的值赋值给ecx,这里的[ebp-18h]其实对应的就是 i
0040D80E mov ecx,dword ptr [ebp-18h] (i)
2.将ebp-10h里的值赋值给edx,这里的[ebp-10h]其实对应的是p,即数组首地址
0040D811 mov edx,dword ptr [ebp-10h] (p)
3.movsx是带符号扩展赋值,将edx+ecx2,也就是p+i\数据宽度地址里存储的值赋给eax
0040D814 movsx eax,word ptr [edx+ecx*2]
为什么要使用movsx指令?
内存对齐的结果,先前的笔记就提到过:char short 在计算时都会转变为dword宽度来进行计算
相关笔记可以参考:
逆向基础笔记十三 汇编C语言类型转换
逆向基础笔记十八 汇编 结构体和内存对齐
4.将前面暂存在寄存器中的值赋给变量j
0040D818 mov dword ptr [j],eax
指针翻转数组
翻转数组思想
翻转数组的思想就是从数组两端(数组首部和数组尾部)开始然后逐渐向中间靠拢,相互交换数组中的内容
代码
#include "stdafx.h"
void function(){
int arr[5]={1,2,3,4,5};
int* begin=&arr[0];
int* end=begin+4;
while(begin<end){
int tmp=*begin;
*begin=*end;
*end=tmp;
begin++;
end--;
}
int i;
for(i=0;i<5;i++){
printf("%d\n",arr[i]);
}
}
int main(int argc, char* argv[])
{
function();
return 0;
}
代码分析
数组翻转的关键代码是:
int* begin=&arr[0];
int* end=begin+4;
while(begin<end){
int tmp=*begin;
*begin=*end;
*end=tmp;
begin++;
end--;
}
1.获取数组首地址和尾地址
int* begin=&arr[0];
int* end=begin+4;
2.循环直到所有数组成员交换结束
while(begin<end){
}
3.取出begin中的数据放在临时变量中
int tmp=*begin;
4.用end里存储的值覆盖begin
*begin=*end;
5.将原本备份的begin的变量tmp赋值给end,此时已经完成了交换
*end=tmp;
6.继续交换,让指针向数组中间靠拢
begin++;
end--;
运行结果
可以看到,数组成功翻转了
反汇编实现翻转数组
前面使用指针实现了数组的翻转,为进一步了解其本质,自己手写汇编代码实现翻转数组
下面的汇编代码中省略了 dword ptr ds:[],默认就是取dword
代码
#include "stdafx.h"
void function(){
int arr[5]={1,2,3,4,5};
int len=sizeof(arr)/sizeof(int)-1;
__asm{
xor ecx,ecx
_begin:
mov eax,len
sub eax,ecx
lea edx,[arr+ecx*4]
push edx
mov edx,[edx]
lea ebx,[arr+4*eax]
push ebx
mov ebx,[ebx]
xchg [arr+ecx*4],ebx
pop ebx
mov [ebx],edx
pop edx
inc ecx
cmp edx,ebx
jb _begin
}
int i;
for(i=0;i<5;i++){
printf("%d\n",arr[i]);
}
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果
能够正确地实现相同的功能O(∩_∩)O
反汇编代码分析
__asm{
xor ecx,ecx
_begin:
mov eax,len
sub eax,ecx
lea edx,[arr+ecx*4]
push edx
mov edx,[edx]
lea ebx,[arr+4*eax]
push ebx
mov ebx,[ebx]
xchg [arr+ecx*4],ebx
pop ebx
mov [ebx],edx
pop edx
inc ecx
cmp edx,ebx
jb _begin
}
1.将ecx清零,初始化ecx,ecx在这里是作为偏移量来使用的(刚开始为首地址的偏移,后来慢慢往中间靠拢)
xor ecx,ecx
2.声明一个程序段,后续跳转会用到
_begin:
3.将数组的长度减1的值赋给eax,因为数组从0开始,所以要减1
mov eax,len
4.用先前的eax减去ecx获得偏移(刚开始为尾地址的偏移,后来慢慢往中间靠拢)
sub eax,ecx
5.通过数组首地址加上ecx偏移取得地址,刚开始取得的为首地址,相当于edx=begin
lea edx,[arr+ecx*4]
6.将前面获得的地址edx放入堆栈中
push edx
7.取出edx中的值,这里相当于edx=*begin=tmp
mov edx,[edx]
8.通过数组首地址加上偏移eax取得地址,刚开始取得的为尾地址,相当于ebx=end
lea ebx,[arr+4*eax]
9.将前面获得的地址ebx放入堆栈中
push ebx
10.取出ebx中的值,这里对相当于ebx=*end
mov ebx,[ebx]
11.交换arr+ecx*4(*begin)和ebx(*end)里存储的值,这里相当于*begin=*end
xchg [arr+ecx*4],ebx
12.将先前的push的end的地址恢复到ebx,使得ebx=end
pop ebx
13.这里相当于*end=tmp,此时数组中的两个成员就已经交换完毕了
mov [ebx],edx
14.将先前push的begin的地址恢复到edx,使得edx=begin
pop edx
15.让ecx自增一,这里相当于begin++;end--; 因为这里end的偏移是通过len-begin的偏移得到的
inc ecx
16.比较edx和ebx
jb:jump below,小于则跳转(无符号),这里相当于while(begin<end)中的比较
如果begin<end则继续跳回去执行
cmp edx,ebx
jb _begin
总结
可以通过指针来存储数组的各成员地址,然后用指针来遍历数组,翻转数组