- </pre><pre name="code" class="cpp">#include <stdio.h>
-
- int fun2(int x, int y, int z)
- {
- int i = x + y;
- int j = y + z;
- int k = i + j;
- return k;
- }
-
- int fun1(int a, int b)
- {
- int c = a + b;
- int d = 0;
- d = fun2(a, b, c);
- return d;
- }
-
- int main()
- {
- int num1 = 10;
- int num2 = 20;
-
- fun1(num1, num2);
- return 0;
- }
以上邊代碼為例,main函數(shù)調(diào)用了fun1函數(shù), 而fun1函數(shù)又調(diào)用了fun2函數(shù)。在調(diào)用過(guò)程中??臻g的變化如下
在fun1調(diào)用前 目前是main函數(shù)的??臻gnum1變量已經(jīng)賦值為10, num2變量賦值為20;
ESP 是棧指針寄存器這個(gè)寄存器中存儲(chǔ)著棧頂?shù)牡刂贰?EBP中存儲(chǔ)著棧底的地址。 函數(shù)??臻g主要是由這兩個(gè)寄存器來(lái)確定
main函數(shù)調(diào)用fun1函數(shù)時(shí),第1
步操作就是把傳入的參數(shù)壓入到棧中。 C語(yǔ)言使用的是_cdecl調(diào)用方式,參數(shù)從右向左依次壓入棧中
所以實(shí)參num2的數(shù)值被復(fù)制到了形參b所在的內(nèi)存中
接著又將實(shí)參num1的數(shù)值復(fù)制到了形參a所在的內(nèi)存中。ESP的數(shù)值也隨著不斷減小以指向棧頂
調(diào)用函數(shù)結(jié)束后都會(huì)返回到代碼所調(diào)用行的下一行繼續(xù)執(zhí)行。那么他是怎樣知道返回后要繼續(xù)執(zhí)行哪塊地址上的命令呢。參數(shù)壓入??臻g后,接下來(lái)的工作就是保存被調(diào)用函數(shù)返回后要執(zhí)行指令的地址。也就是保存到當(dāng)前ESP所指向的??臻g中
再接下來(lái),保存當(dāng)前函數(shù)的棧底到ESP所指向內(nèi)存中去。
提升棧底,此時(shí) ESP 和EBP都指向相同地址
ESP減8,
我們已經(jīng)為fun1的兩個(gè)形參分配完了內(nèi)存空間。通過(guò)代碼我們看到,fun1中有兩個(gè)局部變量c和d 。所以ESP減8的目的就是為我們的局部變量分配空間
而在fun1函數(shù)中我們又調(diào)用了fun2函數(shù),fun2函數(shù)的調(diào)用過(guò)程與fun1類(lèi)似
在fun2函數(shù)中,k運(yùn)算得80;
函數(shù)返回語(yǔ)句 return k;我們要返回k的值,可是當(dāng)我們退出函數(shù)后,顯然fun2的??臻g已經(jīng)不再有效,那么他是怎么把這個(gè)80傳遞到fun1中去的呢
計(jì)算機(jī)中存儲(chǔ)數(shù)據(jù)的不僅僅有內(nèi)存。CPU中也有若干用來(lái)存儲(chǔ)數(shù)據(jù)的空間,稱(chēng)之為寄存器。所以在函數(shù)退出之前都會(huì)把結(jié)果保存到這些寄存器當(dāng)中。32位CPU的通用寄存自然最多也只有32位了,64位CPU的通用寄存器最多也只有64位。所以函數(shù)參數(shù)傳遞通常為基本數(shù)據(jù)類(lèi)型或指針。因?yàn)檫@些數(shù)據(jù)的寬度都沒(méi)有超過(guò)寄存器的最大位寬。當(dāng)我們向函數(shù)中傳遞一個(gè)結(jié)構(gòu)體或類(lèi)時(shí),這個(gè)過(guò)程是一個(gè)數(shù)據(jù)復(fù)制的過(guò)程,如果結(jié)構(gòu)體或類(lèi)成員較多,復(fù)制過(guò)程肯定會(huì)消耗更多的空間和時(shí)間。當(dāng)返回一個(gè)結(jié)構(gòu)體或類(lèi)對(duì)象時(shí),同樣會(huì)產(chǎn)生數(shù)據(jù)復(fù)制的過(guò)程。所以編程中通常是不會(huì)這樣做的,而只需要傳遞一個(gè)結(jié)構(gòu)體指針或類(lèi)對(duì)象地址。只需要簡(jiǎn)單傳遞32位或64位的地址,一切問(wèn)題都可以得到解決。提高程序運(yùn)行效率,節(jié)省空間。這也是指針之所以強(qiáng)大高效的原因之一。
函數(shù)調(diào)用??臻g變化 我們已經(jīng)基本了解了,接下來(lái)再看下,函數(shù)退出時(shí)棧的變化
ESP-0xC 此時(shí)ESP和EBP指向相同地址,當(dāng)前地址內(nèi)存中存放的是前一個(gè)函數(shù)的EBP地址
恢復(fù)原來(lái)EBP的數(shù)值
恢復(fù)EIP的值為00401232 也就是告訴CPU回到調(diào)用fun2函數(shù)之前的函數(shù)中繼續(xù)執(zhí)行下一行代碼
徹底恢復(fù)到fun1的棧空間。 fun2的棧空間不再有效。 函數(shù)退出后原來(lái)使用的數(shù)值計(jì)算機(jī)并沒(méi)有做多余的回收工作。just leave
it alone.
所以你不應(yīng)該返回一個(gè)函數(shù)局部變量的指針。當(dāng)退出函數(shù)后這部分空間是不可控制的。因?yàn)樵诤筮叺拇a很 可能又調(diào)用了其它函數(shù),重新使用了fun2所使用過(guò)的??臻g。這時(shí)你用指針操作這塊空間得到的是一個(gè)未知的數(shù)值。這個(gè)數(shù)值是不確定的。也就行成了野指針。還有局部變量的作用域也是由于這個(gè)原因。局部變量和參數(shù)只在當(dāng)前函數(shù)內(nèi)有效。退出后就無(wú)法也不應(yīng)該再繼續(xù)使用。
還記得
大明湖畔的 return k嗎? d = fun2(a, b, c); 相當(dāng)于 d = k;
這個(gè)時(shí)候不再是把k的值復(fù)制給d了,fun2函數(shù)已經(jīng)退出,同樣他的??臻g不再有效。在 fun2退出時(shí)k的值是復(fù)制到了寄存器eax中的,所以這時(shí)的d的數(shù)值是從寄存器eax中取得的。
接下來(lái)fun1運(yùn)行完成退出過(guò)程類(lèi)似fun2
|