【C語言】小伙子,你真的知道函數(shù)是怎么調(diào)用的嗎?-創(chuàng)新互聯(lián)

吼吼吼,噴火👀

成都創(chuàng)新互聯(lián)從2013年成立,先為文縣等服務(wù)建站,文縣等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為文縣企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

在這里插入圖片描述

提示:本文意在使用反匯編的語言給大家介紹函數(shù)調(diào)用中棧區(qū)上的過程變化,加深我們對于代碼底層的理解,由于不同的編譯器使用下,可能造成一些差異,但這并不影響我們對于知識原理的掌握,所以本文不必過多糾結(jié)細(xì)節(jié)處的變化,將內(nèi)容原理學(xué)會才是最重要的。接下來就開始今天的學(xué)習(xí)吧!

文章目錄
  • 一、知識準(zhǔn)備工作
    • 1. 寄存器
    • 2. 匯編指令
  • 二、函數(shù)棧幀的創(chuàng)建與銷毀過程(從匯編角度去看)
    • 2.1 main函數(shù)棧幀的創(chuàng)建和初始化
    • 2.2 局部變量的初始化和分配棧幀空間
    • 2.3 函數(shù)調(diào)用前的準(zhǔn)備工作
    • 2.4 Add函數(shù)棧幀的創(chuàng)建和初始化
    • 2.5 Add函數(shù)內(nèi)部進(jìn)行的變量初始化和分配空間
    • 2.6 Add函數(shù)棧幀的銷毀和返回值的帶回
    • 2.7 重新回到main函數(shù)后,形參會怎么樣呢?
  • 三、回答幾個問題,檢查你看懂沒😐
    • 1.局部變量是怎么創(chuàng)建的?
    • 2.為什么局部變量的值是隨機值?
    • 3.函數(shù)是怎么傳參的?傳參的順序是怎樣的?
    • 4.形參和實參是什么關(guān)系?
    • 5.函數(shù)調(diào)用是怎么做的?
    • 6.函數(shù)調(diào)用結(jié)束后是怎么返回的


一、知識準(zhǔn)備工作 1. 寄存器

寄存器是集成到CPU內(nèi)部的用來存放數(shù)據(jù)的一些小型存儲區(qū)域,可以暫時存放參與運算的數(shù)據(jù)和運算結(jié)果。
分為標(biāo)志寄存器FR,指令指針寄存器IP,段寄存器,指針和變址寄存器,通用寄存器組等……

>我們今天所講到的都是通用寄存器

寄存器名稱功能
eax累加寄存器,相對于其他寄存器,在進(jìn)行運算方面比較常用。
ebx基地址寄存器,通常作為內(nèi)存偏移指針使用(相對于EAX、 ECX、EDX)
ecx計數(shù)寄存器,通常用于特定指令的計數(shù),可用于循環(huán)操作。比如:重復(fù)的字符存儲操作,或者數(shù)字統(tǒng)計
edi通常在內(nèi)存操作指令中作為“目的地址指針”使用。當(dāng)然, EDI可以被裝入任意的數(shù)值, 但通常沒有人把它當(dāng)作通用寄存器來用
esi通常在內(nèi)存操作指令中作為“源地址指針”使用。當(dāng)然, ESI也可以被裝入任意的數(shù)值, 但通常沒有人把它當(dāng)作通用寄存器來用。
esp為擴展基址指針寄存器,也被稱為幀指針寄存器,用于存放函數(shù)棧底指針。它會隨著我們??臻g的大小變化,從而改變其所指地址的位置,以適應(yīng)棧幀空間的大小變化
ebp為擴展棧指針寄存器,是指針寄存器的一種,用于存放函數(shù)棧頂指針。作為一個完整函數(shù)所對應(yīng)的棧幀空間的底線
2. 匯編指令

1.push

壓棧操作,他會改變esp所指向的位置,從而適應(yīng)棧幀空間的擴大,操作方式就是將操作數(shù)直接壓棧到棧幀空間

注意:在x86的環(huán)境下,esp的地址以4字節(jié)為單位

004018B0 55                   push        ebp  
004018B9 53                   push        ebx  
004018BA 56                   push        esi 

以上就是push指令的代碼形式

