1. 程式人生 > >堆疊、棧幀與函式呼叫過程分析

堆疊、棧幀與函式呼叫過程分析

函式呼叫是程式設計中的重要環節,也是程式設計師應聘時常被問及的,本文就函式呼叫的過程進行分析。

一、堆和棧

首先要清楚的是程式對記憶體的使用分為以下幾個區:

l         棧區(stack):由編譯器自動分配和釋放,存放函式的引數值,區域性變數的值等。操作方式類似於資料結構中的棧。

l         堆區(heap):一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時可能由作業系統回收。與資料結構中的堆是兩碼事,分配方式類似於連結串列。

l         全域性區(static):全域性變數和靜態變數存放在此。

l         文字常量區:常量字串放在此,程式結束後由系統釋放。

l         程式程式碼區:存放函式體的二進位制程式碼。

典型的記憶體區域分配如圖所示:

其次是堆和棧的申請方式:

棧由系統自動分配,速度較快,在windows下棧是向低地址擴充套件的資料結構,是一塊連續的記憶體區域,大小是2MB。

堆需要程式設計師自己申請,並指明大小,速度比較慢。在C中用malloc,C++中用new。另外,堆是向高地址擴充套件的資料結構,是不連續的記憶體區域,堆的大小受限於計算機的虛擬記憶體。因此堆空間獲取和使用比較靈活,可用空間較大。

二、棧幀結構和函式呼叫過程

棧在函式呼叫中的作用:引數傳遞、區域性變數分配、儲存呼叫的返回地址、儲存暫存器以供恢復。

棧幀(stack Frame):一次函式呼叫包括將資料和控制從程式碼的一個部分傳遞到另外一個部分,棧幀與某個過程呼叫一一對映。每個函式的每次呼叫,都有它自己獨立的一個棧幀,這個棧幀中維持著所需要的各種資訊。暫存器ebp指向當前的棧幀的底部(高地址),暫存器esp指向當前的棧幀的頂部(低址地)。

函式呼叫規則:

l         _cdecl:按從右至左的順序壓引數入棧,由呼叫者把引數彈出棧。由於每次函式呼叫都要由編譯器產生清楚堆疊的程式碼,所以使用_cdecl的程式碼比使用_stdcall的程式碼要大很多,但是這種方式支援可變引數。對於C函式,名字修飾約定為在函式名前加下劃線。對於C++,除非特變使用extern C,C++使用不同的名字修飾方式。

l         _stdcall:按從右至左的順序壓引數入棧,由被呼叫者把引數彈出棧。呼叫約定在輸出函式名前加上一個下劃線字首,後面加上一個“@”符號和其引數的位元組數。

l         _fastcall:主要特點就是快,因為它是通過暫存器來傳送引數的,和__stdcall很象,唯一差別就是頭兩個引數通過暫存器傳送。注意通過暫存器傳送的兩個引數是從左向右的,即第一個引數進ECX,第2個進EDX,其他引數是從右向左的入stack。返回仍然通過EAX。
最後,以一個例子來解釋函式呼叫過程

void func(int param1 ,int param2,int param3)

{

       int var1 = param1;

       int var2 = param2;

       int var3 = param3;

       printf("param1地址:0X%08X/n",&param1);

       printf("param2地址:0X%08X/n",&param2);

       printf("param3地址:0X%08X/n",&param3);

       printf("var1地址:  0X%08X/n",&var1);

       printf("var2地址:  0X%08X/n",&var2);

       printf("var3地址:  0X%08X/n",&var3);

}

int main(int argc, char* argv[])

{

       func(1,2,3);

       return 0;

}

執行結果如圖:

下面分析呼叫過程:

在堆疊中變數分佈是從高地址到低地址分佈,EBP是指向棧底的指標,在過程呼叫中不變,又稱為幀指標。ESP指向棧頂,程式執行時移動,ESP減小分配空間,ESP增大釋放空間,ESP又稱為棧指標。3個引數以從左向右的順序壓入堆疊,及從param3到param1,棧內分佈如下圖:

然後是返回地址入棧:此時的棧內分佈如下:

通過跳轉指令進入函式後,函式地址入棧後,EBP入棧,然後把當前ESP的值給EBP,彙編指令如下:

push ebp

mov ebp esp

此時棧頂和棧底指向同一位置,棧內分佈如下:

然後是    int var1 = param1; int var2 = param2; int var3 = param3;按申明順序依次儲存。

相關推薦

堆疊函式呼叫過程分析

函式呼叫是程式設計中的重要環節,也是程式設計師應聘時常被問及的,本文就函式呼叫的過程進行分析。 一、堆和棧 首先要清楚的是程式對記憶體的使用分為以下幾個區: l         棧區(stack):由編譯器自動分配和釋放,存放函式的引數值,區域性變數的值等。操作方式類似於資料結構中的棧。 l        

ARM函式呼叫過程分析

1.  ARM的棧幀     先來看看ARM的棧幀佈局圖:          上圖描述的是ARM的棧幀佈局方式,main stack frame為呼叫函式的棧幀,func1 stack frame為當前函式(被呼叫者)的棧幀,棧底在高地址,棧向下增長。圖中FP就是棧基址,

x86 子函式呼叫過程分析

工作系統:深度作業系統 15.1 工作環境: gcc version 5.3.1 20160114 (Debian 5.3.1-6) GN

x86_64架構下函式呼叫過程分析

//被分析的C程式 int test1(int a1,int b1) { int c1; c1 = a1+b1; return c1; } int test2(int a2,char b2) { int c2; c2 =

【18.7.27】函式呼叫過程的深度理解()

