內容導讀
- 1.暫存器
- 2.函式堆疊幀
- 2.1函式堆疊幀的概述
- 2.2函式堆疊幀創建程序
- 2.2.1被呼叫的main函式
- 2.2.2函式堆疊幀創建與銷毀的程序
前面的話:
作者水平很有限,如果發現錯誤,一定要及時告知作者哦!感謝感謝!
博主的碼云gitee,平常博主寫的程式代碼都在里面,
1.暫存器
暫存器是中央處理器內的組成部分,暫存器是有限存貯容量的高速存貯部件,它們可用來暫存指令、資料和地址,在中央處理器的控制部件中,包含的暫存器有指令暫存器(IR)和程式計數器(PC),在中央處理器的算術及邏輯部件中,暫存器有累加器(ACC),
本文不過多深入了解暫存器,只要知道暫存器集成在CPU之中和以下幾個暫存器就可以了,

2.函式堆疊幀
2.1函式堆疊幀的概述
C語言中,每個堆疊幀對應著一個未運行完的函式,堆疊幀中保存了該函式的回傳地址和區域變數,
堆疊幀也叫程序活動記錄,是編譯器用來實作程序/函式呼叫的一種資料結構,
函式堆疊幀的創建和銷毀是基于堆疊所實作的,
所謂堆疊,是一種資料結構,具有先進后出的特點,在函式堆疊幀創建程序中,記憶體從高地址開始使用,越后面創建的函式堆疊幀或壓堆疊資料,所存盤的空間地址越低,

想要更深入了解堆疊這一資料結構,歡迎訪問博主另一篇文章:堆疊和佇列介紹和基本功能從理論到實踐
2.2函式堆疊幀創建程序
2.2.1被呼叫的main函式
main函式是會被其他函式呼叫的,在不同編譯器中呼叫main的函式也不同,
在VS2019中,main函式會被下面幾個編譯器內置的函式鏈式訪問,

首先,這個invoke_main函式會回傳main函式的回傳值,
static int __cdecl invoke_main()
{
return main(__argc, __argv, _get_initial_narrow_environment());
}
然后會有一個名叫main_result的int const型別變數接收,invoke_main函式的回傳值,也就是main函式的回傳值,最后這個main_result會被編譯器其他函式所使用,
int const main_result = invoke_main();


函式堆疊幀的結構如下
esp為堆疊頂指標
ebp為堆疊底指標
它們共同維護函式堆疊幀

2.2.2函式堆疊幀創建與銷毀的程序
對于函式堆疊幀的創建與銷毀,我們以一個簡單的程式為例,
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int add(int a, int b)
{
int d = a + b;
return d;
}
int main()
{
int a = 2;
int b = 6;
int c = 0;
c = add(a, b);
printf("%d\n",c);
return 0;
}
創建main函式堆疊幀前,esp,ebp會初始在如圖位置

00892580 push ebp //ebp壓堆疊
00892581 mov ebp,esp //將esp的值賦給ebp
00892583 sub esp,0E4h //將esp的值減0E4h,也就是為main函式堆疊幀分配空間

00892589 push ebx //ebx壓堆疊
0089258A push esi //esi壓堆疊
0089258B push edi //edi壓堆疊
0089258C lea edi,[ebp-24h]
0089258F mov ecx,9
00892594 mov eax,0CCCCCCCCh
00892599 rep stos dword ptr es:[edi] //將main初始函式堆疊幀全部初始化為0CCCCCCCCh

0089259B mov ecx,89C003h
008925A0 call 0089130C //進入main函式
int a = 2;
008925A5 mov dword ptr [ebp-8],2 //ebp - 8就是a的位置,將a賦值為2
int b = 6;
008925AC mov dword ptr [ebp-14h],6 //同理ebp - 14h為b的地址將b賦值為6
int c = 0;
008925B3 mov dword ptr [ebp-20h],0 //ebp - 20h為c的地址,c賦值為0

c = add(a, b);
008925BA mov eax,dword ptr [ebp-14h] //傳參,將b值傳給add函式 ,先將b值傳給eax
008925BD push eax //eax壓堆疊
008925BE mov ecx,dword ptr [ebp-8] //傳參,將a值傳給add函式,先將a值傳給ecx
008925C1 push ecx //ecx壓堆疊

008925C2 call 00891023 //進入add
//帶符號:008925C2 call _add (0891023h)
int add(int a, int b)
{
008917B0 push ebp //記錄上一個ebp的地址
008917B1 mov ebp,esp //將ebp賦值成esp地址
008917B3 sub esp,0CCh //add函式堆疊幀
008917B9 push ebx
008917BA push esi
008917BB push edi
008917BC lea edi,[ebp-0Ch]
008917BF mov ecx,3
008917C4 mov eax,0CCCCCCCCh
008917C9 rep stos dword ptr es:[edi] //與main函式堆疊幀初始化同理,將add函式初始化為CC CC CC CC
008917CB mov ecx,offset _18BA86EA_test@c (089C003h)
008917D0 call @__CheckForDebuggerJustMyCode@4 (089130Ch)
//008917C9 rep stos dword ptr es:[edi]
//008917CB mov ecx,89C003h
//008917D0 call 0089130C
// int d = a + b;
//008917D5 mov eax,dword ptr [a]
//008917D8 add eax,dword ptr [b]
//008917DB mov dword ptr [d],eax
// return d;
//008917DE mov eax,dword ptr [d]
int d = a + b;
008917D5 mov eax,dword ptr [ebp+8] //將a賦值給eax
008917D8 add eax,dword ptr [ebp+0Ch] //將eax加上b,即2+6 = 8
008917DB mov dword ptr [ebp-8],eax //將eax=8賦值給d
return d;
008917DE mov eax,dword ptr [ebp-8] //將d的值賦值給暫存器eax
}

008917E1 pop edi //出堆疊edi
008917E2 pop esi //出堆疊esi
008917E3 pop ebx //出堆疊ebx
008917E4 add esp,0CCh //將add函式銷毀,esp回到ebp的位置
008917EA cmp ebp,esp
008917EC call 00891235 //回到main
008917F1 mov esp,ebp //將ebp的地址給esp
008917F3 pop ebp //出堆疊ebp,讓ebp指向上一次地址位置
008917F4 ret
008925C7 add esp,8 // 銷毀兩個形參,esp指向main函式堆疊頂
008925CA mov dword ptr [ebp-20h],eax //將eax(回傳)值8賦值給ebp - 20h 也就是c


printf("%d\n",c);
008925CD mov eax,dword ptr [ebp-20h] //將c值賦給eax
008925D0 push eax
008925D1 push 897BCCh
008925D6 call 008913A2
008925DB add esp,8
return 0;
008925DE xor eax,eax
}
//和add函式銷毀一樣,main函式銷毀,結束程式
008925E0 pop edi
008925E1 pop esi
008925E2 pop ebx
008925E3 add esp,0E4h
008925E9 cmp ebp,esp
008925EB call 00891235
008925F0 mov esp,ebp
008925F2 pop ebp
008925F3 ret
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/292346.html
標籤:其他