2.pop

出棧操作,他也會改變esp所指向的位置,從而適應(yīng)棧幀空間的減小,操作方式就是將操作數(shù)直接跳出,離開棧幀空間

注意:在x86的環(huán)境下,esp的地址以4字節(jié)為單位

00401910 5F                   pop         edi  
00401911 5E                   pop         esi  
00401912 5B                   pop         ebx 

以上就是pop指令的代碼形式

3.add

用于將兩個運算子相加,將所得結(jié)果寫入第一個運算子,可以用于改變esp,edp等所指位置,調(diào)整他倆所維護(hù)的函數(shù)棧幀空間

00401913 81 C4 E4 00 00 00    add         esp,0E4h 
004018F7 83 C4 08             add         esp,8 
00401875 83 C4 18             add         esp,18h

以上就是add指令的代碼形式。從代碼可以看出,add操作后,改變了esp所指位置,效果和pop與push指令相似

4.sub

減操作指令,從寄存器中減去后運算子,并將結(jié)果保存到目標(biāo)寄存器中

004018B3 81 EC E4 00 00 00    sub         esp,0E4h
00401833 81 EC C0 00 00 00    sub         esp,0C0h 

以上就是sub指令的代碼形式。從代碼可以看出,sub操作后,改變了esp所指位置,效果和pop與push指令相似

5.lea

lea指令即為load effective address,其實這個指令的功能比較常見的就是將地址賦值給我的操作數(shù),相當(dāng)于我對地址進(jìn)行一個臨時拷貝放到目的地址指針里面去

004018BC 8D 7D DC             lea         edi,[ebp-24h]

上面的代碼,就是將[edp-24h]的地址放到edi目的地址指針里面去,這也是匯編當(dāng)中常見的一種寫法

6.mov

將一個源操作地址傳送到目的地地址(相當(dāng)于賦值操作),源操作地址并不會被改變

004018B1 8B EC               mov        ebp,esp  
004018BF B9 09 00 00 00      mov        ecx,9  這里賦值給ecx計數(shù)寄存器,進(jìn)行循環(huán)
004018C4 B8 CC CC CC CC      mov        eax,0CCCCCCCCh 

上面就是mov指令的代碼形式,第二行和第三行,我分別將9(16進(jìn)制下的表示形式)和1個數(shù)據(jù)(16進(jìn)制下的表示形式)賦值給ecx計數(shù)寄存器和eax累加寄存器

7.rep和stos

rep就是repeat,它其實就是一個重復(fù)前綴指令,需要搭配其他指令,補全具體的功能

stos就是store string,它其實就是一個串存儲指令,它的功能是將eax中的數(shù)據(jù)放入的edi所指的地址中,同時,edi會增加4個字節(jié),rep使指令重復(fù)執(zhí)行ecx中填寫的次數(shù)。

004018BC 8D 7D DC             lea         edi,[ebp-24h]  
004018BF B9 09 00 00 00       mov         ecx,9  
004018C4 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
004018C9 F3 AB                rep stos    dword ptr es:[edi] 

以上代碼,就是一個完整功能的匯編指令。其功能為,將一個開辟好的函數(shù)棧幀內(nèi)容用eax寄存器里面的內(nèi)容賦值

dword是指double word,即為雙字大小,一個字的字節(jié)大小是2字節(jié),所以雙字的字節(jié)大小就為4字節(jié)

ptr指的是pointer,我們最后一行指令就是將eax里面的內(nèi)容賦值到edi(目的地址指針)所指向的地址處,一次賦值4個字節(jié),重復(fù)ecx次

由于棧幀空間使用習(xí)慣是,先使用高地址再使用低地址,所以我們會以edi為起點,進(jìn)行循環(huán)賦值,直到9h減為0次,才會停止。

注意:向下賦值,從低地址向高地址處進(jìn)行賦值,以edi中地址指針為起點向下,直到遇到了ebp指針

8.jmp

無條件跳轉(zhuǎn)指令,以下代碼就是無條件跳轉(zhuǎn)到IP地址為0401770h處,其實這個地址就是Add函數(shù)的地址

004010B4 E9 B7 06 00 00       jmp         Add (0401770h) 

