簡體   English   中英

堆棧是否在需要時分配更多空間,還是會溢出?

[英]Does a stack allocate more space if needed or does it overflow?

對於x86組裝,假設我們有一個像這樣的堆棧 在此處輸入圖片說明

堆棧為它擁有的2個局部變量分配了2個字。 但是,如果您強行將第三個局部變量推入堆棧,該怎么辦? ESP是否向上移動以為變量留出空間,還是變量會覆蓋ESP?

使用x86,將數據“推入”堆棧的指令還會修改堆棧指針(在本例中為%esp )以標記新的堆棧頂部。 “彈出”數據的指令沿相反方向修改堆棧指針。

在沒有特殊推入和彈出指令的機器上,程序必須首先修改堆棧指針,然后將數據存儲到堆棧中。

通常,為堆棧保留較大的空間。 堆棧指針僅標記當前正在使用的部分。 程序可以根據需要隨意上下移動堆棧指針。

為堆棧保留的區域可能取決於操作系統和/或開發人員工具。 例如,在具有Apple開發人員工具的macOS上,默認堆棧大小為8兆字節,可以通過將“ -stack_size size ”切換到鏈接器( ld命令)進行更改。 (這是針對主堆棧的。使用多個線程的程序為每個創建的線程都有一個額外的堆棧。這些線程的堆棧大小是分別設置的。)

盡管為堆棧保留了大范圍的虛擬地址空間,但操作系統可能不會在程序啟動后立即將其全部映射到物理內存。 操作系統可能只映射一部分,然后隨着堆棧增長到該區域映射更多的部分。

通常,超出堆棧的虛擬地址空間的某些部分保持未映射狀態,因此嘗試訪問它會導致異常。 該區域中的地址空間頁面稱為保護頁面。 因此,如果程序使堆棧超出保留區域並嘗試將值寫入未映射的保護頁,則會發生異常,並且系統將報告堆棧溢出。

沒有什么可以阻止程序寫入為堆棧保留但略微超出堆棧指針的區域。 這將是一個錯誤,但是通常不會被硬件檢測到。 此外,執行此操作的程序可能會正常運行一段時間。 它可以將數據存儲到該區域並按預期加載回去。 但是,您在過程中還會發生其他通常不知道的事情。 例如,信號可能會傳遞給您的過程。 發生這種情況時,系統會中斷程序的常規處理,將新數據壓入堆棧,並調用信號處理程序例程。 當例程返回時,將從堆棧中刪除數據,並且程序將恢復正常執行。 但是,如果您的程序已將數據存儲在堆棧指針之外,則該數據現在消失了,因為它已被信號處理程序的數據覆蓋。 因此,將數據存儲在堆棧指針之外的程序在大多數情況下似乎都可以運行,但是在信號到達錯誤時刻的極少數情況下會失敗。

(在某些系統上,堆棧的安全區域實際上是超出堆棧指針中地址的固定距離,而不是恰好位於該地址處。這個額外的安全空間可以稱為“紅色區域”。)

有關大多數詳細信息,請參見@Eric的答案。

有些操作系統不僅懶得立即將整個保留的堆棧區域實際映射到物理頁面,有些甚至根本沒有邏輯映射它。 例如,在Linux上, /proc/self/maps中的堆棧映射的大小小於ulimit -s值。 但是觸摸該區域中的內存將導致內核擴展映射(達到最大大小限制),即使它遠低於當前映射的末尾。

這與通常的懶惰映射分開,在懶惰映射中,新分配的mmap(MAP_ANONYMOUS)區域的所有頁面都在寫時復制映射到了相同的物理頁面(全部為零)。 因此,讀取新頁面可能會給您帶來TLB丟失(要在該表中查找該虛擬地址的信息)和L1D高速緩存命中(因為所有仍然寫的頁面的物理地址都相同) 1

在其他操作系統(例如Windows)上,您不能一jump而就。 如果訪問位於當前映射的最低地址頁面的幾個頁面之內,則內核的頁面錯誤處理程序將僅為您映射新的堆棧內存。 (它也可以檢查ESP / RSP是否在故障地址之下)。 如果這些檢查中的任何一個失敗,頁面錯誤都會導致程序異常,而不是通過靜默映射該頁面並重新運行裝入或存儲指令來處理。

這意味着在堆棧上為大型陣列分配空間必須在需要它的操作系統上每頁左右探測堆棧 微軟為其Visual Studio編譯器提供的文檔中包含一些詳細信息: /Gs選項默認為/Gs4096 :當大量增加堆棧時,或每4k至少探測一次,或者對於可能大小那么大的可變大小的數組進行探測。

“探針”沒什么特別的,只是加載或存儲到堆棧地址以觸發頁面錯誤(如果尚未分配頁面),因此堆棧一次增加一頁而不是出錯。


腳注

  1. malloccalloc使用mmap ,而calloc知道mmap會將頁面清零,因此當它從內核獲得新的內存時,它不會重做清零。

    但是C ++ std::vector太笨了(在gcc和clang中,使用libc++libstdc++ ),並且即使在new最終調用mmap大型分配中,也會弄臟所有新的內存本身。 C ++的可替換new意味着編譯器/庫只能通過-fwhole-program或鏈接時優化來對此進行優化,但是從理論上講這是可能的。 由於實際上不會發生這種情況,因此,如果此行為有用(例如,僅寫一部分的稀疏分配),則使用自定義分配器或避免使用std::vector

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM