1. 程式人生 > >函式呼叫棧空間的分配和釋放

函式呼叫棧空間的分配和釋放

 1 函式執行的時候有自己的臨時棧 (c++中函式呼叫時有兩個棧空間,物件的棧空間和函式的棧空間)

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

#include <stdio.h>

int add(int a,int b)
{
	printf("%d,%d\n",a,b);
	return a+b;
}

int main()
{
	int (*fun)(int) = (int(*)(int))add;
	int r = fun(1);
	printf("%d\n",r);
	return 0;
}


引數是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) {}

來源:http://m.blog.csdn.net/blog/wa8887396/8964712