下面這張圖片就是跳轉(zhuǎn)之后的結(jié)果,由光標(biāo)的位置我們可以看到,通過jmp指令,我們確實跳轉(zhuǎn)到了相應(yīng)的地址處
在這里插入圖片描述

9.call

將程序下一條指令的位置的IP壓入堆棧中,轉(zhuǎn)移到調(diào)用的子程序。

一般來說,執(zhí)行一條CALL指令相當(dāng)于執(zhí)行一條push指令加一條jmp指令。
call指令是調(diào)用子程序,后面緊跟的應(yīng)該是子程序名或者過程名。

004018F2 E8 BD F7 FF FF       call        _Add (04010B4h) 

下面圖片就是call指令執(zhí)行后的結(jié)果,壓棧的操作,可以通過監(jiān)視窗口,觀察esp的地址變化來看
在這里插入圖片描述
在這里插入圖片描述

10.ret

用于終止當(dāng)前函數(shù)的執(zhí)行,將運行權(quán)交還給上層函數(shù)。也就是,當(dāng)前函數(shù)的幀將被回收。
并且pop掉棧幀空間的call指令的下一條指令的地址,用于回到上層函數(shù)中call指令的下一條指令,同時esp指針地址+4字節(jié)(因為call下一條指令的IP被pop掉了)

004017BB C3                   ret 
二、函數(shù)棧幀的創(chuàng)建與銷毀過程(從匯編角度去看)

1.從下面的原碼中我們也可以看出,其實我們的main函數(shù)也是被其他函數(shù)調(diào)用的。在vs2022的環(huán)境底下,main函數(shù)被_SCRT_STARTUP_MAIN函數(shù)調(diào)用了

#if defined _SCRT_STARTUP_MAIN

    using main_policy = __scrt_main_policy;
    using file_policy = __scrt_file_policy;
    using argv_policy = __scrt_narrow_argv_policy;
    using environment_policy = __scrt_narrow_environment_policy;

    static int __cdecl invoke_main()
    {return main(__argc, __argv, _get_initial_narrow_environment());
    }

2.下面的代碼分別是C語言代碼和匯編語言代碼

#define _CRT_SRCURE_NO_WARNINGS 1
#pragma  warning (disable:4996) 
#includeint Add(int x, int y)
{int z = 0;
	z = x + y;
	return z;
}
int main()
{int a = 10;
	int b = 20;
	int c = 0;

	c = Add(a, b);

	printf("%d", c);
	return 0;
}
int main()                    main函數(shù)內(nèi)部的匯編代碼
{004018B0 55                   push        ebp  
004018B1 8B EC                mov         ebp,esp  
004018B3 81 EC E4 00 00 00    sub         esp,0E4h  
004018B9 53                   push        ebx  
004018BA 56                   push        esi  
004018BB 57                   push        edi  
004018BC 8D 7D DC             lea         edi,[ebp-24h]  
004018BF B9 09 00 00 00       mov         ecx,9  
004018C4 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
004018C9 F3 AB                rep stos    dword ptr es:[edi]  
004018CB B9 08 C0 40 00       mov         ecx,offset _EDC442E6_函數(shù)棧幀的創(chuàng)建和銷毀\函數(shù)棧幀的創(chuàng)建和銷毀\函數(shù)棧幀的創(chuàng)建和銷毀@c (040C008h)  
004018D0 E8 46 FA FF FF       call        @__CheckForDebuggerJustMyCode@4 (040131Bh)  
	int a = 10;
004018D5 C7 45 F8 0A 00 00 00 mov         dword ptr [a],0Ah  
	int b = 20;
004018DC C7 45 EC 14 00 00 00 mov         dword ptr [b],14h  
	int c = 0;
004018E3 C7 45 E0 00 00 00 00 mov         dword ptr [c],0  

	c = Add(a, b);
004018EA 8B 45 EC             mov         eax,dword ptr [b]  
004018ED 50                   push        eax  
004018EE 8B 4D F8             mov         ecx,dword ptr [a]  
004018F1 51                   push        ecx  
004018F2 E8 BD F7 FF FF       call        _Add (04010B4h)  
004018F7 83 C4 08             add         esp,8  
004018FA 89 45 E0             mov         dword ptr [c],eax  

	printf("%d", c);
004018FD 8B 45 E0             mov         eax,dword ptr [c]  
00401900 50                   push        eax  
00401901 68 30 7B 40 00       push        offset string "%d" (0407B30h)  
00401906 E8 C7 F7 FF FF       call        _printf (04010D2h)  
0040190B 83 C4 08             add         esp,8  
	return 0;
0040190E 33 C0                xor         eax,eax  
}
00401910 5F                   pop         edi  
00401911 5E                   pop         esi  
00401912 5B                   pop         ebx  
00401913 81 C4 E4 00 00 00    add         esp,0E4h  
00401919 3B EC                cmp         ebp,esp  
0040191B E8 24 F9 FF FF       call        __RTC_CheckEsp (0401244h)  
00401920 8B E5                mov         esp,ebp  
00401922 5D                   pop         ebp  
00401923 C3                   ret  

int Add(int x, int y)         Add函數(shù)內(nèi)部的匯編代碼
{00E11770 55                   push        ebp  
00E11771 8B EC                mov         ebp,esp  
00E11773 81 EC CC 00 00 00    sub         esp,0CCh  
00E11779 53                   push        ebx  
00E1177A 56                   push        esi  
00E1177B 57                   push        edi  
00E1177C 8D 7D F4             lea         edi,[ebp-0Ch]  
00E1177F B9 03 00 00 00       mov         ecx,3  
00E11784 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E11789 F3 AB                rep stos    dword ptr es:[edi]  
00E1178B B9 08 C0 E1 00       mov         ecx,offset _EDC442E6_函數(shù)棧幀的創(chuàng)建和銷毀\函數(shù)棧幀的創(chuàng)建和銷毀\函數(shù)棧幀的創(chuàng)建和銷毀@c (0E1C008h)  
00E11790 E8 86 FB FF FF       call        @__CheckForDebuggerJustMyCode@4 (0E1131Bh)  
	int z = 0;
00E11795 C7 45 F8 00 00 00 00 mov         dword ptr [z],0  
	z = x + y;
00E1179C 8B 45 08             mov         eax,dword ptr [x]  
00E1179F 03 45 0C             add         eax,dword ptr [y]  
00E117A2 89 45 F8             mov         dword ptr [z],eax  
	return z;
00E117A5 8B 45 F8             mov         eax,dword ptr [z]  
}
00E117A8 5F                   pop         edi  
00E117A9 5E                   pop         esi  
00E117AA 5B                   pop         ebx  
00E117AB 81 C4 CC 00 00 00    add         esp,0CCh  
00E117B1 3B EC                cmp         ebp,esp  
00E117B3 E8 8C FA FF FF       call        __RTC_CheckEsp (0E11244h)  
00E117B8 8B E5                mov         esp,ebp  
00E117BA 5D                   pop         ebp  
00E117BB C3                   ret 
2.1 main函數(shù)棧幀的創(chuàng)建和初始化
int main()                    main函數(shù)內(nèi)部的匯編代碼
{004018B0 55                   push        ebp  
004018B1 8B EC                mov         ebp,esp  
004018B3 81 EC E4 00 00 00    sub         esp,0E4h //將esp位置上移
004018B9 53                   push        ebx  
004018BA 56                   push        esi  
004018BB 57                   push        edi  
004018BC 8D 7D DC             lea         edi,[ebp-24h] //將ebp-24h這個地址給到目的地址指針edi中
004018BF B9 09 00 00 00       mov         ecx,9  //將9給到ecx寄存器
004018C4 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  //把0cccccccch這個內(nèi)容給到eax寄存器
004018C9 F3 AB                rep stos    dword ptr es:[edi]  //從edi此時存儲位置開始,逐漸向高地址開始初始化內(nèi)容,直到ebp所指位置為終點,結(jié)束初始化內(nèi)容的步驟

開始講解:
由于我們壓棧edp,所以esp會先增加4字節(jié)的大小,然后又mov把esp給到edp,然后又對esp減小0E4h大小,所以esp指針向上移動了0E4h字節(jié)的大小,其實這就是為main函數(shù)的棧幀開辟空間大小,此時esp的位置便指向函數(shù)棧幀的頂端,然后我們在分別壓棧ebx,esi,edi,下一步我們對main函數(shù)棧幀內(nèi)容進(jìn)行初始化,每一次初始化double word字節(jié)大小的內(nèi)容,初始化為0cccccccch這個內(nèi)容,重復(fù)循環(huán)9次
在這里插入圖片描述
在這里插入圖片描述

2.2 局部變量的初始化和分配棧幀空間
int a = 10;
00E118D5 C7 45 F8 0A 00 00 00 mov         dword ptr [ebp-8],0Ah  
	int b = 20;
00E118DC C7 45 EC 14 00 00 00 mov         dword ptr [ebp-14h],14h  
	int c = 0;
00E118E3 C7 45 E0 00 00 00 00 mov         dword ptr [ebp-20h],0 

我們其實只要用局部變量的值覆蓋掉main函數(shù)棧幀中剛開始初始化的內(nèi)容,這樣就完成了局部變量的內(nèi)容初始化和空間的分配這個步驟了
在這里插入圖片描述

2.3 函數(shù)調(diào)用前的準(zhǔn)備工作
c = Add(a, b);
00E118EA 8B 45 EC             mov         eax,dword ptr [ebp-14h]  //先將ebp-14h地址處的值給到我們的eax寄存器
00E118ED 50                   push        eax  //然后再將eax中的值進(jìn)行壓棧操作
00E118EE 8B 4D F8             mov         ecx,dword ptr [ebp-8]  //先將ebp-8h地址處的值給到我們的ecx寄存器
00E118F1 51                   push        ecx  //然后再將ecx中的值進(jìn)行壓棧操作
00E118F2 E8 BD F7 FF FF       call        00E110B4  //這里就是跳轉(zhuǎn)到add函數(shù)內(nèi)部的一條指令,并且將00E110B4地址進(jìn)行壓棧操作

我們再函數(shù)調(diào)用前肯定是要有準(zhǔn)備的,由匯編可以看出,我們進(jìn)行兩次的壓棧操作,這其實就是在開辟形參x y,他們只是實參a b的一份臨時拷貝,另外一個重要的點就是,我們在進(jìn)行壓棧操作時,會先對變量b進(jìn)行壓棧操作,然后在對變量a進(jìn)行壓棧操作
在這里插入圖片描述
下面就是執(zhí)行call指令后的畫面,再次逐語句調(diào)試后就來到了Add函數(shù)內(nèi)部的匯編語言代碼
在這里插入圖片描述
在這里插入圖片描述

2.4 Add函數(shù)棧幀的創(chuàng)建和初始化
00E11770 55                   push        ebp  //壓棧main函數(shù)棧幀底部的地址
00E11771 8B EC                mov         ebp,esp  
00E11773 81 EC CC 00 00 00    sub         esp,0CCh  
00E11779 53                   push        ebx  
00E1177A 56                   push        esi  
00E1177B 57                   push        edi  
00E1177C 8D 7D F4             lea         edi,[ebp-0Ch]  
00E1177F B9 03 00 00 00       mov         ecx,3  
00E11784 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
00E11789 F3 AB                rep stos    dword ptr es:[edi] 

注意觀察Add函數(shù)中的第一條匯編代碼,我們將ebp指針地址壓棧到了棧幀空間當(dāng)中,而這個edp其實就是指向main函數(shù)棧幀底部的指針,隨后我們又進(jìn)行了調(diào)整ebp和esp位置的匯編指令操作,其目的就是重新改變ebp和esp所維護(hù)的函數(shù)棧幀空間,由原來的維護(hù)main函數(shù)改成維護(hù)Add函數(shù)。

然后后面的指令又和main函數(shù)棧幀創(chuàng)建的指令一樣了,還是分配空間,壓棧ebx,esi,edi,然后再進(jìn)行Add函數(shù)棧幀的初始化內(nèi)容操作。
在這里插入圖片描述

2.5 Add函數(shù)內(nèi)部進(jìn)行的變量初始化和分配空間
int z = 0;
00E11795 C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0  
	z = x + y;
00E1179C 8B 45 08             mov         eax,dword ptr [ebp+8]  
00E1179F 03 45 0C             add         eax,dword ptr [ebp+0Ch]  
00E117A2 89 45 F8             mov         dword ptr [ebp-8],eax

我們這里其實就是在ebp-8地址處進(jìn)行了變量c的內(nèi)容初始化和分配空間,將其內(nèi)容初始化為0

然后我們將ebp+8處的值放到eax寄存器當(dāng)中,再將eax中的值加上ebp+0ch處的值,再次給到eax寄存器當(dāng)中,然后我們在把eax中的值mov到ebp-8(其實就是變量z所在的位置),就是z=x+y的操作
在這里插入圖片描述

2.6 Add函數(shù)棧幀的銷毀和返回值的帶回
return z;
00E117A5 8B 45 F8             mov         eax,dword ptr [ebp-8]  //用eax存儲了ebp-8處的值
}
00E117A8 5F                   pop         edi  
00E117A9 5E                   pop         esi  
00E117AA 5B                   pop         ebx  
00E117AB 81 C4 CC 00 00 00    add         esp,0CCh  
00E117B1 3B EC                cmp         ebp,esp  
00E117B3 E8 8C FA FF FF       call        00E11244  
00E117B8 8B E5                mov         esp,ebp  
00E117BA 5D                   pop         ebp  //讓ebp重新回到main函數(shù)棧幀的底部
00E117BB C3                   ret  //執(zhí)行這條指令后我們會直接回到main函數(shù)中call指令的下一條指令的位置

注意我們的指令,我們將ebp-8處的值重新放回到eax寄存器當(dāng)中(這么做的原因是什么呢?其實我們都知道離開函數(shù)時,變量z就會被銷毀,其中所被賦有的值也會灰飛煙滅,但我們的寄存器可不會因為函數(shù)調(diào)用的結(jié)束而被銷毀,它可是被集成在CPU上的啊,怎么可能說銷毀就銷毀)

我們將edi,esi,ebx等進(jìn)行了出棧操作pop(彈出)然后增加了esp的值,其實就是向下移動,改變他和ebp所維護(hù)的棧幀空間了,最后我們pop了ebp的值,值得注意的是此刻的ebp可不是一般的ebp(他是王維詩里的ebp),他其實就是調(diào)用函數(shù)前我們壓棧進(jìn)去的main函數(shù)棧幀底部的地址位置,所以我們就能通過pop找回了main棧幀的底部,讓ebp重新回到main的底部,也就是讓他和esp重新去維護(hù)main函數(shù),離開Add函數(shù)棧幀(也就是銷毀Add函數(shù)棧幀)
在這里插入圖片描述

2.7 重新回到main函數(shù)后,形參會怎么樣呢?
c = Add(a, b);
00E118EA 8B 45 EC             mov         eax,dword ptr [ebp-14h]  
00E118ED 50                   push        eax  
00E118EE 8B 4D F8             mov         ecx,dword ptr [ebp-8]  
00E118F1 51                   push        ecx  
00E118F2 E8 BD F7 FF FF       call        00E110B4  
00E118F7 83 C4 08             add         esp,8  //這就是Add函數(shù)調(diào)用結(jié)束后,我們要做的第一件事,也是第一條指令
00E118FA 89 45 E0             mov         dword ptr [ebp-20h],eax 

