C語言反匯編-多維數組與指針


反匯編(Disassembly) 即把目標二進制機器碼轉為匯編代碼的過程,該技術常用於軟件破解、外掛技術、病毒分析、逆向工程、軟件漢化等領域,學習和理解反匯編對軟件調試、系統漏洞挖掘、內核原理及理解高級語言代碼都有相當大的幫助,軟件一切神秘的運行機制全在反匯編代碼里面。

數組和指針都是針對地址操作,但它們有許多不同之處,數組是相同數據類型的集合,以線性方式連續存儲在內存中,而指針只是一個保存地址值的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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM