函數
如果一個函數有聲明沒實現,那么就會出現鏈接錯誤:
以上代碼會出現鏈接錯誤。
- 函數實現
int MyTest(int x, int y)
{
return x + y;
}
以上是函數實現,函數實現可以與聲明放在同一個文件中,也可以不在同一個文件
中。
-
函數調用
在運行過程中,函數名+括號+實參,可以實現函數調用。 -
實參與形參的概念
所謂的形參,就是在函數實現過程中,占位的參數,比如shang上方代碼中的x,y都是形參
函數的作者,在實現函數時,是不知道函數調用時,形參的具體值的。在函數調用時,調用函數的作者,是具體知道參數的值,那個值,就是實參。
以上的5,6就是實參(實際傳遞的參數)。 -
return與返回值
return語句用於函數返回,並帶出返回值(如果有)。
對於函數調用那一方,可以將函數調用后的返回值存儲下來,也可以直接放棄。
除了“帶出返回值”的作用外,還要加強“函數返回”的理解。所謂的函數返回,具體說就
是將執行流程跳轉到調用當前函數的那一方
C語言中的變參函數
- printf在MSDN中奇怪的資料
以上的中括號,其實是技術文檔的約定,表示可選內容。所以,從C語言的角度看,
printf的函數聲明,其實是:
以上的三個英文句號(...),代表參數個數可變。也就是說,函數調用過程中,傳遞任意
多個實參,都會被認為語法正確。
變參函數,是我們可以發明更方便的函數,比如,計算任意多個正數的和。
另外,要注意,變參語法並沒有顛覆之前學習的規則,也就是說,函數聲明(又
稱為函數原型)與函數的調用必須匹配。
實踐以下代碼加深對聲明與調用必須匹配的理解
int main(int argc, char* argv[])
{
char *szHello = ("hello,%d");
printf(szHello,10);
}
函數的本質是什么
函數本身,其實就是一堆有意義的機器碼。
我們可以通過調試過程中,反匯編窗口去確認。
內存區域的區分技巧
操作系統為了好管理,內存是分區域的。
對於去掉隨機基地址的工程,我們可以通過內存地址,簡單區分它屬於哪塊內存區
域。
全局區域:一般以0x004x開頭,全局區域中一般存放:函數的機器碼、字符串、
全局變量
棧區域:一般以0x0018、0x0019、0x0012開頭,函數中的局部變量、函數調用
過程中的形參都會放在棧中
堆區域:暫時不用掌握
函數的調用過程
函數的調用過程,涉及兩方:
函數的調用方(main)
函數的被調用方(MyAdd)
函數的調用及返回的過程,就是在調用方和被調用方切換流程的過程。
又因為函數肩負着接口的作用,所以,除了流程切換之外,還需要保證:
調用方傳遞的參數,可以被被調用方正確的獲取
被調用方要能夠傳遞出返回值,並且被調用方正確的獲取
流程轉移方面:過得去,回得來
那么,C程序中,函數調用過程的細節是如何的呢?
棧幀的概念
內存區域有一塊被划分為棧,所有被調用的函數,都會使用這塊區域,但是,他們的
局部變量、參數等並不會重疊。
每一次函數被調用,都有特定的一塊占內存與這次調用對應,這稱為“棧幀”。
開始調用某函數,會自動分配棧幀空間。
如果某函數調用結束,那么會回收棧幀空間,這個過程,稱為平衡棧。
調用過程細節
大概的輪廓,逐一詳細解釋:
在函數調用過程中
按照調用約定傳參(將實參復制到棧中)
保存返回地址
流程轉移到被調用函數
保存上一層棧幀的地址
開辟局部變量空間
執行被調用函數的相關代碼
最終將返回值存放在寄存器eax中
返回到調用方
按照約定傳參
們為了不將問題復雜,我們現在只討論C語言的默認調用約定,它具體的操作是:
從右往左傳參
將參數依次push到內存的棧中
【實際操作請見視頻】
實際上,C語言中的調用約定有三種:
C約定:從右往左傳參,通過內存棧區域傳參,調用方平衡棧(調用方通過匯
編,修改esp及存取)
_stdcall:傳參方向和傳參介質(內存)都與C約定一致。被調用方平衡棧。
_fastcall:傳參方向從左往右,介質是寄存器+內存,被調用方平衡棧
從節省空間角度而言,_stdcall更優秀。
面試題:printf是什么約定?為什么?
答:C約定。因為printf是變參函數,變參函數只有調用方才知道具體傳遞了幾個參
數,所以必須需要調用方平衡棧。