區域性變數太大導致棧溢位

2021-07-15 02:30:33 字數 3650 閱讀 2927

問題:

昨天,有同學遇到棧溢位的問題。在做大三小學期專案時,需要乙個750x750的矩陣。於是在棧中定義了乙個二維陣列。為了說明問題,做如下簡化:

/*

測試環境:

window平台 vs2013

*/int main()

這看似沒有問題,定義了乙個變數,不大,才4.5m左右。可是,當執行時出現了棧溢位。什麼情況?

編譯器和作業系統背後的原理:

作業系統為例實現對使用者程式的管理,使用程序+執行緒來執行我們的程式。涉及到執行緒,必須得考慮作業系統和編譯器。

作業系統,例如linux,使用vm_area_struct結構體來管理使用者空間,在載入乙個elf或其他格式的可執行檔案時總是會參考檔案中給出的資訊來設定這個結構體中的內容。其中**和唯讀資料段可能就由同乙個vm_area_struct來管理、棧區由另乙個vm_area_struct來管理、堆區也有乙個vm_area_struct、共享區有乙個vm_area_struct、對映檔案有自己vm_area_struct。所有的vm_area_struct按位址大小連成乙個vm_area_struct鍊錶。如果vm_area_struct過多,貌似超過32個時,作業系統就會為其建立乙個紅黑樹,加快查詢過程。

task_struct中有乙個陣列rlim用來記錄乙個對程序分配資源的限制,陣列的第rlimit_strack項記錄了棧的大小限制。

struct task_struct

struct signal_struct

struct rlimit

struct rlimit ;
init_task

那麼,所有程序的rlimit陣列什麼時候被設定呢?由使用者建立的每個程序都繼承其父程序rlimit陣列的內容。

在系統靜態的建立第乙個核心程序時,為idle[init_task]程序靜態的初始化了乙個rlimit陣列:

#define init_rlimits                            \

, \ [rlimit_fsize] = , \

[rlimit_data] = , \

[rlimit_stack] = , \

[rlimit_core] = , \

[rlimit_rss] = , \

[rlimit_nproc] = , \

[rlimit_nofile] = , \

[rlimit_memlock] = , \

[rlimit_as] = , \

[rlimit_locks] = , \

[rlimit_sigpending] = , \

[rlimit_msgqueue] = , \}/*

* limit the stack by to some sane default: root can always

* increase this limit if needed.. 8mb seems reasonable.

*/#define _stk_lim (8*1024*1024)

#ifndef rlim_infinity

#define rlim_infinity (~0ul)//0取反,自然是能表示的最大值。

#endif

#ifndef _stk_lim_max

#define _stk_lim_max rlim_infinity

#endif

如果超級使用者不做更改,所有其他程序的rlimit最初的祖先就是這個程序的rlimit【在建立乙個程序時,在do_fork中會呼叫copy_signal函式將父程序的rlimit拷貝到子程序】。

分配棧記憶體

應該可以這麼理解:程序的棧有乙個vm_area_struct,在編譯器中可以設定他的大小:

可以看到指定虛擬記憶體中棧分配的合計大小預設為1mb。不過,我們可以在堆疊保留大小選項中調整大小。對於linux作業系統,棧的vm_area_struct的大小由rlim[rlimit_stack].rlim_cur限制。在**中可以更改rlim[rlimit_stack].rlim_cur的大小,甚至可以將其值設定為無窮大,完全不對棧的大小設定限制。

棧溢位

假設在程序執行過程中,棧已經增長到了vm_area_struct的邊界,在下一次壓棧操作時,訪問的虛擬位址落在了空洞中,正常情況下就會產生棧溢位,報出段錯誤。但是棧區是個特例。

這個落在空洞中的push訪存操作導致缺頁異常,缺頁異常發現位址就在棧區附近。所以,異常處理程式會檢查rlim[rlimit_stack].rlim_cur的限制,如果對這個位址的訪問不超出這個限制,就使用expand_stack()擴充套件vm_area_struct的邊界,將這個位址包含進去。否則,訪問出錯,報出段錯誤。

好了,說明白了原理?下面分析出錯原因。

我們看到上面的**只是定義了乙個區域性變數,並沒有顯示的程序記憶體訪問。按理說沒有訪存就不會有段錯誤啊?

其實是有訪存的,可以參考動態分配棧記憶體之alloca內幕。 在**需要一塊區域性棧變數時,他會在**中呼叫__alloca_probe在棧中申請記憶體,最後還要對申請的記憶體測試test dword ptr [eax],eax,這裡就存在訪存操作,如果記憶體訪問不合理,程式就會報出棧溢位錯誤。這樣就能在**測試的早期定位出錯位置,而不至於執行到某個某名奇妙的地方才報出錯誤。

怎麼解決

明白是棧記憶體不夠,接下來怎麼辦呢?

動態分配棧記憶體之alloca內幕

STM32區域性變數過大導致棧溢位

最近專案除錯中發現只要使用memset函式對乙個區域性陣列賦值時,就會導致其他全域性變數值被更改,接著就進入hardfault錯誤。後來發現區域性變數和全域性變數位址重疊。data write結構體為全域性變數,ota data為區域性陣列。看了啟動檔案startup stm32f10x hd.s中...

全域性變數 區域性變數 棧 堆

一般全域性變數存放在資料區,區域性變數存放在棧區,動態變數存放在堆區,函式 放在 區。棧區是普通的棧資料結構,遵循lifo後進先出的規則,區域性變數安排在那裡是asm時就規定的,這樣可以在乙個函式結束後平衡堆疊,操作簡單,效率高 堆 動態區 在這裡應當叫堆疊 不要和資料結構中的堆搞混 是程式在編譯時...

成員變數 區域性變數

成員變數 作為類的成員而存在,直接存在於類中。所有類的成員變 量可以通過this來引用。區域性變數 作為方法或語句塊的成員而存在,存在於方法的引數列表和方法定義中。1.成員變數可以被 public,protect,private,static等修飾符修飾,而 區域性變數不能被控制修飾符及 stati...