注意我們的光標(biāo)位置,他此時就是指向我們call指令的下一條指令

我們的esp在經(jīng)過add匯編指令之后會向下移動8個字節(jié)的位置,正好跳過了我們?yōu)樾螀 y開辟的棧幀空間,此時也就是銷毀了形參x y
在這里插入圖片描述

讀到這里我們今天的學(xué)習(xí)就結(jié)束了,我們講解了Add函數(shù)在匯編角度下是如何被調(diào)用的?又是如何將返回值帶回?又是如何開辟函數(shù)棧幀?如何銷毀函數(shù)棧幀?等等各種問題,如果你還是有疑問的話,建議將這篇文章收藏起來,多看幾次,當(dāng)然光看肯定是學(xué)不會的,你可以在自己的電腦上試一試,觀察一下具體的現(xiàn)象,加深理解

三、回答幾個問題,檢查你看懂沒😐 1.局部變量是怎么創(chuàng)建的?

我們先為變量所在的函數(shù)開辟一塊兒函數(shù)棧幀空間,為其分配好相應(yīng)的大小,并且對其進(jìn)行初始化,初始化的內(nèi)容就是(0cccccccch),正因為如此,如果我們不對局部變量進(jìn)行初始化的話,他的值其實就是0cccccccch,這個值正是燙燙燙的原因,所以我們局部變量的創(chuàng)建,是在函數(shù)棧幀開辟好的前提下,在里面尋找一個地址,把這個地址的空間分配給我們的變量,如果你想初始化這個變量,就把(0ccccccch)用你想要的值將其給覆蓋掉,就可以了。

