十三誡增修--10:不要在 stack 設置過大的變數以避免堆疊溢位

作者: wtchen (沒有存在感的人)   2016-05-23 22:02:35
10. 不要在 stack 設置過大的變數以避免堆疊溢位(stack overflow)
由於編譯器會自行決定 stack 的上限,某些預設是數 KB 或數十KB,當變數所需的空
間過大時,很容易造成 stack overflow,程式亦隨之當掉(segmentation fault)。
可能造成堆疊溢位的原因包括遞迴太多次(多為程式設計缺陷),
或是在 stack 設置過大的變數。
錯誤例子:
int array[10000000]; // 在stack宣告過大陣列
std::array<int, 10000000> myarray; //在stack宣告過大std::array
正確例子:
C:
int *array = (int*) malloc( 10000000*sizeof(int) );
C++:
std::vector<int> v;
v.resize(10000000);
說明:建議將使用空間較大的變數用malloc/new配置在 heap 上,由於此時 stack
上只需配置一個 int* 的空間指到在heap的該變數,可避免 stack overflow。
使用 heap 時,雖然整個 process 可用的空間是有限的,但採用動態抓取
的方式,new 無法配置時會丟出 std::bad_alloc 例外,malloc 無法配置
時會回傳 null(註2),不會影響到正常使用下的程式功能
備註:
註1. 使用 heap 時,整個 process 可用的空間一樣是有限的,若是需要頻繁地
malloc / free 或 new / delete 較大的空間,需注意避免造成記憶體破碎
(memory fragmentation)。
註2. 由於Linux使用overcommit機制管理記憶體,malloc即使在記憶體不足時
仍然會回傳非NULL的address,同樣情形在Windows/Mac OS則會回傳NULL
(感謝 LiloHuang 補充)
補充資料:
- https://zh.wikipedia.org/wiki/%E5%A0%86%E7%96%8A%E6%BA%A2%E4%BD%8D
- http://stackoverflow.com/questions/3770457/what-is-memory-fragmentation
- http://library.softwareverify.com/memory-fragmentation-your-worst-nightmare/
overcommit跟malloc:
- http://goo.gl/V9krbB
- http://goo.gl/5tCLQc
作者: LiloHuang (十年一刻)   2016-05-23 22:20:00
Linux 上 malloc 回傳 non-NULL 不代表就是配置成功要留意預設的 optimistic memory allocation strategy
作者: kwpn (ITSST)   2016-05-23 22:22:00
若回傳non-NULL但是是失敗,要怎知道是失敗,有error code嗎
作者: LiloHuang (十年一刻)   2016-05-23 22:22:00
有興趣的可以看一下 Linux 文件 http://goo.gl/V9krbB沒有辦法知道,除非關掉 overcommit 的機制 (預設為開)
作者: wtchen (沒有存在感的人)   2016-05-23 22:25:00
L大指的好像是realloc的情況?如果沒有辦法增加到想要
作者: LiloHuang (十年一刻)   2016-05-23 22:25:00
再來一篇延伸閱讀給有興趣的 http://goo.gl/5tCLQc不單是 realloc,呼叫 malloc 幾乎都會回傳 non-NULL因為採用 overcommit 的機制,建議把延伸閱讀看一下看過不少人以為 non-NULL 就是可以讀寫,其實不一定...
作者: wtchen (沒有存在感的人)   2016-05-23 22:29:00
這樣如果出現memory frag.不是很難debug嗎?
作者: LiloHuang (十年一刻)   2016-05-23 22:32:00
overcommit 某種程度可以讓系統有效的利用實體記憶體
作者: wtchen (沒有存在感的人)   2016-05-23 22:38:00
kernel沒有限制user space使用memory的上限?
作者: LiloHuang (十年一刻)   2016-05-23 22:40:00
這是兩件事情,單一 process 是否達到上限,跟一堆
作者: wtchen (沒有存在感的人)   2016-05-23 22:40:00
剛剛查了一下,Windows好像也有overcommit?所以windows會有同樣的問題嗎?
作者: LiloHuang (十年一刻)   2016-05-23 22:41:00
processes 配置了 1GB ,但是卻沒辦法正常存取是兩件事Windows / Mac OS X 在記憶體不夠用時,皆會回傳 NULL
作者: tinlans ( )   2016-05-25 12:02:00
標題殺人 被標題騙到很生氣地進來看 XD
作者: hylkevin (19s)   2016-05-27 17:32:00
linux會在需要實體記憶體時觸發swap或oom,所以頂多有效能問題,應該不至於會error。
作者: LiloHuang (十年一刻)   2016-05-27 18:52:00
不夠用就是會被 OOM killer 給砍掉... 不是無上限的#include <cstdlib>#include <cstring>int main() {const size_t CHUNK_SIZE = 1024*1024*1024;const size_t NUMBER_OF_CHUNK = 1024;void *chunks[NUMBER_OF_CHUNK];// allocate without mappingfor (size_t i=0; i<NUMBER_OF_CHUNK; i++) {chunks[i] = malloc(CHUNK_SIZE);}// use memset to map physical memoryfor (size_t i=0; i<NUMBER_OF_CHUNK; i++) {memset(chunks[i], 0x0, CHUNK_SIZE);}// reclaim allocated chunksfor (size_t i=0; i<NUMBER_OF_CHUNK; i++) {free(chunks[i]);}return 0;}有興趣的自己跑跑看上面,看會不會被 OOM killer 砍掉順便檢查一下 malloc 的回傳值,是否都是 non-NULL加個 if (!chunks[i]) throw std::bad_alloc(); 之類的看到底是 OOM killer 砍掉還是 NULL dereference 掛掉這三個迴圈一定要分開跑,寫成一個迴圈就不一定會被砍
作者: wtchen (沒有存在感的人)   2016-05-27 19:16:00
OOM不會選擇砍別的process嗎?如果在preempt RT kernel的話加上mlockall就會砍別的process吧?
作者: LiloHuang (十年一刻)   2016-05-27 19:18:00
有評分機制,可參考 oom_kill.c https://goo.gl/vVqyeo有可能別的 process 會被砍,假設它 oom_score 更高只是上面的範例,通常會是被砍的那一個... :)
作者: wtchen (沒有存在感的人)   2016-05-27 20:32:00
要寫的文章又多一篇 XD

Links booklink

Contact Us: admin [ a t ] ucptt.com