反汇编即把目标二进制机器码转为汇编代码的过程,该技术常用于软件破解、外挂技术、病毒分析、逆向工程、软件汉化等领域,学习和理解反汇编对软件调试、系统漏洞挖掘、内核原理及理解高级语言代码都有相当大的帮助,软件一切神秘的运行机制全在反汇编代码里面。
数组和指针都是针对地址操作,但它们有许多不同之处,数组是相同数据类型的集合,以线性方式连续存储在内存中,而指针只是一个保存地址值的4字节变量。
在使用中,数组名是一个地址常量值,保存数组首元素地址不可修改,只能以此为基地址访问内存数据;而指针却是一个变量,只要修改指针中所保存的地址数据,就可以随意访问,不受约束.本章将深入介绍数组的构成以及两种寻址方式。
定义单循环一维的数组: 数组默认是使用局部变量存储的,拥有局部变量的所有特性,且数组中的数据在内存中是线性存储的,其数据由低到高在内存中的堆栈中存储,如下是一个简单的数组定义:
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[5] = { 1, 2, 3, 4, 5 };
int x;
for (x = 0; x < 5; x++){
printf("打印数组元素: %d \n", array[x]);
}
return 0;
}
第一种Debug版反汇编代码如下,可以看到重点的部分是mov ecx,dword ptr ss:[ebp+eax*4-0x18]
其中eax寄存器存储的就是数组下标,而乘以4是因为整数占4字节的存储空间所以要乘以4,最后的减0x18则是将堆栈指向第一个数组元素.
004113DE | C745 E8 01000000 | mov dword ptr ss:[ebp-0x18],0x1 | 数组第1个元素
004113E5 | C745 EC 02000000 | mov dword ptr ss:[ebp-0x14],0x2 | 数组第2个元素
004113EC | C745 F0 03000000 | mov dword ptr ss:[ebp-0x10],0x3 | 数组第3个元素
004113F3 | C745 F4 04000000 | mov dword ptr ss:[ebp-0xC],0x4 | 数组第4个元素
004113FA | C745 F8 05000000 | mov dword ptr ss:[ebp-0x8],0x5 | 数组第5个元素
00411401 | C745 DC 00000000 | mov dword ptr ss:[ebp-0x24],0x0 | for循环初始化条件
00411408 | EB 09 | jmp 0x411413 |
0041140A | 8B45 DC | mov eax,dword ptr ss:[ebp-0x24] | x++
0041140D | 83C0 01 | add eax,0x1 | for循环每次加1
00411410 | 8945 DC | mov dword ptr ss:[ebp-0x24],eax |
00411413 | 837D DC 05 | cmp dword ptr ss:[ebp-0x24],0x5 |
00411417 | 7D 21 | jge 0x41143A | 判断x是否大于等于5
00411419 | 8BF4 | mov esi,esp | main.c:9
0041141B | 8B45 DC | mov eax,dword ptr ss:[ebp-0x24] | 取出第一个数组元素的基址
0041141E | 8B4C85 E8 | mov ecx,dword ptr ss:[ebp+eax*4-0x18] | 一维数组寻址公式
00411422 | 51 | push ecx |
00411423 | 68 58584100 | push consoleapplication1.415858 | 415858:"打印数组元素: %d \n"
00411428 | FF15 14914100 | call dword ptr ds:[<&printf>] | 打印出来
0041142E | 83C4 08 | add esp,0x8 |
00411431 | 3BF4 | cmp esi,esp |
00411433 | E8 FEFCFFFF | call 0x411136 |
00411438 | EB D0 | jmp 0x41140A | main.c:10
0041143A | 33C0 | xor eax,eax | main.c:11
第二种Release版反汇编代码如下,相比于Debug版本的代码,编译器对代码进行了一定程度的优化,g观察反汇编代码可以看出数组元素1-4是直接通过mxx0寄存器直接存储的,也就是编译器将其写死在了代码里,其他地方变化不大,需要注意在寻址过程中,数组不同于局部变量,不会被赋予常量值而使用常量传播.
00401006 | 66:0F6F05 10214000 | movdqa xmm0,xmmword ptr ds:[<__xmm@000000040000000300 | 将1-4元素压入xmm0寄存器
0040100E | 56 | push esi |
0040100F | 57 | push edi |
00401010 | 8B3D 90204000 | mov edi,dword ptr ds:[<&printf>] | edi 存储printf地址
00401016 | 33F6 | xor esi,esi | 清除esi做循环条件
00401018 | F3:0F7F45 EC | movdqu xmmword ptr ss:[ebp-0x14],xmm0 | 直接将1-4写入内存
0040101D | C745 FC 05000000 | mov dword ptr ss:[ebp-0x4],0x5 | 最后写一个5
00401024 | FF74B5 EC | push dword ptr ss:[ebp+esi*4-0x14] | 寻址方式未变动
00401028 | 68 00214000 | push disable.402100 | 402100:"打印数组元素: %d \n"
0040102D | FFD7 | call edi | 调用Printf
0040102F | 46 | inc esi | 每次递增
00401030 | 83C4 08 | add esp,0x8 |
00401033 | 83FE 05 | cmp esi,0x5 | 判断是否小于5
00401036 | 7C EC | jl 0x401024 |
这里我写了一段双循环代码,当程序运行后外部每循环一次内层则循环3次,你可以尝试逆向它并总结经验.
#include <stdio.h>
int main(int argc, char *argv[])
{
int array1[5] = { 1, 2, 3, 4, 5 };
int array2[3] = { 99,88,77 };
int x,y;
int external_len = sizeof(array1) / sizeof(array1[0]);
for (x = 0; x < external_len; x++)
{
printf("外层循环计数: %d \n", array1[x]);
int inside_len = sizeof(array2) / sizeof(array2[0]);
for (y = 0; y < inside_len; y++)
{
printf("内层循环计数: %d \n", array2[y]);
}
printf("\n");
}
getchar();
return 0;
}
汇编代码
004113CC | 8DBD E0FEFFFF | lea edi,dword ptr ss:[ebp-0x120] |
004113D2 | B9 48000000 | mov ecx,0x48 | 48:'H'
004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |
004113DC | F3:AB | rep stosd |
004113DE | C745 E8 01000000 | mov dword ptr ss:[ebp-0x18],0x1 | 数组第1个元素
004113E5 | C745 EC 02000000 | mov dword ptr ss:[ebp-0x14],0x2 | 数组第2个元素
004113EC | C745 F0 03000000 | mov dword ptr ss:[ebp-0x10],0x3 | 数组第3个元素
004113F3 | C745 F4 04000000 | mov dword ptr ss:[ebp-0xC],0x4 | 数组第4个元素
004113FA | C745 F8 05000000 | mov dword ptr ss:[ebp-0x8],0x5 | 数组第5个元素
00411401 | C745 D4 63000000 | mov dword ptr ss:[ebp-0x2C],0x63 | main.c:6, 63:'c'
00411408 | C745 D8 58000000 | mov dword ptr ss:[ebp-0x28],0x58 | 58:'X'
0041140F | C745 DC 4D000000 | mov dword ptr ss:[ebp-0x24],0x4D | 4D:'M'
00411416 | C745 B0 05000000 | mov dword ptr ss:[ebp-0x50],0x5 | main.c:9
0041141D | C745 C8 00000000 | mov dword ptr ss:[ebp-0x38],0x0 | main.c:10
00411424 | EB 09 | jmp 0x41142F |
00411426 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] |
00411429 | 83C0 01 | add eax,0x1 | 外层循环递增条件
0041142C | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax |
0041142F | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 外层循环变量
00411432 | 3B45 B0 | cmp eax,dword ptr ss:[ebp-0x50] | 比较外层循环
00411435 | 7D 7D | jge 0x4114B4 |
00411437 | 8BF4 | mov esi,esp | main.c:12
00411439 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 外层循环,循环次数 0,1,2,3,4
0041143C | 8B4C85 E8 | mov ecx,dword ptr ss:[ebp+eax*4-0x18] | 通过公式定位到元素
00411440 | 51 | push ecx |
00411441 | 68 58584100 | push consoleapplication1.415858 | 415858:"外层循环计数: %d \n"
00411446 | FF15 10914100 | call dword ptr ds:[<&printf>] | 打印外层循环计数
0041144C | 83C4 08 | add esp,0x8 |
0041144F | 3BF4 | cmp esi,esp |
00411451 | E8 E0FCFFFF | call 0x411136 |
00411456 | C745 A4 03000000 | mov dword ptr ss:[ebp-0x5C],0x3 | 指定内层循环计数
0041145D | C745 BC 00000000 | mov dword ptr ss:[ebp-0x44],0x0 | y=0
00411464 | EB 09 | jmp 0x41146F |
00411466 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411469 | 83C0 01 | add eax,0x1 | 内层循环y每次递增
0041146C | 8945 BC | mov dword ptr ss:[ebp-0x44],eax |
0041146F | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411472 | 3B45 A4 | cmp eax,dword ptr ss:[ebp-0x5C] | 比较内层循环是否大于3
00411475 | 7D 21 | jge 0x411498 |
00411477 | 8BF4 | mov esi,esp | main.c:16
00411479 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] | 取出y的值
0041147C | 8B4C85 D4 | mov ecx,dword ptr ss:[ebp+eax*4-0x2C] | 通过公式定位到元素位置
00411480 | 51 | push ecx |
00411481 | 68 70584100 | push consoleapplication1.415870 | 415870:"内层循环计数: %d \n"
00411486 | FF15 10914100 | call dword ptr ds:[<&printf>] | 打印
0041148C | 83C4 08 | add esp,0x8 |
0041148F | 3BF4 | cmp esi,esp |
00411491 | E8 A0FCFFFF | call 0x411136 |
00411496 | EB CE | jmp 0x411466 | main.c:17
00411498 | 8BF4 | mov esi,esp | main.c:18
0041149A | 68 88584100 | push consoleapplication1.415888 | 415888:L"\n"
0041149F | FF15 10914100 | call dword ptr ds:[<&printf>] |
004114A5 | 83C4 04 | add esp,0x4 |
004114A8 | 3BF4 | cmp esi,esp |
004114AA | E8 87FCFFFF | call 0x411136 |
004114AF | E9 72FFFFFF | jmp 0x411426 | main.c:19
定义并使用二维的数组: 二维数组是一维数组的高阶抽象,其在内存中的排列也是线性存储的,只是在寻址方式上有所区别而已.
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
int *Pointer1 = &array[0][0];
printf("array[0][0]基址: %x \n", Pointer1);
printf("array[0][0]数据:%d\n", *(Pointer1));
printf("arrya[0][1]数据:%d\n", *(Pointer1 + 1));
int *Pointer2 = &array[1][2];
printf("array[1][2]基址: %x \n", Pointer2);
printf("数组元素个数:%d\n", sizeof(array) / sizeof(int));
return 0;
}
一张图理解寻址过程。
首先来研究一下第一个元素数组元素的寻址,也就是寻找到Pointer1 = > array[0][0]
这个内存的空间,此处省略不必要的数据节约空间.
004113DE | C745 E4 01000000 | mov dword ptr ss:[ebp-0x1C],0x1 | 数组第1个元素
004113E5 | C745 E8 02000000 | mov dword ptr ss:[ebp-0x18],0x2 | 数组第2个元素
004113EC | C745 EC 03000000 | mov dword ptr ss:[ebp-0x14],0x3 | 数组第3个元素
004113F3 | C745 F0 04000000 | mov dword ptr ss:[ebp-0x10],0x4 | 数组第4个元素
004113FA | C745 F4 05000000 | mov dword ptr ss:[ebp-0xC],0x5 | 数组第5个元素
00411401 | C745 F8 06000000 | mov dword ptr ss:[ebp-0x8],0x6 | 数组第6个元素
00411408 | B8 0C000000 | mov eax,0xC | 每一个一维数组的大小3*4=0C
0041140D | 6BC8 00 | imul ecx,eax,0x0 | 相乘ecx作为数组维度寻址
00411410 | 8D540D E4 | lea edx,dword ptr ss:[ebp+ecx-0x1C] | 取第一个数组元素首地址(基地址)
00411414 | B8 04000000 | mov eax,0x4 | 每个元素占用4字节空间
00411419 | 6BC8 00 | imul ecx,eax,0x0 | 计算第一个数组元素偏移
0041141C | 03D1 | add edx,ecx | 得出array[0][0]的地址
0041141E | 8955 D8 | mov dword ptr ss:[ebp-0x28],edx | edx存储的就是 array[0][0] 的地址
00411421 | 8BF4 | mov esi,esp | main.c:8
00411423 | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] |
00411426 | 50 | push eax | 压入堆栈,打印出来
00411427 | 68 58584100 | push consoleapplication1.415858 | 415858:"array[0][0]基址: %x \n"
0041142C | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411432 | 83C4 08 | add esp,0x8 |
00411435 | 3BF4 | cmp esi,esp |
00411437 | E8 FAFCFFFF | call 0x411136 |
0041143C | 8BF4 | mov esi,esp | main.c:9
0041143E | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] | 取出数组基地址
00411441 | 8B08 | mov ecx,dword ptr ds:[eax] | 打印出 array[0][0]
00411443 | 51 | push ecx |
00411444 | 68 74584100 | push consoleapplication1.415874 | 415874:"array[0][0]数据:%d\n"
00411449 | FF15 14914100 | call dword ptr ds:[<&printf>] |
0041144F | 83C4 08 | add esp,0x8 |
00411452 | 3BF4 | cmp esi,esp |
00411454 | E8 DDFCFFFF | call 0x411136 |
00411459 | 8BF4 | mov esi,esp | main.c:10
0041145B | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] |
0041145E | 8B48 04 | mov ecx,dword ptr ds:[eax+0x4] | 打印出 array[0][1]
00411461 | 51 | push ecx |
00411462 | 68 90584100 | push consoleapplication1.415890 | 415890:"arrya[0][1]数据:%d\n"
00411467 | FF15 14914100 | call dword ptr ds:[<&printf>] |
0041146D | 83C4 08 | add esp,0x8 |
00411470 | 3BF4 | cmp esi,esp |
00411472 | E8 BFFCFFFF | call 0x411136 |
00411477 | B8 0C000000 | mov eax,0xC | 每一个一维数组的大小3*4=0C
0041147C | C1E0 00 | shl eax,0x0 |
0041147F | 8D4C05 E4 | lea ecx,dword ptr ss:[ebp+eax-0x1C] | 取第一个数组元素首地址(基地址)
00411483 | BA 04000000 | mov edx,0x4 | 每个元素占用4字节空间
00411488 | D1E2 | shl edx,0x1 | 移位前 edx=4 移位后 edx=8
0041148A | 03CA | add ecx,edx | 得出array[1][2]的地址
0041148C | 894D CC | mov dword ptr ss:[ebp-0x34],ecx | 保存这个内存地址
0041148F | 8BF4 | mov esi,esp | main.c:13
00411491 | 8B45 CC | mov eax,dword ptr ss:[ebp-0x34] | 取出内存地址中的值
00411494 | 50 | push eax | 压入堆栈,准备输出
00411495 | 68 AC584100 | push consoleapplication1.4158AC | 4158AC:"array[1][2]基址: %x \n"
0041149A | FF15 14914100 | call dword ptr ds:[<&printf>] |
004114A0 | 83C4 08 | add esp,0x8 |
为了简单起见,我们对上方代码稍微修改下,并编译.
int main(int argc ,char *argv[])
{
int array[2][3] ={{1,2,3},{4,5,6}};
int x=0,y=1;
array[x][y] = 0;
return 0;
}
使用vc++6.0 的编译结果,在Debug模式下,其公式: 数组首地址 + sizeof(type[一维数组元素]) * x + sizeof(int) * y
0040106E 8B45 E4 mov eax, dword ptr [ebp-1C] ; eax = x 坐标
00401071 6BC0 0C imul eax, eax, 0C ; eax = x * 0c 索引数组
00401074 8D4C05 E8 lea ecx, dword ptr [ebp+eax-18] ; ecx = y 坐标
00401078 8B55 E0 mov edx, dword ptr [ebp-20] ; edx = 1
0040107B C70491 00000000 mov dword ptr [ecx+edx*4], 0 ; 1+1*4=5 4字节中的5,指向第2个元素
上方汇编代码,解释: 1.第1条代码中的EAX是获取到的x的值,此处为C语言中的
x=0
. 2.第2条代码中的0C: 每个元素占4字节,而每个数组有3个元素,3x4=0C. 3.第3条代码中的ECX: 代表数组的y坐标. 4.第5条代码:ecx + edx * 4
相当于数组首地址 + sizeof(int) * y
.
为了简单起见,我们对上方代码稍微修改下,然后通过不同的方式进行寻址,观察代码发生的变化.
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
int x = 0, y = 1;
array[x][y] = 0;
int a = 1, b = 2;
array[a][b] = 1;
return 0;
}
在Debug模式下的反汇编代码如下,其中0C代表的是二维数组中的一维数组的总大小3*4=0C
,最终寻址公式则是通过将二维坐标转为一维坐标来实现的寻址.
004113F8 | C745 D8 00000000 | mov dword ptr ss:[ebp-0x28],0x0 | x = 0
004113FF | C745 CC 01000000 | mov dword ptr ss:[ebp-0x34],0x1 | y = 1
00411406 | 6B45 D8 0C | imul eax,dword ptr ss:[ebp-0x28],0xC | eax = 0 * 0c = 0 定位x坐标
0041140A | 8D4C05 E4 | lea ecx,dword ptr ss:[ebp+eax-0x1C] | 找到数组array[0]的首地址
0041140E | 8B55 CC | mov edx,dword ptr ss:[ebp-0x34] | 数组y坐标 1
00411411 | C70491 00000000 | mov dword ptr ds:[ecx+edx*4],0x0 | ecx(数组首地址) + y坐标 * 4
00411418 | C745 C0 01000000 | mov dword ptr ss:[ebp-0x40],0x1 | a = 1
0041141F | C745 B4 02000000 | mov dword ptr ss:[ebp-0x4C],0x2 | b = 2
00411426 | 6B45 C0 0C | imul eax,dword ptr ss:[ebp-0x40],0xC | eax = 1 * 0c = 0c
0041142A | 8D4C05 E4 | lea ecx,dword ptr ss:[ebp+eax-0x1C] | 找到数组array[1]的首地址
0041142E | 8B55 B4 | mov edx,dword ptr ss:[ebp-0x4C] | 数组b坐标 2
00411431 | C70491 01000000 | mov dword ptr ds:[ecx+edx*4],0x1 | ecx(数组首地址) + b坐标 * 4
定义并使用三维数组: 三维数组就是在二维数组的基础上增加了一维,所以寻址上与二维差不多.
#include <stdio.h>
int main(int argc, char *argv[])
{
int Array[2][3][4] = {
{ { 1, 2, 3, 4 },{ 2, 3, 4, 5 },{ 3, 4, 5, 6 } },
{ { 4, 5, 6, 7 },{ 5, 6, 7, 8 },{ 6, 7, 8, 9 } }
};
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 3; y++)
{
for (int z = 0; z < 4; z++)
{
printf("输出内部循环: %d\n", Array[x][y][z]);
}
printf("\n");
}
printf("--------------------------------\n");
}
system("pause");
return 0;
}
三维数组的存储同样是通过一维方式存储的,只不过在寻址上有所不同而已,我们的内存只能实现一维。
int main(int argc, char* argv[])
{
int Array[2][3][4] = {NULL};
int x = 0;
int y = 1;
int z = 2;
Array[x][y][z] = 3;
return 0;
}
针对三维数组 int Array[M][C][H]
其下标操作Array[x][y][z]=3
数组寻址公式为:
Array + sizeof(type[C][H]) * x + sizeof(type[H])*y + sizeof(type)*z
00401056 |. 8B45 9C mov eax, dword ptr [ebp-64] ; eax=x
00401059 |. 6BC0 30 imul eax, eax, 30 ; sizeof(type[C][H])*x
0040105C |. 8D4C05 A0 lea ecx, dword ptr [ebp+eax-60] ;
00401060 |. 8B55 98 mov edx, dword ptr [ebp-68] ; Array[C]
00401063 |. C1E2 04 shl edx, 4
00401066 |. 03CA add ecx, edx
00401068 |. 8B45 94 mov eax, dword ptr [ebp-6C] ; Array[Z]
0040106B |. C70481 030000>mov dword ptr [ecx+eax*4], 3
上方汇编代码,解释: 1.第1条指令中得出
eax=x
的值. 2.第2条指令eax * 30
,相当于求出sizeof(type[C][H]) * x
3.第3条指令求出数组首地址+eax-60
也就求出Array[M]
位置,并取地址放入ECX 4.第4条指令:[ebp-68]
存放Y的值,此处就是求出y的值 5.第5条指令:左移4位,相当于2^4次方也就是16这一步相当于求sizeof(type[H])
的值 6.Array[M] + sizeof(type[H])
的值求出Array[M][C]
的值
#include <stdio.h>
int main(int argc, char *argv[])
{
int Array[2][3][4] = {
{ { 1, 2, 3, 4 },{ 2, 3, 4, 5 },{ 3, 4, 5, 6 } },
{ { 4, 5, 6, 7 },{ 5, 6, 7, 8 },{ 6, 7, 8, 9 } }
};
int x = 0, y = 1, z = 2;
Array[x][y][z] = 1;
int a = 1, b = 2, c = 3;
Array[a][b][c] = 1;
return 0;
}
针对三维数组 Array[M][C][H]
其下标操作 Array[x][y][z]=1
寻址公式为: Array + sizeof(Array[C][H]) * x + sizeof(Array[H]) * y + sizeof(Array) * z
00411476 | C745 90 00000000 | mov dword ptr ss:[ebp-0x70],0x0 | x
0041147D | C745 84 01000000 | mov dword ptr ss:[ebp-0x7C],0x1 | y
00411484 | C785 78FFFFFF 02000000 | mov dword ptr ss:[ebp-0x88],0x2 | z
0041148E | 6B45 90 30 | imul eax,dword ptr ss:[ebp-0x70],0x30 | sizeof(type[C][H])*x => eax = 0
00411492 | 8D4C05 9C | lea ecx,dword ptr ss:[ebp+eax-0x64] | 取出数组基地址
00411496 | 8B55 84 | mov edx,dword ptr ss:[ebp-0x7C] | Array[C] => edx = 1
00411499 | C1E2 04 | shl edx,0x4 | edx 左移 4 位= 10
0041149C | 03CA | add ecx,edx | 定位 ecx = [0][1]
0041149E | 8B85 78FFFFFF | mov eax,dword ptr ss:[ebp-0x88] | Array[Z] => eax = 2
004114A4 | C70481 01000000 | mov dword ptr ds:[ecx+eax*4],0x1 | 最终赋值
004114AB | C785 6CFFFFFF 01000000 | mov dword ptr ss:[ebp-0x94],0x1 | a
004114B5 | C785 60FFFFFF 02000000 | mov dword ptr ss:[ebp-0xA0],0x2 | b
004114BF | C785 54FFFFFF 03000000 | mov dword ptr ss:[ebp-0xAC],0x3 | c
004114C9 | 6B85 6CFFFFFF 30 | imul eax,dword ptr ss:[ebp-0x94],0x30 | 定位到 Array[1]
004114D0 | 8D4C05 9C | lea ecx,dword ptr ss:[ebp+eax-0x64] | 数组基址加上eax偏移
004114D4 | 8B95 60FFFFFF | mov edx,dword ptr ss:[ebp-0xA0] | 定位b
004114DA | C1E2 04 | shl edx,0x4 | edx 左移 4位 = 20
004114DD | 03CA | add ecx,edx | 在Array[1][2]基础上加20字节
004114DF | 8B85 54FFFFFF | mov eax,dword ptr ss:[ebp-0xAC] | 定位c
004114E5 | C70481 01000000 | mov dword ptr ds:[ecx+eax*4],0x1 | 最终赋值
定义并使用字符串数组: 其实字符串本身就是数组,只不过该数组最后一个数据统一使用0作为字符串结束符,编译器为字符类型的数组赋值其实就是复制字符串的过程,每次赋值4字节数据.
#include <stdio.h>
int main(int argc, char *argv[])
{
char str[] = "hello lyshark";
printf("字符串数组: %s\n", str);
char *str1 = "hello lyshark";
printf("指针字符串: %s\n", str1);
return 0;
}
其反汇编代码如下,我们的字符串其实是被编译器在编译的时候写死在程序中的,针对小字符串程序会现将其存入寄存器,然后在调用输出,而对于大字符串或者是指针字符串而言则是直接输出常量中的内容并不会经过取值与赋值等操作,所以相应的使用字符串指针的效率要远大于使用字符串数组.
004113DE | A1 58584100 | mov eax,dword ptr ds:[0x415858] | main.c:5, 00415858:"hello lyshark"
004113E3 | 8945 EC | mov dword ptr ss:[ebp-0x14],eax |
004113E6 | 8B0D 5C584100 | mov ecx,dword ptr ds:[0x41585C] | 0041585C:"o lyshark"
004113EC | 894D F0 | mov dword ptr ss:[ebp-0x10],ecx |
004113EF | 8B15 60584100 | mov edx,dword ptr ds:[0x415860] | 00415860:"shark"
004113F5 | 8955 F4 | mov dword ptr ss:[ebp-0xC],edx |
004113F8 | 66:A1 64584100 | mov ax,word ptr ds:[0x415864] | 00415864:L"k"
004113FE | 66:8945 F8 | mov word ptr ss:[ebp-0x8],ax |
00411402 | 8BF4 | mov esi,esp | main.c:6
00411404 | 8D45 EC | lea eax,dword ptr ss:[ebp-0x14] |
00411407 | 50 | push eax |
00411408 | 68 68584100 | push consoleapplication1.415868 | 415868:"字符串数组: %s\n"
0041140D | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411413 | 83C4 08 | add esp,0x8 |
00411416 | 3BF4 | cmp esi,esp |
00411418 | E8 19FDFFFF | call 0x411136 |
0041141D | C745 E0 58584100 | mov dword ptr ss:[ebp-0x20],0x415858 | main.c:8, 415858:"hello lyshark"
00411424 | 8BF4 | mov esi,esp | main.c:9
00411426 | 8B45 E0 | mov eax,dword ptr ss:[ebp-0x20] |
00411429 | 50 | push eax |
0041142A | 68 7C584100 | push consoleapplication1.41587C | 41587C:"指针字符串: %s\n"
0041142F | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411435 | 83C4 08 | add esp,0x8 |
存放指针类型数据的数组: 数组中各数据元素都是由相同类型的指针组成,我们就称之为指针数组.
指针数组主要用于管理同种类型的指针,一般用于处理若干个字符串的操作,使用指针数组处理多字符串更加的方便,简洁,高效,需要注意的是,虽然同属于数组但是指针数组,但与常规的数组还有所差别,指针数组中的数据为地址类型,寻址时需要再次进行间接访问才能够获取到真正的数据,这也是他们之间最大的不同.
#include <stdio.h>
int main(int argc, char *argv[])
{
char *pBuffer[3] = { "hello ", "lyshark ", "!\r\n" };
for (int x = 0; x < 3; x++)
{
printf(pBuffer[x]);
}
return 0;
}
上方的代码经过编译后,我们定位到字符串初始化位置,会发现初始化仅仅只是将常量中的字符串首地址复制到了堆栈中.
004113CC | 8DBD 20FFFFFF | lea edi,dword ptr ss:[ebp-0xE0] |
004113D2 | B9 38000000 | mov ecx,0x38 | 38:'8'
004113D7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |
004113DC | F3:AB | rep stosd |
004113DE | C745 F0 58584100 | mov dword ptr ss:[ebp-0x10],consoleapplication1.415858 | main.c:6, 415858:"hello "
004113E5 | C745 F4 60584100 | mov dword ptr ss:[ebp-0xC],consoleapplication1.415860 | 415860:"lyshark "
004113EC | C745 F8 6C584100 | mov dword ptr ss:[ebp-0x8],consoleapplication1.41586C | 41586C:"!\r\n"
接着来看以下代码,该汇编片段主要用于索引数组元素,由于指针本质上也是数组,故寻址方式与我们的数组基本相同.
004113F3 | C745 E4 00000000 | mov dword ptr ss:[ebp-0x1C],0x0 | 初始化打印变量为0
004113FA | EB 09 | jmp 0x411405 |
004113FC | 8B45 E4 | mov eax,dword ptr ss:[ebp-0x1C] |
004113FF | 83C0 01 | add eax,0x1 | 每次让变量加一
00411402 | 8945 E4 | mov dword ptr ss:[ebp-0x1C],eax |
00411405 | 837D E4 03 | cmp dword ptr ss:[ebp-0x1C],0x3 | 总共需要循环3次
00411409 | 7D 1C | jge 0x411427 |
0041140B | 8BF4 | mov esi,esp | main.c:10
0041140D | 8B45 E4 | mov eax,dword ptr ss:[ebp-0x1C] |
00411410 | 8B4C85 F0 | mov ecx,dword ptr ss:[ebp+eax*4-0x10] | 一维数组寻址方式
00411414 | 51 | push ecx |
00411415 | FF15 14914100 | call dword ptr ds:[<&printf>] |
0041141B | 83C4 04 | add esp,0x4 |
0041141E | 3BF4 | cmp esi,esp |
00411420 | E8 11FDFFFF | call 0x411136 |
00411425 | EB D5 | jmp 0x4113FC | main.c:11
上方代码定义了1维字符串数组,该数组由3个指针变量组成,故长度为12字节,数组所指向的字符串长度与数组本身没有关系,而2维数组则与之不同,我们接着将上方代码稍加修改:
#include <stdio.h>
int main(int argc, char *argv[])
{
char cArray[2][10] = {
{ "hello" },
{ "lyshark" }
};
for (int x = 0; x<2; x++)
{
printf(cArray[x]);
}
return 0;
}
观察反汇编代码,字符指针数组寻址后,得到的是数组成员内容,而2维字符数组寻址后,得到的却是数组中某个1维数组的首地址.
004113DE | A1 58584100 | mov eax,dword ptr ds:[0x415858] | 存储 hell
004113E3 | 8945 E8 | mov dword ptr ss:[ebp-0x18],eax |
004113E6 | 66:8B0D 5C584100 | mov cx,word ptr ds:[0x41585C] | 存储 o
004113ED | 66:894D EC | mov word ptr ss:[ebp-0x14],cx |
004113F1 | 33C0 | xor eax,eax |
004113F3 | 8945 EE | mov dword ptr ss:[ebp-0x12],eax |
004113F6 | A1 60584100 | mov eax,dword ptr ds:[0x415860] | 存储lysh
004113FB | 8945 F2 | mov dword ptr ss:[ebp-0xE],eax |
004113FE | 8B0D 64584100 | mov ecx,dword ptr ds:[0x415864] | 存储ark
00411404 | 894D F6 | mov dword ptr ss:[ebp-0xA],ecx |
00411407 | 33C0 | xor eax,eax |
00411409 | 66:8945 FA | mov word ptr ss:[ebp-0x6],ax |
0041140D | C745 DC 00000000 | mov dword ptr ss:[ebp-0x24],0x0 | 初始化循环条件
00411414 | EB 09 | jmp 0x41141F |
00411416 | 8B45 DC | mov eax,dword ptr ss:[ebp-0x24] |
00411419 | 83C0 01 | add eax,0x1 | 每次循环加一
0041141C | 8945 DC | mov dword ptr ss:[ebp-0x24],eax |
0041141F | 837D DC 02 | cmp dword ptr ss:[ebp-0x24],0x2 | 小于2则循环
00411423 | 7D 1D | jge 0x411442 |
00411425 | 6B45 DC 0A | imul eax,dword ptr ss:[ebp-0x24],0xA | main.c:12
00411429 | 8D4C05 E8 | lea ecx,dword ptr ss:[ebp+eax-0x18] | 每次取出一段字符串
0041142D | 8BF4 | mov esi,esp |
0041142F | 51 | push ecx | 压栈,并打印
00411430 | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411436 | 83C4 04 | add esp,0x4 |
指向数组的指针变量: 当指针变量保存的数据为数组的首地址,且将此地址解释为数组时,此指针变量被称为数组指针.
如下代码我们定义了一个指向二维数组Array[x][y]
的指针char (*pArray)[10]=Array
,并通过数组指针循环遍历数组中的原有数据.
#include <stdio.h>
int main(int argc, char *argv[])
{
char Array[3][10] = { "hello", "lyshark", "! \r\n" };
char(*pArray)[10] = Array;
for (int x = 0; x<3; x++)
{
printf(*pArray);
pArray++;
}
return 0;
}
先看下面的汇编代码,程序通过lea eax,dword ptr ss:[ebp-0x24]
获取到整个字符串数组的基址,然后通过使用for循环遍历这个字符串指针,循环末尾通过使用add eax,0xA
这条汇编指令,每次让指针递增10,之所以是10是因为char类型为1个字节,而1维数组的大小为10,所以每次递增就要为10,如果为整数类型就需要递增4 * 10
也就是每次递增40了.
00411426 | 8D45 DC | lea eax,dword ptr ss:[ebp-0x24] | 获取第一个字符串数组首地址
00411429 | 8945 D0 | mov dword ptr ss:[ebp-0x30],eax | [ebp-30]:"lyshark"
0041142C | C745 C4 00000000 | mov dword ptr ss:[ebp-0x3C],0x0 | 指定for循环计数
00411433 | EB 09 | jmp 0x41143E |
00411435 | 8B45 C4 | mov eax,dword ptr ss:[ebp-0x3C] |
00411438 | 83C0 01 | add eax,0x1 | for循环每次递增1
0041143B | 8945 C4 | mov dword ptr ss:[ebp-0x3C],eax |
0041143E | 837D C4 03 | cmp dword ptr ss:[ebp-0x3C],0x3 | 最多输出3次
00411442 | 7D 21 | jge 0x411465 |
00411444 | 8BF4 | mov esi,esp | main.c:10
00411446 | 8B45 D0 | mov eax,dword ptr ss:[ebp-0x30] | [ebp-30]:"lyshark"
00411449 | 50 | push eax | 输出数组中的数据
0041144A | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411450 | 83C4 04 | add esp,0x4 |
00411453 | 3BF4 | cmp esi,esp |
00411455 | E8 DCFCFFFF | call 0x411136 |
0041145A | 8B45 D0 | mov eax,dword ptr ss:[ebp-0x30] | main.c:11, [ebp-30]:"lyshark"
0041145D | 83C0 0A | add eax,0xA | 指针每次递增10
00411460 | 8945 D0 | mov dword ptr ss:[ebp-0x30],eax | [ebp-30]:"lyshark"
00411463 | EB D0 | jmp 0x411435 | main.c:12
00411465 | 33C0 | xor eax,eax | main.c:13, eax:"lyshark"
指向函数的指针(函数指针): 程序通过CALL指令跳转到函数首地址执行代码,既然是地址那就可以使用指针变量来存储函数的首地址,该指针变量被乘坐函数指针.
在编译时,编译器为函数代码分配一段存储空间,这段存储空间的起始地址成为这个函数的指针,我们可以调用这个指针实现间接调用指针所指向的函数.
#include <stdio.h>
void __stdcall Show()
{
printf("hello lyshark\n");
}
int main(int argc, char* argv[])
{
void(__stdcall *pShow)(void) = Show;
pShow();
Show();
return 0;
}
函数指针的类型由返回值,参数信息,调用约定组成,它决定了函数指针在函数调用过程中参数的传递,返回,以及如何堆栈平衡等,上方的代码中__stdcall
就是VC编译器中的默认平栈方式,这里也可以省略.
0041142C | 8DBD 34FFFFFF | lea edi,dword ptr ss:[ebp-0xCC] | edi:__enc$textbss$end+10E
00411432 | B9 33000000 | mov ecx,0x33 |
00411437 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |
0041143C | F3:AB | rep stosd |
0041143E | C745 F8 96104100 | mov dword ptr ss:[ebp-0x8],0x411096 | 获得Show函数入口
00411445 | 8BF4 | mov esi,esp | main.c:11, esi:__enc$textbss$end+10E
00411447 | FF55 F8 | call dword ptr ss:[ebp-0x8] | 通过堆栈间接调用
0041144A | 3BF4 | cmp esi,esp | esi:__enc$textbss$end+10E
如上代码清单,演示了函数指针的赋值和调用过程,与函数调用最大的区别在于函数是直接调用,而函数指针的调用需要取出指针变量中保存的地址,然后进行call dword ptr ss:[ebp-0x8]
间接的调用,接着我们给函数增加两个参数传递,对上方C代码稍作修改,并编译.
#include <stdio.h>
int __stdcall Show(int nShow,int nCount){
for (int x = 0; x < nCount; x++)
{
printf("hello %d --> %d \n", nShow,nCount);
}
return nShow;
}
int main(int argc, char* argv[])
{
int(__stdcall *pShow)(int,int) = Show;
int Ret = pShow(1,3);
printf("返回值 = %d \n", Ret);
return 0;
}
如下反汇编代码,代码中的函数指针调用只是多了参数的传递,以及返回值的接收,其他地方没有太大变化,都是间接函数调用.
0041146E | C745 F8 A0104100 | mov dword ptr ss:[ebp-0x8],0x4110A0 | 获取Show函数基地址
00411475 | 8BF4 | mov esi,esp | main.c:15
00411477 | 6A 03 | push 0x3 | 压入第二个参数
00411479 | 6A 01 | push 0x1 | 压入第一个参数
0041147B | FF55 F8 | call dword ptr ss:[ebp-0x8] | 间接调用Show函数
0041147E | 3BF4 | cmp esi,esp |
00411480 | E8 B6FCFFFF | call 0x41113B |
00411485 | 8945 EC | mov dword ptr ss:[ebp-0x14],eax | 保存出函数返回值
00411488 | 8BF4 | mov esi,esp | main.c:16
0041148A | 8B45 EC | mov eax,dword ptr ss:[ebp-0x14] |
0041148D | 50 | push eax | 压入堆栈,打印返回值
0041148E | 68 70584100 | push consoleapplication1.415870 | 415870:"返回值 = %d \n"
00411493 | FF15 14914100 | call dword ptr ds:[<&printf>] |
00411499 | 83C4 08 | add esp,0x8 |
返回指针值的函数(指针函数): 通常情况下函数的返回值可以返回整数,返回浮点数,返回字符串等,还可以返回一个指针,或者说返回一个地址,而返回的这个地址通常情况下可使用递增指针的方式对其指针内部的元素进行遍历,这个我把它称为指针函数.
#include <stdio.h>
int *Search(int Count)
{
int Array[] = { 1,2,3,4,5,6,7,8,9,10 };
int *RetArray[] = { 0,0,0,0,0,0,0,0,0,0 };
for (int x = 0; x < 10; x++)
{
//printf("打印原始数据: %d \t\t", Array[x]);
Array[x] = Array[x] + Count; // 让元素每次加Count
RetArray[x] = Array[x]; // 将相加的数据放入RetArray
//printf("打印新的数据: %d \t\n", RetArray[x]);
}
return RetArray;
}
int main(int argc, char* argv[])
{
int *p = NULL;
p = Search(2);
printf("指针地址: %p\n",p);
for (int x = 0; x < 10; x++)
{
printf("%d\t",*(p));
}
system("pause");
return 0;
}
第二个案例,我们来研究一下,其反汇编形式。
#include <stdio.h>
int *SetPtr(int *ptr)
{
return (ptr);
}
int main(int argc, char* argv[])
{
int Array[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int *p = NULL;
p = SetPtr(Array);
for (int x = 0; x < 10;x++)
{
printf("输出指针地址: %p --> 对应数值: %d \n", p,*p);
p++;
}
system("pause");
return 0;
}
反汇编代码如下
0041147E | 50 | push eax |
0041147F | E8 2BFCFFFF | call 0x4110AF |
00411484 | 83C4 04 | add esp,0x4 |
00411487 | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax | 将数组传递
0041148A | C745 BC 00000000 | mov dword ptr ss:[ebp-0x44],0x0 | 循环计数 0
00411491 | EB 09 | jmp 0x41149C |
00411493 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411496 | 83C0 01 | add eax,0x1 | 每次循环递增
00411499 | 8945 BC | mov dword ptr ss:[ebp-0x44],eax |
0041149C | 837D BC 0A | cmp dword ptr ss:[ebp-0x44],0xA | A:'\n'
004114A0 | 7D 2C | jge 0x4114CE |
004114A2 | 8BF4 | mov esi,esp | main.c:16
004114A4 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 获取数组首地址
004114A7 | 8B08 | mov ecx,dword ptr ds:[eax] | 取出数组下标
004114A9 | 51 | push ecx |
004114AA | 8B55 C8 | mov edx,dword ptr ss:[ebp-0x38] |
004114AD | 52 | push edx |
004114AE | 68 58584100 | push consoleapplication1.415858 | 415858:"输出指针地址: %p --> 对应数值: %d \n"
004114B3 | FF15 18914100 | call dword ptr ds:[<&printf>] |
004114B9 | 83C4 0C | add esp,0xC |
004114BC | 3BF4 | cmp esi,esp |
004114BE | E8 7DFCFFFF | call 0x411140 |
004114C3 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | main.c:17
004114C6 | 83C0 04 | add eax,0x4 | 每次循环使指针递增4字节
004114C9 | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax |
004114CC | EB C5 | jmp 0x411493 | main.c:18
其中的call 0x4110AF
就是我们的指针函数,里面传递通过eax进行,进去看看。
004113D0 | 55 | push ebp | main.c:4
004113D1 | 8BEC | mov ebp,esp |
004113D3 | 81EC C0000000 | sub esp,0xC0 |
004113D9 | 53 | push ebx |
004113DA | 56 | push esi |
004113DB | 57 | push edi |
004113DC | 8DBD 40FFFFFF | lea edi,dword ptr ss:[ebp-0xC0] |
004113E2 | B9 30000000 | mov ecx,0x30 | 30:'0'
004113E7 | B8 CCCCCCCC | mov eax,0xCCCCCCCC |
004113EC | F3:AB | rep stosd |
004113EE | 8B45 08 | mov eax,dword ptr ss:[ebp+0x8] | 获取到的数组指针,通过eax传递到main函数
004113F1 | 5F | pop edi | main.c:6
004113F2 | 5E | pop esi |
004113F3 | 5B | pop ebx |
004113F4 | 8BE5 | mov esp,ebp |
004113F6 | 5D | pop ebp |
004113F7 | C3 | ret |
观察反汇编代码,其中的循环不用看了,主要研究一下指针的传递,代码中的call 0x4110AF
就是我们定义的SetPtr()
这个指针函数,跟进去以后可以发现其通过eax寄存器向main函数传递的指针参数,main函数拿到该指针就可以完成对数组的遍历了.
0041147E | 50 | push eax |
0041147F | E8 2BFCFFFF | call 0x4110AF | 此处就是SetPtr函数
00411484 | 83C4 04 | add esp,0x4 |
00411487 | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax | 将数组传递
0041148A | C745 BC 00000000 | mov dword ptr ss:[ebp-0x44],0x0 | 循环计数 0
00411491 | EB 09 | jmp 0x41149C |
00411493 | 8B45 BC | mov eax,dword ptr ss:[ebp-0x44] |
00411496 | 83C0 01 | add eax,0x1 | 每次循环递增
00411499 | 8945 BC | mov dword ptr ss:[ebp-0x44],eax |
0041149C | 837D BC 0A | cmp dword ptr ss:[ebp-0x44],0xA | A:'\n'
004114A0 | 7D 2C | jge 0x4114CE |
004114A2 | 8BF4 | mov esi,esp | main.c:16
004114A4 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | 获取数组首地址
004114A7 | 8B08 | mov ecx,dword ptr ds:[eax] | 取出数组下标
004114A9 | 51 | push ecx |
004114AA | 8B55 C8 | mov edx,dword ptr ss:[ebp-0x38] |
004114AD | 52 | push edx |
004114AE | 68 58584100 | push consoleapplication1.415858 | "输出指针地址: %p --> 对应数值: %d \n"
004114B3 | FF15 18914100 | call dword ptr ds:[<&printf>] |
004114B9 | 83C4 0C | add esp,0xC |
004114BC | 3BF4 | cmp esi,esp |
004114BE | E8 7DFCFFFF | call 0x411140 |
004114C3 | 8B45 C8 | mov eax,dword ptr ss:[ebp-0x38] | main.c:17
004114C6 | 83C0 04 | add eax,0x4 | 每次循环使指针递增4字节
004114C9 | 8945 C8 | mov dword ptr ss:[ebp-0x38],eax |
004114CC | EB C5 | jmp 0x411493 | main.c:18
指向指针的指针(多级指针): 当一个数组其全部元素均为指针类型,该数组则被称作指针数组,顾名思义就是存放指针的数组,其中的每一个数组成员都是指向另一个数据结构的指针,通常我们会将这种数组叫做指向指针的指针数组,先来编译一个简单案例:
#include <stdio.h>
int main(int argc, char* argv[])
{
char *name[] = { "hello", "lyshark", "Welcome" };
char **ptr = NULL;
for (int x = 0; x < 3; x++)
{
ptr = name + x;
printf("地址:%x --> 元素:%s \n", ptr,*ptr);
}
system("pause");
return 0;
}
观察反汇编代码,此处可看出我们的字符串被编译器干成了常量,写死在了程序里,需要时直接读出到压入堆栈并通过数组公式寻址,这里不再分析了,指针寻址过程与数组是一样的.
004113DE | C745 F0 58584100 | mov dword ptr ss:[ebp-0x10],0x415858 | hello 常量字符串
004113E5 | C745 F4 60584100 | mov dword ptr ss:[ebp-0xC],0x415860 | lyshark 常量字符串
004113EC | C745 F8 6C584100 | mov dword ptr ss:[ebp-0x8],0x41586C | welcome 常量字符串
004113F3 | C745 E4 00000000 | mov dword ptr ss:[ebp-0x1C],0x0 | main.c:6, [ebp-1C]:"`XA"==&"lyshark"
004113FA | C745 D8 00000000 | mov dword ptr ss:[ebp-0x28],0x0 | main.c:8
00411401 | EB 09 | jmp 0x41140C |
00411403 | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] |
00411406 | 83C0 01 | add eax,0x1 | 每次递增
00411409 | 8945 D8 | mov dword ptr ss:[ebp-0x28],eax |
0041140C | 837D D8 03 | cmp dword ptr ss:[ebp-0x28],0x3 | 循环3次
00411410 | 7D 2D | jge 0x41143F |
00411412 | 8B45 D8 | mov eax,dword ptr ss:[ebp-0x28] | 取第几个
00411415 | 8D4C85 F0 | lea ecx,dword ptr ss:[ebp+eax*4-0x10] |
00411419 | 894D E4 | mov dword ptr ss:[ebp-0x1C],ecx | 将取出的内存地址放入内存
0041141C | 8BF4 | mov esi,esp | main.c:11
0041141E | 8B45 E4 | mov eax,dword ptr ss:[ebp-0x1C] | 拿出来搞
00411421 | 8B08 | mov ecx,dword ptr ds:[eax] | 将字符串首地址怼到ecx
00411423 | 51 | push ecx |
00411424 | 8B55 E4 | mov edx,dword ptr ss:[ebp-0x1C] | [ebp-1C]:"`XA"==&"lyshark"
00411427 | 52 | push edx |
00411428 | 68 78584100 | push consoleapplication1.415878 | 415878:"地址:%x --> 元素:%s \n"
0041142D | FF15 18914100 | call dword ptr ds:[<&printf>] | 输出
00411433 | 83C4 0C | add esp,0xC |
00411436 | 3BF4 | cmp esi,esp |
00411438 | E8 FEFCFFFF | call 0x41113B |
0041143D | EB C4 | jmp 0x411403 | main.c:12
接着我们增加难度,在数组中存放指针,首先定义一个数组里面存放原始数据,然后定义一个指针数组用于存储原始数据的内存地址,并通过指向指针数据的内存地址索引到元素值,这段代码我就不去反编译了,其实针对指针的操作都是差不多的,定位公式技巧也都是那个样子,自己研究研究吧.
#include <stdio.h>
int main(int argc, char* argv[])
{
int Array[5] = { 1, 3, 5, 7, 9 };
int ArrayPtr[5] = { &Array[0], &Array[1], &Array[2], &Array[3], &Array[4] };
int **p;
p = ArrayPtr;
for (int x = 0; x < 5; x++)
{
printf("地址: %x --> 数据: %d \n",*p,**p);
p++;
}
system("pause");
return 0;
}