棧溢出

棧溢出

棧溢出是由於C語言系列沒有內置檢查機制來確保複製到緩衝區的數據不得大於緩衝區的大小,因此當這個數據足夠大的時候,將會溢出緩衝區的範圍。 在Python中,函式調用是通過棧(stack)這種數據結構實現的,每當進入一個函式調用,棧就會加一層棧幀,每當函式返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。

定義

棧溢出就是緩衝區溢出的一種。 由於緩衝區溢出而使得有用的存儲單元被改寫,往往會引發不可預料的後果。程式在運行過程中,為了臨時存取數據的需要,一般都要分配一些記憶體空間,通常稱這些空間為緩衝區。如果向緩衝區中寫入超過其本身長度的數據,以致於緩衝區無法容納,就會造成緩衝區以外的存儲單元被改寫,這種現象就稱為緩衝區溢出。緩衝區長度一般與用戶自己定義的緩衝變數的類型有關。

棧溢出就是緩衝區溢出的一種。

在pascal語言中,棧溢出的錯誤代碼為202號錯誤。

在Python中,函式調用是通過棧(stack)這種數據結構實現的,每當進入一個函式調用,棧就會加一層棧幀,每當函式返回,棧就會減一層棧幀。由於棧的大小不是無限的,所以,遞歸調用的次數過多,會導致棧溢出。

例子如下:

def fact(n):

if n==1:

return 1

return n * fact(n - 1)

>>> fact(1000)

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

File "<stdin>", line 4, in fact

...

File "<stdin>", line 4, in fact

RuntimeError: maximum recursion depth exceeded in comparison

解決方法

解決遞歸調用棧溢出的方法是通過尾遞歸最佳化,事實上尾遞歸和循環的效果是一樣的,所以,把循環看成是一種特殊的尾遞歸函式也是可以的。

尾遞歸是指,在函式返回的時候,調用自身本身,並且, return語句不能包含表達式。這樣,編譯器或者解釋器就可以把尾遞歸做最佳化,使遞歸本身無論調用多少次,都只占用一個棧幀,不會出現棧溢出的情況。

如上棧溢出例子,由於fact(n)函式return n * fact(n - 1)引入了乘法表達式,所以就不是尾遞歸了。要改成尾遞歸方式,需要多一點代碼,主要是要把每一步的乘積傳入到遞歸函式中:

def fact(n):

return fact_iter(n, 1)

def fact_iter(num, product):

if num == 1:

return product

return fact_iter(num - 1, num * product)

可以看到,return fact_iter(num - 1, num * product)僅返回遞歸函式本身,num - 1和num * product在函式調用前就會被計算,不影響函式調用。尾遞歸調用時,如果做了最佳化,棧不會增長,因此,無論多少次調用也不會導致棧溢出。

性質

由於緩衝區溢出而使得有用的存儲單元被改寫,往往會引發不可預料的後果。向這些單元寫入任意的數據,一般只會導致程式崩潰之類的事故,對這種情況我們也至多說這個程式有bug。但如果向這些單元寫入的是精心準備好的數據,就可能使得程式流程被劫持,致使不希望的代碼被執行,落入攻擊者的掌控之中,這就不僅僅是bug,而是漏洞(exploit)了。

相關詞條

相關搜尋

熱門詞條

聯絡我們