函數棧幀的長度在編譯時被確定
❶ C/C++的實現問題
咱們一個問題問題來看:
1.主函數調用FUNCA時將EBP入棧問題:
鏈接器在最終鏈接所有目標文件生成可執行文件的時候,會尋找你的程序入口點main,main實際上會被運行時庫中的某個函數所調用(main並非是程序真正的入口點,真正的入口點在運行時庫中的某個函數,具體跟平台有關),所以在main函數中,首先保存原來的ebp值(push ebp),然後將原來的棧頂設為棧底(mov ebp, esp),到這里已經開辟一個新的棧幀。
為什麼沒有把esp入棧呢,因為esp已經保存到ebp中了,而ebp已經入棧了,如果要恢復以前的棧幀的話,只需要把ebp的值放入esp,從棧中彈出保存的ebp值到ebp寄存器即可,所以根本沒有保存esp的必要。你可以按這個解釋畫個草圖,一畫便知。x86下,棧是向小地址方向增長的,即壓棧會導致esp的值減小。
然後就是為局部變數分配存儲空間(sub esp,0x10),這里為何用減呢?原因上面已經說過,棧是向小地址方向增長的,所以應該用減。跟小尾序表示無關,小尾序僅僅代表數字在內存中的存儲布局。
2.其餘的問題
leave並非是一個偽指令,而是一個真正的x86指令。它相當於mov esp,ebp; pop ebp這兩條指令。前面已經說過,ebp中放的是上一個棧幀中esp的值,所以mov esp,ebp將恢復esp的值。因為esp這時已經和ebp一樣,故pop ebp彈出的恰好是已經保存的ebp的值。這樣,leave指令執行完畢後,main函數的棧幀就被銷毀,所以函數內部的局部變數也就被銷毀了。
如果局部變數加上了static限定符,該局部變數實際上並不是在當前函數的棧幀中,而是在初始化數據段里,該變數的地址是固定的,所以編譯器無須為該靜態變數分配棧空間,所有對該變數的引用都是通過一個固定地址來進行的。棧空間就是通過基址寄存器ebp和棧頂指針寄存器esp來限定的,故esp上移實際上導致了釋放出一部分空閑空間,因為在ebp到esp這段地址范圍內已經無法找到以前的臨時變數,故我們說變數已經被銷毀。
兄弟,看在哥哥寫這么長的份兒上,給分兒吧!
❷ 傳智的老師講c語言函數的棧幀調用過程講錯了吧大家進來看看
你的老師是沒說錯的。棧的規則是先進後出,後進先出。進入棧不代表會運行,它只是進入了1個運行的隊列。按照你的方法那這個棧基本沒意義了,因為你是進棧就出棧了,直接就運行了。並沒有一個隊列順序。
1、printf(n2)它最先進棧,所以它的排在隊列的最後面,它最後運行。這符合程序的運行順序
2、printf(n1)它第二進棧,所以它是倒數第二個運行的。以此類推下去。
3、如果按照你的說法,main最先進棧,你會發現它是在棧的底部,它無法最先運行。因為它上面還有你說的printf1,2,3,4,5等等。先出棧也是它們,意味著先運行也是它們。這和事實不符的。因為運行一個程序是從主函數開始運行的。
4、根據棧的規定,這里main應該是最後進棧的才對,因為它最後進棧,所以他最先出棧,也是它最先運行。
❸ 在進行函數調用時,函數的棧幀有什麼作用
棧的規則是先進後出,後進先出。進入棧不代表會運行,它只是進入了1個運行的隊列。按照你的方法那這個棧基本沒意義了,因為你是進棧就出棧了,直接就運行了。並沒有一個隊列順序。 1、printf(n2)它最先進棧,所以它的排在
❹ masm32問題,關於函數堆棧平衡
誰告訴你
在masm32中一個函數即使堆棧不平衡,在函數的末尾也有mov esp,ebp pop ebp進行平衡
的?
函數開始的push ebp是用來保存ebp寄存器(函數調用者棧幀的棧底)的,同時對與微軟系列的編譯器,局部變數採用ebp-X來訪問
mov edb,esp是函數的調用者的棧幀的棧頂作為一個新的棧幀的開始
兩行代碼之後
堆棧內容應該是這樣的
頂
調用者的棧幀底地址
該函數返回地址
底
現在我再來一句 push eax
此時堆棧變成這樣
頂
eax的內容
調用者的棧幀底地址
該函數返回地址
底
此時堆棧已經不平衡
然後我用你的mov esp,ebp pop ebp准備返回
請問能正確返回嗎?
當執行到ret的時候堆棧的情況是這樣的(甚至ebp的值都不能恢復)
頂
調用者的棧幀底地址
該函數返回地址
底
這樣直接返回到了函數開始的時候ebp所以位置了
你說這兩個代碼能保證函數的返回嗎?
函數要正確返回,堆棧必須平衡(排除你自己設置堆棧讓ret返回的情況)!
❺ C語言中,函數調用是通過棧實現的,怎樣理解這句話
在一個函數中調用另一個函數,會將當前狀態入棧,等執行完下一個後出棧,恢復狀態繼續往下執行
例:
int
myfun()//假如指向到此函數
{
...
myfun1();
//調用myfun1函數,那我之前的變數怎麼辦?入棧保存,然後跳轉到函數myfun1
....
}
int
myfun1()
{
myfun2();
//又要調用新的怎麼辦,入棧保存現有變數,然後進入myfun2
}
int
myfun2()
{
return
0;
//運行完myfun2,終於返回了。然後回到myfun1,保存的變數出棧,繼續執行myfun1
}
❻ 棧幀是什麼
棧幀也叫過程活動記錄,是編譯器用來實現過程或函數調用的一種數據結構。
C語言中,每個棧幀對應著一個未運行完的函數。棧幀中保存了該函數的返回地址和局部變數。
棧幀,顧名思義,就是棧中的一幀,棧分成很多幀,就如同一個視頻動作分成好多幀一樣。每個棧幀,對應一個函數,就是這個函數在棧中佔用的部分。
❼ C++申請釋放動態內存的問題
還是系統會記住之前早請這塊內存的信息,釋放時只要是這個指針,不管是什麼類型都正確釋放?
就是這樣。在windows下,new與malloc分配的內存都是在c++運行時庫函數用HeapCreate創建的堆上申請的。堆結構本身記錄了各塊的內存大小與類型
在執行delete或free時,內部調用HeapFree,你傳入的指針free(p);其中的p在free內部會經過(void *)的轉換,所以說free類似函數只關心p的數值而不關心p的類型。HeapFree可以根據p的值查詢堆管理器維護的數據結構來得到那塊內存的大小並釋放。
delete操作符也是類似。不過數組要用delete []p;,這不是說HeapFree需要知道p是個數組,這只是c++的特性,是高級語言規則。
---------------------------------------------------
還有int pa=new int[100]這樣形式的,delete (char*)pa會泄漏嗎?
不會泄漏,只是這不合章法。delete是操作符,你要按c++標准來運用它。雖然這在windows上可以,可是說不定在其他操作系統上不行。
正確的做法就是delete []pa;
--------------------------------------------------
ivaniren的實驗很有啟發性。
為了理解new及delete,觀察它們的代碼以及棧幀
以下是new的源碼:
void * operator new( unsigned int cb )
{
void *res = _nh_malloc( cb, 1 );
return res;
}
可見new是個操作符,_nh_malloc是一個內部函數沒有源碼,觀察棧上它的調用可知:_nh_malloc調用了_heap_alloc,而_heap_alloc又調用了RtlHeapAlloc,可見new操作符的本質還是HeapAlloc。至於new操作符會調用構造函數的問題,觀察ivaniren給出的代碼的匯編:
43: B * pb = new B;
004011D3 6A 04 push 4
004011D5 E8 C6 85 00 00 call operator new (004097a0)
004011DA 83 C4 04 add esp,4
004011DD 89 45 E0 mov dword ptr [ebp-20h],eax
004011E0 C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0
004011E7 83 7D E0 00 cmp dword ptr [ebp-20h],0
004011EB 74 0D je main+5Ah (004011fa)
004011ED 8B 4D E0 mov ecx,dword ptr [ebp-20h]
004011F0 E8 6F FE FF FF call @ILT+95(B::B) (00401064)
這段代碼先調用了operator new (004097a0),後調用了@ILT+95(B::B) (00401064),也就是構造函數。那麼程序自己怎麼知道是要調用B的構造函數呢?這是在編譯過程中編譯器根據new B來確定的,這就是說,對源碼中出現的new操作符,編譯器先計算出需要分配的大小,分配空間,再調用構造函數
對於delete,它的源碼是:
void operator delete( void * p )
{
free( p );
}
之所以它會調用析構函數,是因為和上述分析的new一個原因
關於ivaniren的問題
pba = new B[3];
delete pba; //~ 只調用了一個7構函數,是否釋放了內存,還有待高人解答
那麼我告訴你,內存釋放了,原因就是operator delete的源碼,但是析構函數卻沒有正確調用
---------------------------------------------------
在補充一點:
以下是類中delete操作符的一段匯編:
004016F0 E8 A6 F9 FF FF call @ILT+150(B::~B) (0040109b)
004016F5 8B 45 08 mov eax,dword ptr [ebp+8]
004016F8 83 E0 01 and eax,1
004016FB 85 C0 test eax,eax
004016FD 74 0C je B::`scalar deleting destructor'+3Bh (0040170b)
004016FF 8B 4D FC mov ecx,dword ptr [ebp-4]
00401702 51 push ecx
00401703 E8 88 14 00 00 call operator delete (00402b90)
可見,delete時與new相反,是先call @ILT+150(B::~B) (0040109b)調用析構函數
再call operator delete (00402b90)釋放內存。
所以對delete pba;這樣的代碼首先調用pba[0]的析構函數,然後再釋放3個B的內存空間
雖然是這么說,但是釋放3個B的內存空間時卻在_CRTIsValidHeapPoint上中止了,為什麼呢?
因為調式版本對delete有檢查,若delete的調用和new的調用不相符合,則會發生錯誤,在發行版本中則不會出錯
---------------------------------------------------
樓主,你領會上文要領了嗎?delete一般不會泄漏。但我剛才又想到了一種情況:某C++類在構造函數中調用malloc,在析構函數中調用free,那麼如果delete與new不匹配,雖然c++類的空間被釋放了,可是構造函數調用malloc申請的內存卻沒機會釋放。如果這樣的c++類被創造出一個大數組的數量又被錯誤的delete掉,那麼內存的損失將是巨大的。
所以最好的辦法還是遵守規則
--------------------------------
什麼我不見了!我因為修改答復所以到後面來了
❽ 關於c語言程序的,概念問題,這句話對么
一般的局部變數是保存在堆棧中函數結束後堆棧會被清空,局部變數也就不復存在了。
靜態的局部變數是保存在全局靜態區域的,即使函數結束他仍然存在。再次調用時的值是上一次調用後的值。只要主函數沒有結束他就一直存在。
我剛學的時候在這點上走了不少彎路給你推薦篇文章吧。看了你會明白很多的。
找了一圈沒找到。你到網上搜林銳的C/C++高質量編程,在內存管理那一章有詳細的介紹。
希望可以幫到你。
❾ 函數調用時,實參存儲在哪個函數的棧幀中
程序中,一個函數是一個過程,這個過程可以分為包括傳入參數、過程代碼、返回三部分構成。由於一個函數過程需要用到內部變數、臨時變數等,所以需要在進程空間的棧空間分配一段存儲片段來存儲函數過程中的這些參數,該內存片段即為棧幀。
棧幀的由來:
為一個函數的過程提供一個存儲函數局部變數,參數,返回地址和其他臨時變數;
棧幀的結構:
圖片來源(現代編譯原理)
棧幀的周期:
進入函數~函數返回,該階段內棧幀作為
不同的語言具體的實現方式略有不同,但是,總體上,fun(a,b);
局部變數:
包括函數傳入的形參和函數內部定義的變數;
返回地址:
指令指針P指向call fun,那麼fun棧幀存儲的返回地址為P+1;現今的編譯器的一個約定是將返回地址存到一個固定的寄存器中,這樣比讀取棧幀(內存)效率要高。
臨時變數:
主要為計算,運算過程中的中間臨時變數;
參數傳遞:
其一:如果fun中調用另一個函數k(a,b...n);那麼,傳遞的參數是形參,按照現代編譯規定,前k個形參是通過寄存器傳遞,後n-k個形參通過棧幀的實參部分(棧幀的尾部)來傳遞;
其二:主要為在fun中要調用函數g(&a,&b),那麼為g()函數傳出實參(形參是通過寄存器來傳遞的)是通過「傳出實參」區塊進行的,這么做主要是為了保證該實參能夠被內層嵌套的函數訪問。比如,g函數由調用一個k(a地址)函數,同樣需要用到a的地址,所以fun在傳遞參數時必須為實參(&a)傳遞申請固定的內存存儲空間(而非用寄存器)這樣k函數可以通過固定的內存地址(fun的形參列表來獲取參數值)。這時的g的棧幀為fun棧幀的下一幀(相鄰的空間地址),即調用者和被調用者的棧幀是相連的;
保護的寄存器:
棧幀作為函數過程的一個臨時內存存儲區塊,同時負責函數調用過程中寄存器值的保存和還原。即:假設fun函數目前佔用了寄存器Ri存儲一個臨時變數t,而此時調用了函數g(),在g()函數中可能需要用到寄存器Ri做運算的臨時存儲,那麼如何確保g()函數調用返回後,Ri恢復到fun中t的原來值。這就需要在調用者或者被調用者中選擇其一來保存原有Ri的值,即caller-save或者callee-save。
最後一個問題:進程空間中棧幀的體現是什麼?
進程的棧空間 & 棧幀的增長:
調用函數和被調用函數間的棧幀是相鄰的,即如果進程進入一個遞歸函數f(),遞歸k層。那麼在第k層嵌套時,進程的的棧空間已產生出新的k個連續的f()函數的棧幀,當然每個棧幀的內存儲的變數值是因函數過程而定的。
❿ "棧"和"棧幀"這兩個概念到底如何區分
1、棧:FILO先進後出的數據結構
棧底是第一個進棧的數據的位置(壓箱底)
棧頂是最後一個進棧的數據位置
2、根據SP指針指向的位置,棧可分為滿棧和空棧
滿棧:當sp指針總是指向最後壓入堆棧的數據(ARM採用滿棧)
棧的作用:
1)保存局部變數
分析代碼:
[html]view plain
#include<stdio.h>
intmain()
{
inta;
a++;
returna;
}</span>
stack:fileformatelf32-littlearm
Disassemblyofsection.text:
00000000<main>:
#include<stdio.h>
intmain()
{
0:e52db004push{fp};(strfp,[sp,#-4]!)@將棧幀底部指針FP壓入棧中;創建屬於main函數的棧幀。
4:e28db000addfp,sp,#0;0x0@fp指針為函數棧幀的底部,
8:e24dd00csubsp,sp,#12;0xc@sp指針為棧幀的頂部,同時為棧的棧頂。
inta;
a++;
c:e51b3008ldrr3,[fp,#-8]@由此三句可知變數a在棧幀中執行了加法操作,及棧幀具有保存局部變數的作用
10:e2833001addr3,r3,#1;0x1
14:e50b3008strr3,[fp,#-8]
returna;
18:e51b3008ldrr3,[fp,#-8]
}
</span>
<spanstyle="font-size:18px;">#include<stdio.h>
voidfunc1(inta,intb,intc,intd,inte,intf)
{
intk;
k=e+f;
}
intmain()
{
func1(1,2,3,4,5,6);
return0;
}
反匯編之後的代碼;
voidfunc1(inta,intb,intc,intd,inte,intf)@多於4個參數
{
0:e52db004push{fp};(strfp,[sp,#-4]!)@保存main函數的棧幀底部指針FP
4:e28db000addfp,sp,#0;0x0
8:e24dd01csubsp,sp,#28;0x1c@由棧幀頂部指針SP創建一片棧幀保存子函數的前四個參數
c:e50b0010strr0,[fp,#-16]@a
10:e50b1014strr1,[fp,#-20]@b
14:e50b2018strr2,[fp,#-24]@c
18:e50b301cstrr3,[fp,#-28]@d
intk;
k=e+f;
1c:e59b3004ldrr3,[fp,#4]@在子函數的棧幀中實現第五個參數與第六個參數的運算
20:e59b2008ldrr2,[fp,#8]@由ldrr2,[fp,#8]知參數保存在main函數的棧幀中,並運算
24:e0833002addr3,r3,r2@以子函數的棧幀底部指針(fp)做參考坐標實現對參數的查找
28:e50b3008strr3,[fp,#-8]
}
2c:e28bd000addsp,fp,#0;0x0
30:e8bd0800pop{fp}
34:e12fff1ebxlr
00000038<main>:
intmain()
{
38:e92d4800push{fp,lr}@由於調用子函數,先保存main函數的棧幀底部指針FP和返回地址LR(當前PC指針的下一地址)
3c:e28db004addfp,sp,#4;0x4@可知先壓入FP,後壓入lr.把此時子函數(被調用者)的棧幀底部指針FP指向保存在子函數棧幀的main函數(調用者)的棧幀底部指針FP
40:e24dd008subsp,sp,#8;0x8@創建棧
func1(1,2,3,4,5,6);
44:e3a03005movr3,#5;0x5
48:e58d3000strr3,[sp]
4c:e3a03006movr3,#6;0x6
50:e58d3004strr3,[sp,#4]
54:e3a00001movr0,#1;0x1@用通用寄存器保存前四個參數的值
58:e3a01002movr1,#2;0x2
5c:e3a02003movr2,#3;0x3
60:e3a03004movr3,#4;0x4
64:ebfffffebl0<func1>
return0;
68:e3a03000movr3,#0;0x0
}
6c:e1a00003movr0,r3
70:e24bd004subsp,fp,#4;0x4
74:e8bd4800pop{fp,lr}
78:e12fff1ebxlr</span>
<spanstyle="font-size:18px;">include<stdio.h>
voidfunc2(inta,intb)
{
intk;
k=a+b;
}
voidfunc1(inta,intb)
{
intc;
func2(3,4);
c=a+b;
}
intmain()
{
func1(1,2);
return0;
}</span>
<spanstyle="font-size:18px;">voidfunc2(inta,intb)
{
0:e52db004push{fp};(strfp,[sp,#-4]!)
4:e28db000addfp,sp,#0;0x0
8:e24dd014subsp,sp,#20;0x14
c:e50b0010strr0,[fp,#-16]@保存寄存器的值
10:e50b1014strr1,[fp,#-20]
intk;
k=a+b;
14:e51b3010ldrr3,[fp,#-16]
18:e51b2014ldrr2,[fp,#-20]
1c:e0833002addr3,r3,r2
20:e50b3008strr3,[fp,#-8]
}
24:e28bd000addsp,fp,#0;0x0
28:e8bd0800pop{fp}
2c:e12fff1ebxlr
00000030<func1>:
voidfunc1(inta,intb)
{
30:e92d4800push{fp,lr}
34:e28db004addfp,sp,#4;0x4
38:e24dd010subsp,sp,#16;0x10
3c:e50b0010strr0,[fp,#-16]@代碼44行調用func2函數後,又使用r0 1保存參數,所以此時將r0 1寄存器的
40:e50b1014strr1,[fp,#-20]@值放入棧中
intc;
func2(3,4);
44:e3a00003movr0,#3;0x3
48:e3a01004movr1,#4;0x4
4c:ebfffffebl0<func2>
c=a+b;
50:e51b3010ldrr3,[fp,#-16]
54:e51b2014ldrr2,[fp,#-20]
58:e0833002addr3,r3,r2
5c:e50b3008strr3,[fp,#-8]
}
60:e24bd004subsp,fp,#4;0x4
64:e8bd4800pop{fp,lr}
68:e12fff1ebxlr
0000006c<main>:
intmain()
{
6c:e92d4800push{fp,lr}
70:e28db004addfp,sp,#4;0x4
func1(1,2);
74:e3a00001movr0,#1;0x1
78:e3a01002movr1,#2;0x2
7c:ebfffffebl30<func1>
return0;
80:e3a03000movr3,#0;0x0
}
84:e1a00003movr0,r3
88:e24bd004subsp,fp,#4;0x4
8c:e8bd4800pop{fp,lr}
90:e12fff1ebxlr</span>
反匯編之後的代碼;
[html]view plain
2)保存函數的參數
分析代碼:
[html]view plain
註:C中,若函數的參數小於等於4個,則用通用寄存器保存其參數值,多於4個的參數保存在棧中
3)保存寄存器的值
分析代碼:
[html]view plain
反匯編之後的代碼;
[html]view plain
初始化棧:即對SP指針賦予一個內存地址(統一標准:2440、6410、210)
在內存的64MB位置即ldr sp, =0x34000000(2440)
ldr sp, =0x54000000(6410)
ldr sp, =0x24000000(210)
由上可知ARM採用滿棧(指向剛入棧的數據)、降棧(由高地址向低地址入棧)
問題:因為ARM不同工作模式有不同的棧,定義棧的技巧是什麼,避免定義相同的地址使用不同棧?
轉自:http://blog.csdn.net/u011467781/article/details/39559737