高級編程學習筆記(二) 函數調用棧空間的分配和釋放

原地址:http://blog.csdn.net/a8887396/article/details/8964712


1 函數執行的時候有自己的臨時棧 (c++中函數調用時有兩個棧空間,對象的棧空間和函數的棧空間)


 2 函數的參數就在臨時棧中,如果函數傳遞實參過去,則用來初始化臨時參數變量。


  1. #include <stdio.h>  
  2.   
  3. int add(int a,int b)  
  4. {  
  5.     printf("%d,%d\n",a,b);  
  6.     return a+b;  
  7. }  
  8.   
  9. int main()  
  10. {  
  11.     int (*fun)(int) = (int(*)(int))add;  
  12.     int r = fun(1);  
  13.     printf("%d\n",r);  
  14.     return 0;  
  15. }  


參數是fun傳遞的,實際調用的函數是add,在調用函數初始化臨時變量時,如果棧中有傳參數,則初始化臨時變量,若沒有,則不初始化。
所以上文中會出現一個很大的值,因爲b沒有被初始化
但如果 
int (*fun)(int) = (int(*)(int))add; 改成 int (*fun)(int,int,int) = (int(*)(int,int,int ))add;
int r = fun(1); 改成 int r = fun(1,2,5); 
這時的返回值是正確的(3),因爲add中的2個臨時變量都被初始化來,並且add中沒有第三個參數,所以5不會被使用.也不會出問題



3 返回值的返回形式
  通過寄存器返回值 (通過返回值返回值)

 通過參數返回值 (存放返回值的參數必須是指針,指針指向的區域必須事先分配)

 如果通過參數返回指針,那麼參數就必須是雙指針 (以此類推,參數是三指針,是向通過參數返回雙指針)


4 調用約定
__stdcall
__cdecl
__fastcall
  調用約定(Calling convention)決定以下內容: 函數參數的壓棧順序,由調用者還是被調用者把參數彈出棧,以及產生函數修飾名的方法。
  採用__cdecl約定時:函數參數按照從右到左的順序入棧,並且由調用函數者把參數彈出棧以清理堆棧。
  採用__stdcal約定時:函數參數按照從右到左的順序入棧,被調用的函數在返回前清理傳送參數的棧,函數參數個數固定。由於函數體本身知道傳進來的參數個數,因此被調用的函數可以在返回前用一條ret n指令直接清理傳遞參數的堆棧。
  採用__fastcall約定時:將函數的從左邊開始的兩個大小不大於4個字節(DWORD)的參數分別放在ECX和EDX寄存器,其餘的參數仍舊自右向左壓棧傳送,被調用的函數在返回前清理傳送參數的堆棧。__fastcall約定一般指傳送不超過4個字節的參數,通過寄存器,不用棧,這樣比較快。
  __cdecl和__stdcal之間的唯一區別在於返回時是由被調用者清理棧,還是由調用用者清理棧。
  但是,這兩種清理棧的方式,會對有什麼影響呢?
  __stdcall與__cdecl兩者之間的區別:
  WINDOWS的函數調用時需要用到棧(STACK,一種先入後出的存儲結構)。當函數調用完成後,棧需要清除,這裏就是問題的關鍵,如何清除?
  如果函數使用_cdecl,那麼棧的清除工作是由調用者,用COM的術語來講就是客戶來完成的。這樣帶來了一個棘手的問題,不同的編譯器產生棧的方式不盡相同,那麼調用者能否正常的完成清除工作呢?答案是不能。
  如果使用__stdcall,上面的問題就解決了,函數自己解決清除工作。所以,在跨(開發)平臺的調用中,我們都使用__stdcall(雖然有時是以WINAPI的樣子出現)。
  那麼爲什麼還需要_cdecl呢?當我們遇到這樣的函數如fprintf()它的參數是可變的,不定長的,被調用者事先無法知道參數的長度,事後的清除工作也無法正常的進行,因此,這種情況我們只能使用_cdecl。
  到這裏我們有一個結論,如果你的程序中沒有涉及可變參數,最好使用__stdcall關鍵字。


語法格式

linux下
int __attribute__((stdcall)) add(int *a,int *b){}
windows下
int  __stdcall add(int *a,int *b) {}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章