什么是指针
一般关于指针的解释都离不开地址。这里先暂且忘记这个概念
指针其实也是一种数据类型,和先前学习的int float等数据类型没有实质上的区别,只不过这个数据类型是在先前学习的所有数据类型后面加上若干个*号,如char *,int *等等,这种数据类型被称为指针
任意类型后面都可以加上*号,使其成为新的指针数据类型
*可以是任意多个
指针的声明
指针的声明其实在前面介绍什么是指针的时候就已经讲到了,例子如下:
struct S1 {
int a;
};
void function() {
char* a;
short** b;
int*** c;
long**** d;
_int64***** e;
float****** f;
double******* g;
S1******** s1;
}
可以看到所有的其它数据类型(包括结构体)后面加上若干个*后就是所谓的指针了
推荐的声明方式如上
但也可以这样将*放在变量前面,但不推荐,因为这样相当于将这个数据类型拆开了,不利于理解
struct S1 {
int a;
};
void function() {
char *a;
short **b;
int ***c;
long ****d;
_int64 *****e;
float ******f;
double *******g;
S1 ********s1;
}
指针的赋值
在说指针的赋值之前先看看先前普通变量的赋值
普通变量的赋值貌似是直接使用 变量=值即可,但其实是编译器简化了赋值的步骤,实际上在赋值前本应该加上要赋值类型
例子如下:
void function() {
int a;
a=610;
a=(int)610;
}
现在再来看指针的赋值
void function(){
char* a;
a=(char*) 610;
int** b;
b=(int**) 610;
}
在要赋的值前面加上指针的类型即可,貌似和普通变量的赋值并无太大差别,此时也注意到这里的指针也和地址没有什么关联
指针的的类型转换这里暂且不提
指针的数据宽度
研究数据宽度方法
先前研究过其它基本变量的数据宽度,会发现char、short、int都是按照4字节来分配的(内存对齐),但实际使用的情况下则是按照其原本类型的数据宽度来赋值或进行其它操作的
如:
void function(){
char a;
short b;
int c;
a=1;
b=2;
c=3;
}
其对应的反汇编代码为:
11: void function(){
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,4Ch
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-4Ch]
0040101C mov ecx,13h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
12: char a;
13: short b;
14: int c;
15: a=1;
00401028 mov byte ptr [ebp-4],1
16: b=2;
0040102C mov word ptr [ebp-8],offset function+20h (00401030)
17: c=3;
00401032 mov dword ptr [ebp-0Ch],3
18: }
00401039 pop edi
0040103A pop esi
0040103B pop ebx
0040103C mov esp,ebp
0040103E pop ebp
0040103F ret
可以注意到此时提升的堆栈为4Ch,而默认(空函数时)提升的堆栈为40h
00401013 sub esp,4Ch
于是此时为三个变量分配的空间是:4Ch-40h0xC12=3×4,即为char short int都分配了4个字节
但在这三个变量赋值的时候展现出来的就是其原本的数据类型宽度了
15: a=1;
00401028 mov byte ptr [ebp-4],1
在char类型的a中的赋值宽度是 byte,1字节
16: b=2;
0040102C mov word ptr [ebp-8],offset function+20h (00401030)
在short类型的b中的赋值宽度是word,2字节
17: c=3;
00401032 mov dword ptr [ebp-0Ch],3
在int类型的c中的赋值宽度是dword,4字节
研究指针数据宽度
于是如法炮制,按照前面的方法来研究指针的数据宽度
在前面的数据类型后添加*,使其成为指针类型
void function(){
char* a;
short* b;
int* c;
a=(char*) 1;
b= (short*) 2;
c=(int*) 3;
}
其对应的反汇编代码为
11: void function(){
00401010 push ebp
00401011 mov ebp,esp
00401013 sub esp,4Ch
00401016 push ebx
00401017 push esi
00401018 push edi
00401019 lea edi,[ebp-4Ch]
0040101C mov ecx,13h
00401021 mov eax,0CCCCCCCCh
00401026 rep stos dword ptr [edi]
12: char* a;
13: short* b;
14: int* c;
15: a=(char*) 1;
00401028 mov dword ptr [ebp-4],1
16: b= (short*) 2;
0040102F mov dword ptr [ebp-8],2
17: c=(int*) 3;
00401036 mov dword ptr [ebp-0Ch],3
18: }
0040103D pop edi
0040103E pop esi
0040103F pop ebx
00401040 mov esp,ebp
00401042 pop ebp
00401043 ret
直接观察对应的赋值语句:
15: a=(char*) 1;
00401028 mov dword ptr [ebp-4],1
16: b= (short*) 2;
0040102F mov dword ptr [ebp-8],2
17: c=(int*) 3;
00401036 mov dword ptr [ebp-0Ch],3
可以看到,所有赋值的宽度都为dowrd,说明无论是char*、short__、int\__其数据宽度都为4字节
可以使用同样的方法研究float、double、struct等其它数据类型
并且在这里会注意到,指针类型的赋值和非指针类型的赋值在反汇编中并没有什么区别
总结
无论是什么类型,在其后面加上__(无论加几个\__都一样)后其数据宽度都变为4字节
指针的加减
例子
指针类型也支持加减的操作,但不支持乘和除(编译器决定的),来看例子:
#include "stdafx.h"
void function() {
char* a;
short* b;
int* c;
a=(char*) 1;
b= (short*) 2;
c=(int*) 3;
a++;
b++;
c++;
printf("a:%d\t b:%d\tc:%d\n",a,b,c);
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果

分析
这里会观察到结果并不是想象中的2,3,4;而是2,4,7
细心的小伙伴肯定发现了:
2 = 1 + 1 (char数据宽度为1字节)
4 = 2 + 2 (short数据宽度为2字节)
7 = 3 + 4 (int数据宽度为4字节)
结果是加上了原本各自的数据类型的宽度
拓展例子
前面只是都是一级指针,现在将指针换为二级指针:
void function() {
char** a;
short** b;
int** c;
a=(char**) 1;
b= (short**) 2;
c=(int**) 3;
a++;
b++;
c++;
printf("a:%d\t b:%d\tc:%d\n",a,b,c);
}
运行结果

分析
此时的结果为:
5= 1 + 4 (char* 数据宽度为4字节)
6= 2 + 4 (short* 数据宽度为4字节)
7 = 3 + 4 (int* 数据宽度为4字节)
结果为加上 去掉一个*后的数据宽度
拓展例子二
前面的加法操作都只增加了1,现在再来查看增加大于1时的情况
void function() {
char* a;
short* b;
int* c;
a=(char*) 1;
b= (short*) 2;
c=(int*) 3;
a=a+5;
b=b+5;
c=c+5;
printf("a:%d\t b:%d\tc:%d\n",a,b,c);
}
运行结果

分析
此时的结果为:
6= 1 + 5*1 (char 数据宽度为1字节)
12= 2 + 5*2 (short 数据宽度为2字节)
23 = 3 + 5*4 (int 数据宽度为4字节)
结果为加上 去掉一个*后的数据宽度 × 增加的数值
总结
无论是指针的加亦或是减(这里只演示了加法,但减法同理),其加或减的单位为去掉一个*后的数据宽度
也就是实际增减的数值=去掉一个*后的数据宽度 × 增减的数值
指针类型相减
前面提到的指针的加减都是同一个指针里的加减,但指针之间其实也支持相减操作(不支持相加)
但指针之间的加减要求指针的类型必须一致,即char*类型只能和char*类型相加减,不能和char**或其它类型相加减
例子
void function() {
char* a;
char* b;
short* c;
short* d;
int* e;
int* f;
a=(char*) 200;
b=(char*) 100;
c=(short*) 200;
d=(short*) 100;
e=(int*) 200;
f=(int*) 100;
printf("%d\n",a-b);
printf("%d\n",c-d);
printf("%d\n",e-f);
}
运行结果

分析
此时的结果为:
100 = (200 - 100)/1(char 数据宽度为1字节)
50 = (200 - 100)/2 (short 数据宽度为2字节)
25 = (200 - 100)/4 (int 数据宽度为4字节)
结果为相减完后再除以 原本各自的数据宽度
扩展例子
前面只是都是一级指针,现在将指针换为四级指针:
void function() {
char**** a;
char**** b;
short**** c;
short**** d;
int**** e;
int**** f;
a=(char****) 200;
b=(char****) 100;
c=(short****) 200;
d=(short****) 100;
e=(int****) 200;
f=(int****) 100;
printf("%d\n",a-b);
printf("%d\n",c-d);
printf("%d\n",e-f);
}
运行结果

分析
此时的结果为:
25 = (200 - 100)/4(char*** 数据宽度为4字节)
25 = (200 - 100)/4 (short*** 数据宽度为4字节)
25 = (200 - 100)/4 (int*** 数据宽度为4字节)
结果为相减后再除以 去掉一个*后的数据宽度
总结
指针之间的减法,其结果为相减后再除以去掉一个*后的数据宽度
指针之间的比较
指针之间也支持相互比较,但也和上面指针类型相减一样,要求指针类型一致
例子
void function(){
char**** a;
char**** b;
a=(char****) 200;
b=(char****) 100;
if (a>b)
{
printf("a>b\n");
}else{
printf("a<=b\n");
}
}
运行结果

结论
相同类型的指针之间是支持大小比较的
总结
指针的数据宽度为4字节,或者说:无论是什么类型,在其后面加上__(无论加几个\__都一样)后其数据宽度都变为4字节
指针数值支持进行加减,加减的结果=去掉一个*后的数据宽度 × 增减的数值
指针之间支持减法但不支持加法,其结果为相减后再除以去掉一个*后的数据宽度
指针之间支持比较大小,但要求进行比较的两个指针为相同类型
获取变量的数据类型
在C语言中可以使用下面的方法获取一个变量的数据类型:
代码
#include "stdafx.h"
#include <typeinfo>
void function(){
char**** a;
printf("%s\n",typeid(a).name());
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果

使用方式
通过上面的例子可以知道使用方式首先要包含一个相关的头文件typeinfo
然后使用该头文件的方法typeid(变量).name()即可获得变量对应的数据类型
取变量地址
在C语言中可以在变量前加上&符号来获取一个变量的地址
首先看看取回来地址的类型
取地址的返回类型
#include "stdafx.h"
#include <typeinfo>
void function(){
char a;
short b;
int c;
char* d;
printf("%s\n",typeid(&a).name());
printf("%s\n",typeid(&b).name());
printf("%s\n",typeid(&c).name());
printf("%s\n",typeid(&d).name());
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果

分析结果
不难发现,所有取地址返回的类型都为原本变量的类型后加个*,也契合了本笔记的主题——指针
取地址的内容
通过前面的分析得出了取出的地址类型是一个指针类型,现在要观察其存储的内容
代码
#include "stdafx.h"
#include <typeinfo>
//为了方便观察地址 先声明为全局变量
int a;
void function(){
a=610;
int* b=&a;
printf("%x\n",b);
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果

反汇编代码
11: a=610;
00401038 mov dword ptr [a (00427c48)],262h
12: int* b=&a;
00401042 mov dword ptr [ebp-4],offset a (00427c48)
13: printf("%x\n",b);
通过a的赋值语句可以看到a存储在00427c48这个内存地址中
11: a=610;
00401038 mov dword ptr [a (00427c48)],262h
再看下面的指针赋值语句
12: int* b=&a;
00401042 mov dword ptr [ebp-4],offset a (00427c48)
这里的offset a是vc6.0为了方便使用者查看生成的,实际上的语句为:
mov dword ptr ss:[ebp-0x4],0x427C48
也就是直接将全局变量a的地址00427c48赋给b
代码二
前面声明的变量为全局变量,现在来看看局部变量的情况:
#include "stdafx.h"
#include <typeinfo>
void function(){
//这里a声明为局部变量
int a=610;
int* b=&a;
printf("%x\n",b);
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果二

此时的地址显然就是一个堆栈中的地址,对应了变量存储在堆栈中
反汇编代码二
11: int a=610;
00401038 mov dword ptr [ebp-4],262h
12: int* b=&a;
0040103F lea eax,[ebp-4]
00401042 mov dword ptr [ebp-8],eax
13: printf("%x\n",b);
可以看到此时是通过lea指令将变量a的地址ebp-4传给eax,然后再将eax赋值给b
取地址中存储数据
前面讲了如何获取一个变量的地址,那么在获取完地址后,再说说如何获取这地址中存储的数据
在C语言中,在一个指针类型的变量前面加上*符号,即可取出该地址里所存储的内容
取地址数据的返回类型
如法炮制,观察取地址数据的返回类型
代码
#include "stdafx.h"
#include <typeinfo>
void function() {
int***a=(int***) 610;
printf("%s\n",typeid(*a).name());
printf("%s\n",typeid(**a).name());
printf("%s\n",typeid(***a).name());
}
int main(int argc, char* argv[])
{
function();
return 0;
}
运行结果

分析结果
不难发现,所有取地址数据返回的类型都为原本变量的类型后减个*,可以说是和&取地址正好相反
不同的是对于多级指针,可以一次使用多个*来多次取地址中存储的数据
取地址数据的内容
前面了解了*符号的使用,现在来看个稍微复杂点的例子
代码
#include "stdafx.h"
#include <typeinfo>
int a;
void function() {
a=610;
int* b=&a;
int** c=&b;
int*** d=&c;
c=*d;
b=*c;
a=*b;
b=**d;
a=**c;
a=***d;
}
int main(int argc, char* argv[])
{
function();
return 0;
}
反汇编代码
11: a=610;
00401038 mov dword ptr [a (00427c50)],262h
12: int* b=&a;
00401042 mov dword ptr [ebp-4],offset a (00427c50)
13: int** c=&b;
00401049 lea eax,[ebp-4]
0040104C mov dword ptr [ebp-8],eax
14: int*** d=&c;
0040104F lea ecx,[ebp-8]
00401052 mov dword ptr [ebp-0Ch],ecx
15: c=*d;
00401055 mov edx,dword ptr [ebp-0Ch]
00401058 mov eax,dword ptr [edx]
0040105A mov dword ptr [ebp-8],eax
16: b=*c;
0040105D mov ecx,dword ptr [ebp-8]
00401060 mov edx,dword ptr [ecx]
00401062 mov dword ptr [ebp-4],edx
17: a=*b;
00401065 mov eax,dword ptr [ebp-4]
00401068 mov ecx,dword ptr [eax]
0040106A mov dword ptr [a (00427c50)],ecx
18:
19: b=**d;
00401070 mov edx,dword ptr [ebp-0Ch]
00401073 mov eax,dword ptr [edx]
00401075 mov ecx,dword ptr [eax]
00401077 mov dword ptr [ebp-4],ecx
20: a=**c;
0040107A mov edx,dword ptr [ebp-8]
0040107D mov eax,dword ptr [edx]
0040107F mov ecx,dword ptr [eax]
00401081 mov dword ptr [a (00427c50)],ecx
21:
22: a=***d;
00401087 mov edx,dword ptr [ebp-0Ch]
0040108A mov eax,dword ptr [edx]
0040108C mov ecx,dword ptr [eax]
0040108E mov edx,dword ptr [ecx]
00401090 mov dword ptr [a (00427c50)],edx
分析反汇编
首先将各变量信息整理出来,方便后面分析:
| 变量 | 变量地址 | 变量内容/地址的值(十六进制) |
|---|---|---|
| a | 00427c50 | 262(对应十进制为610) |
| b | ebp-4 = 0012FF28 | 00427C50 |
| c | ebp-8 = 0012FF24 | 0012FF28 |
| d | ebp-0Ch = 0012FF20 | 0012FF24 |
代码中涉及的变量较多,这里只拿最复杂的 a=***d来作分析,其它留作样例
22: a=***d;
00401087 mov edx,dword ptr [ebp-0Ch]
0040108A mov eax,dword ptr [edx]
0040108C mov ecx,dword ptr [eax]
0040108E mov edx,dword ptr [ecx]
00401090 mov dword ptr [a (00427c50)],edx
1.这里的ebp-0Ch对应的是d的地址,此时就是将d赋值给edx
00401087 mov edx,dword ptr [ebp-0Ch]

结合内存里的数据可以得到:d的地址ebp-0Ch0012FF20,d=[ebp-0Ch]=0012FF24
这里的代码相当于
00401087 mov edx,0012FF24h(d)
2.将前面edx地址里存储的数据赋值给eax,此时的[edx]存储的其实就是c
0040108A mov eax,dword ptr [edx]

结合内存里的数据可以得到:dedx0012FF24,[edx]0012FF28c
这里的代码相当于
0040108A mov eax,0012FF28(c)
3.将前面eax地址里存储的数据赋值给ecx,此时的[eax]存储的其实就是b
0040108C mov ecx,dword ptr [eax]

结合内存里的数据可以得到:ceax0012FF28,b=[eax]=00427C50
这里的代码相当于
0040108C mov ecx,00427C50(b)
4.将前面ecx地址里存储的数据赋值给edx,此时的[ecx]存储的其实就是a
0040108E mov edx,dword ptr [ecx]

结合内存里的数据可以得到:becx00427C50,a=[ecx]262h610
这里的代码相当于
0040108E mov edx,262h
5.最后将edx赋值给a
00401090 mov dword ptr [a (00427c50)],edx
小总结
可以看到,被赋值变量 = * 赋值变量 在汇编中的形式为:mov 被赋值变量,[赋值变量]
如果有多个*,则多执行几次来取值
总结
可以在变量前加上&符号来获取变量地址
取地址返回的类型都为原本变量的类型后加个*,也就是个指针类型
在一个指针类型的变量前面加上*符号,即可取出该地址里所存储的内容
取地址数据返回的类型都为原本变量的类型后减个*,可以说是和&取地址正好相反
对于多级指针,可以一次使用多个*来多次取地址中存储的数据
指针遍历数组
有了先前的基础,再来看看如何用指针遍历数组
代码
#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是带符号扩展赋值,将edxecx__2,也就是pi\__数据宽度地址里存储的值赋给eax
0040D814 movsx eax,word ptr [edx+ecx*2]
为什么要使用movsx指令?
内存对齐的结果,先前的笔记就提到过:char short 在计算时都会转变为dword宽度来进行计算
相关笔记可以参考:
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
如果begin
cmp edx,ebx
jb _begin
总结
可以通过指针来存储数组的各成员地址,然后用指针来遍历数组,翻转数组