函式可以大大減少我們程式的程式碼量,使程式碼寫起來更加的簡潔,使程式碼思路更加清晰。是我們程式猿在編寫程式碼時必不可少的。 那麼函式的呼叫過程就非常的重要,今天讓我們來從一個深的角度去了解一下函式的呼叫過程。 函式的呼叫過程也可以叫做棧幀 棧幀的定義是:棧幀也叫過程活動記錄,

C++基礎知識:c 函式呼叫過程原理及函式分析

1.關於棧   首先必須明確一點也是非常重要的一點,棧是向下生長的,所謂向下生長是指從記憶體高地址->地地址的路徑延伸,那麼就很明顯了,棧有棧底和棧頂,那麼棧頂的地址要比棧底低。對x86體系的CPU而言,其中   ---> 暫存器ebp(base pointer

函式呼叫過程

函式呼叫過程詳解原始碼#include<stdio.h> int add(int x,int y) { int c=x+y; return c; } int main() { int a=0xaaaaaaaa; int b=0xbbbbbbbb; in

C++ 函式呼叫過程的變化解析

“ 走好選擇的路,別選擇好走的路,你才能擁有真正的自己。” There you go again! I'll back you up! 記錄下函式呼叫的情況~ 函式呼叫的另一個詞語表示叫作 過程。一個過程呼叫包括將資料和控制從程式碼的一部分傳遞到另一部分。

c語言函式呼叫過程的工作原理理解

差不多每個程式設計師都知道,函式呼叫過程,就是層層入棧出棧的過程。 那麼這個過程中的詳細的細節是什麼樣子的呢? 閱讀了以下幾篇文章之後,對整個過程基本理解了: C函式呼叫過程原理及函式棧幀分析 閱讀經典——《深入理解計算機系統》04 函式返回值與棧 針對自己的理解,做個記錄:

堆疊函式呼叫

1) 在棧上建立。在執行函式時,函式內區域性變數的儲存單元都在棧上建立,函式執行結束時這些儲存單元自動被釋放。棧記憶體分配運算內置於處理器的指令集中,一般使用暫存器來存取,效率很高,但是分配的記憶體容量有限。 2) 從堆上分配,亦稱動態記憶體分配。程式在執行的時候用malloc或new申請任意多少的記憶體,

X86架構上函式呼叫過程堆疊

   理解呼叫棧最重要的兩點是:棧的結構,EBP暫存器的作用。首先要認識到這樣兩個事實:  1、一個函式呼叫動作可分解為:零到多個PUSH指令(用於引數入棧),一個CALL指令。CALL指令內部其實還暗含了一個將返回地址(即CALL指令下一條指令的地址)壓棧的動作。  2、幾

C/C++函式呼叫過程--函式(二)

函式呼叫大家都不陌生,呼叫者向被呼叫者傳遞一些引數,然後執行被呼叫者的程式碼,最後被呼叫者向呼叫者返回結果,還有大家比較熟悉的一句話,就是函式呼叫是在棧上發生的,那麼在計算機內部到底是如何實現的呢? 對於程式,編譯器會對其分配一段記憶體,在邏輯上可以分為程式碼段,資料段,堆,棧 程式碼段:儲存程式文字,指

函式呼叫過程中,函式引數的入順序

      函式呼叫過程中,第一個進棧的是(主函式中的)呼叫處的下一條指令(即函式呼叫語句的下一條可執行語句)的地址;然後是函式的各個引數,而在大多數C/C++編譯器中,在函式呼叫的過程中,函式的引數是由右向左入棧的;然後是函式內部的區域性變數(注意static變數是不入

函式呼叫過程函式詳解

當程序被載入到記憶體時,會被分成很多段 程式碼段:儲存程式文字,指令指標EIP就是指向程式碼段,可讀可執行不可寫,如果發生寫操作則會提示segmentation fault 資料段:儲存初始化的全域性變數和靜態變數,可讀可寫不可執行 BSS:未初始化的全域性變數

小例子一步一步解釋“函式呼叫過程的變化過程

1 問題描述   在此之前,我對C中函式呼叫過程中棧的變化,僅限於瞭解有好幾種引數的入棧順序,其中的按照形參逆序入棧是比較常見的,也僅限於瞭解到這個程度,但到底在一個函式A裡面,呼叫另一個函式B的過程中,函式A的棧是怎麼變化的,實參是怎麼傳給函式B的,函式B又是怎麼給函式A返回值的,這些問題都不能很明白的

C++程式碼反彙編後的函式呼叫過程堆疊暫存器EBP和ESP

棧是從高地址向低地址生長的。  ebp始終指向當前棧幀的棧底部 , 通過ebp+4中儲存著函式的返回地址 。函式返回時將EBP的值推給EIP ,返回到上一個函幀繼續執行。 ret 與call指令 相反 ,call 將EIP壓入堆疊,然後跳到標號處。 ret 8  在函式返回

高階語言反彙編程式的函式呼叫過程

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

【Java虛擬機器】和方法呼叫

棧幀和方法呼叫 執行時棧幀結構 區域性變量表 運算元棧 動態連線 返回地址 方法呼叫 解析 分派 靜態分派 動態分配 虛擬機器動態分配的實現

深入理解C語言的函式呼叫過程

    本文主要從程序棧空間的層面複習一下C語言中函式呼叫的具體過程,以加深對一些基礎知識的理解。     先看一個最簡單的程式:   點選(此處)摺疊或開啟  /*test.c*/ #include <stdio.h> int foo1(

【c語言】巨集(#define#和##)函式比較

#define -定義識別符號 ef:#define在預處理階段替代所有的Max #define Max 100 int main() { printf("%d\n", Max); system("pause"); return 0