2.為什么局部變量的值是隨機值?

因為函數(shù)棧幀開辟后,會先對函數(shù)棧幀進(jìn)行內(nèi)容初始化,初始化為0CCCCCCCCh。這正是隨機值的原因

3.函數(shù)是怎么傳參的?傳參的順序是怎樣的?

我們會在調(diào)用函數(shù)前進(jìn)行函數(shù)參數(shù)的內(nèi)容,進(jìn)行一個壓棧操作,當(dāng)進(jìn)入到被調(diào)用函數(shù)內(nèi)部的時候,我們會通過指針的偏移量找到函數(shù)參數(shù),并對其進(jìn)行操作。

由上面的講解,我們可以知道,傳參時,以從右向左的順序來進(jìn)行壓棧操作,我們先將右邊的參數(shù)壓棧,然后再對左邊的參數(shù)壓棧。

所以傳參順序是從左向右的。

4.形參和實參是什么關(guān)系?

形參是實參的一份臨時拷貝,從圖中我們也可以看出,改變形參是不會影響實參的,他只是進(jìn)行了值的一份臨時拷貝,并不會影響到我們的實參。

所以修改形參,是不會對實參有所改變的。并且離開函數(shù)后,形參會快速被銷毀,可見其生命周期的短暫

5.函數(shù)調(diào)用是怎么做的?

我們會通過匯編語言中的call指令,先將其下一條指令的IP壓棧到我們的棧幀空間當(dāng)中,并且指向call指令,會進(jìn)入到被調(diào)用函數(shù)的匯編代碼當(dāng)中,進(jìn)行被調(diào)用函數(shù)的匯編指令

并且我們函數(shù)調(diào)用結(jié)束后,通過ret指令能夠回到上一層函數(shù)中call指令的下一條指令,因為我們的棧幀空間當(dāng)中已經(jīng)壓棧了call指令的下一條指令的IP

6.函數(shù)調(diào)用結(jié)束后是怎么返回的

我們是通過eax寄存器將我們被調(diào)用函數(shù)中的返回值,存儲起來,等回到上一層函數(shù)后,再將eax寄存器中的值釋放出來,給到我們想要的變量里面。

這么做的原因,其實就是因為函數(shù)調(diào)用結(jié)束后,其中變量所占空間都會還給操作系統(tǒng),也就是我們俗稱的變量銷毀,如果我們想要將這個值帶回的話,我們就需要那么一個東西暫存一下這個值,并且這個東西是不會因為函數(shù)調(diào)用結(jié)束而銷毀的,那這個東西是什么呢?額額額,不就是eax寄存器么🥱🥱🥱

你是否還在尋找穩(wěn)定的海外服務(wù)器提供商?創(chuàng)新互聯(lián)www.cdcxhl.cn海外機房具備T級流量清洗系統(tǒng)配攻擊溯源,準(zhǔn)確流量調(diào)度確保服務(wù)器高可用性,企業(yè)級服務(wù)器適合批量采購,新人活動首月15元起,快前往官網(wǎng)查看詳情吧

本文題目:【C語言】小伙子,你真的知道函數(shù)是怎么調(diào)用的嗎?-創(chuàng)新互聯(lián)
瀏覽地址:http://muchs.cn/article2/hidoc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站、定制網(wǎng)站搜索引擎優(yōu)化、小程序開發(fā)、做網(wǎng)站、網(wǎng)站營銷

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都定制網(wǎng)站建設(shè)