c語言的程式執行可以說就是不斷的呼叫函式,從主入口的main函式到各種各樣的庫函式,再到使用者自定義的完成特定功能的函式。
程式中關於乙個函式的操作主要包括三個方面。①函式宣告,②函式定義,③函式呼叫。簡而言之,函式宣告顧名思義就是告訴編譯器有乙個這樣的函式,同時告訴編譯器它的返回值型別和引數型別(引數預設表示引數還沒具體給出)。函式定義就是定義乙個函式要執行那些特定的操作。而函式呼叫就是讓程式在恰當的時候去執行行該函式定義的這些操作。
接下來主要討論函式在呼叫過程中,引數在堆疊中的情況。
在x86平台下,c語言函式呼叫的過程是這樣的。首先把引數從後往前壓入棧中(從後往前,這樣在執行部分就可以直接從棧頂乙個乙個的順序取出來)。(至於彙編部分的其他細節不討論,其他位址指標也不是本文的重點)。在程式執行的時候,就從棧頂把乙個乙個把相應的資料拿出來,賦值給變數。
如此,在傳入引數和函式實際引數型別或數量不相等時,就會出現一些問題。如下,簡單舉一例。
#include
typedef
long
long ll;
void ptchar();//宣告
int main()
void ptchar(a1)//定義
int a1;//☆
在程式中,主程式向mian函式傳入的引數是乙個long long型別,它佔八個位元組,而函式中實際接收的是乙個int型別,佔四個位元組。☆處據說是一種老式的寫法,和新寫法是一樣一樣的。
那麼這個程式輸出結果是多少呢?」20202020」,為啥?7070707070部分資料哪去了呢?實際上70707070也被壓入了棧中,但是由於函式實際只取出乙個int(四位元組)所以就拿不到70707070部分的資料了。
那為什麼拿到的是20202020,而不是70707070呢?以下是我個人理解,如有錯誤請大神指正。在pc上,使用的是小端位元組序,這意味著低數字的資料被放在低位位址中,高數字的資料被放在高位位址中。而棧頂對應的是低位址,棧低對應的是高位址,所以是低數字對應的資料先出棧,高數字對應的資料後出棧,因此上面的例子中只取到了20202020。如果想取到70707070,只要多加乙個接受的引數int a2就可以了。
那麼如果傳入的引數多餘實際的引數會如何呢?如下:
#include
typedef
long
long ll;
void ptchar();
int main()
void ptchar(a1,a2,a3)
int a1,a2,a3;
它的輸出是「20202020 70707070 3f」,這裡3f是乙個我看不懂的資料,確實,他是棧裡面表示其他的含義的資料,不過這裡被取到了,因為實際引數多於傳入的引數,就會往棧的下面繼續取數。
還有碰到傳小數的情況要注意,函式在傳遞的過程中會把float(4位元組)轉為double(八字節),所以傳遞過程中小數統統佔8個位元組。
小插曲:
以下**分別輸出啥? 可以自己在32 和 64的環境下測試一下。
char c='a';
printf("%d\n", sizeof(char));
printf("%d\n", sizeof('a'));
printf("%d\n", sizeof(c));
結果是1,4,1,為何?char不是乙個位元組嗎?其實』a』相當於是int的97,所以成四個位元組了。我也是看了網上別人說的才知道的。
接下來轉戰x86_64的環境。
引數在堆疊中整個過程基本上是一致的。
但是,如果試一下下面的**,就會發現有問題出現了。
#include
typedef
long
long ll;
void ptchar();
int main()
void ptchar(a1,a2)
int a1,a2;
我執行的結果是」20202020 c1b2df88」,前面20202020顯然已經說過了,但是後面出現了乙個不能理解的數字,而且重新執行一下程式,後面的數字變了。顯然這不是呼叫函式傳過來的,而是拿了堆疊裡面的乙個資料。
為什麼呢?顯然堆疊並沒有把long long八個位元組傳過來,只傳過來四個位元組。
然後我們再看看下面這段**:
#include
typedef long long ll;
void ptchar();
int main()
void ptchar(a1,a2,a3,a4,a5,a6,a7)
int a1,a2,a3,a4,a5,a6,a7;
試試結果是啥?
1 2 3 4 5 6 20202020
70707070
看,70707070出來了,資料就再20202020的後面的位址中。
那為什麼第一次不行呢?(其實第一次用取位址的方法也取不到值)
這是因為,在x64平台下面,前6個引數通過64位暫存器來傳遞,相當於是暫存器直接把值賦值給了int,顯然高位就丟了。後面的例子,long long位於第七個引數,並不是通過暫存器傳遞的,而是通過壓棧傳遞的,自然我們就可以通過取位址的方法獲得其值了。好累,去休息休息。
參考:
c 函式堆疊呼叫
由乙個.cpp c檔案到達乙個可執行檔案分為以下幾部分 預編譯階段 進行巨集替換,刪除注釋,展開標頭檔案,新增行號等 編譯階段 進行詞法,語法,語義分析,和 優化,生成彙編指令 彙編 翻譯指令 連線階段 合併段和符號表,符號解析 執行階段 建立虛擬位址和物理對映,載入指令和資料,程式入口位址寫入暫存...
為何C語言(的函式呼叫)需要堆疊
為何c語言 的函式呼叫 需要堆疊,而組合語言卻不需要堆疊 之前看了很多關於 uboot的分析,其中就有說要為 c語言的執行,準備好堆疊。而自己在 uboot的 start.s彙編 中,關於系統初始化,也看到有堆疊指標初始化這個動作。但是,從來只是看到有人說系統初始化要初始化堆疊,即正確給堆疊指標 s...
C 堆疊與函式呼叫
一 c 程式記憶體分配 1 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都在棧上建立,函式結束是,這些儲存單元自動被釋放。棧記憶體的分配運算內置於處理器的指令集中,一般採用暫存器來訪問,效率很高但是分配的記憶體容量有限。2 從堆上分配,亦稱動態記憶體分配。程式在執行時malloc或new任意...