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

看板C_and_CPP作者 (沒有存在感的人)時間9年前 (2016/05/23 22:02), 9年前編輯推噓6(6047)
留言53則, 5人參與, 最新討論串1/1
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 -- ※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 223.136.189.53 ※ 文章網址: https://www.ptt.cc/bbs/C_and_CPP/M.1464012158.A.3DA.html

05/23 22:20, , 1F
Linux 上 malloc 回傳 non-NULL 不代表就是配置成功
05/23 22:20, 1F

05/23 22:21, , 2F
要留意預設的 optimistic memory allocation strategy
05/23 22:21, 2F

05/23 22:22, , 3F
若回傳non-NULL但是是失敗,要怎知道是失敗,有error code嗎
05/23 22:22, 3F

05/23 22:22, , 4F
有興趣的可以看一下 Linux 文件 http://goo.gl/V9krbB
05/23 22:22, 4F

05/23 22:23, , 5F
沒有辦法知道,除非關掉 overcommit 的機制 (預設為開)
05/23 22:23, 5F

05/23 22:25, , 6F
L大指的好像是realloc的情況?如果沒有辦法增加到想要
05/23 22:25, 6F

05/23 22:25, , 7F
再來一篇延伸閱讀給有興趣的 http://goo.gl/5tCLQc
05/23 22:25, 7F

05/23 22:25, , 8F
不單是 realloc,呼叫 malloc 幾乎都會回傳 non-NULL
05/23 22:25, 8F

05/23 22:26, , 9F
因為採用 overcommit 的機制,建議把延伸閱讀看一下
05/23 22:26, 9F

05/23 22:28, , 10F
看過不少人以為 non-NULL 就是可以讀寫,其實不一定...
05/23 22:28, 10F

05/23 22:29, , 11F
這樣如果出現memory frag.不是很難debug嗎?
05/23 22:29, 11F

05/23 22:32, , 12F
overcommit 某種程度可以讓系統有效的利用實體記憶體
05/23 22:32, 12F

05/23 22:38, , 13F
kernel沒有限制user space使用memory的上限?
05/23 22:38, 13F

05/23 22:40, , 14F
這是兩件事情,單一 process 是否達到上限,跟一堆
05/23 22:40, 14F

05/23 22:40, , 15F
剛剛查了一下,Windows好像也有overcommit?
05/23 22:40, 15F

05/23 22:41, , 16F
所以windows會有同樣的問題嗎?
05/23 22:41, 16F

05/23 22:41, , 17F
processes 配置了 1GB ,但是卻沒辦法正常存取是兩件事
05/23 22:41, 17F

05/23 22:42, , 18F
Windows / Mac OS X 在記憶體不夠用時,皆會回傳 NULL
05/23 22:42, 18F
※ 編輯: wtchen (223.136.189.53), 05/23/2016 23:02:14

05/25 12:02, , 19F
標題殺人 被標題騙到很生氣地進來看 XD
05/25 12:02, 19F
抱歉造成誤解.... ※ 編輯: wtchen (220.128.143.228), 05/25/2016 12:53:42

05/27 17:32, , 20F
linux會在需要實體記憶體時觸發swap或oom,所以頂多有
05/27 17:32, 20F

05/27 17:32, , 21F
效能問題,應該不至於會error。
05/27 17:32, 21F

05/27 18:52, , 22F
不夠用就是會被 OOM killer 給砍掉... 不是無上限的
05/27 18:52, 22F

05/27 18:58, , 23F
#include <cstdlib>
05/27 18:58, 23F

05/27 18:58, , 24F
#include <cstring>
05/27 18:58, 24F

05/27 18:59, , 25F
int main() {
05/27 18:59, 25F

05/27 18:59, , 26F
const size_t CHUNK_SIZE = 1024*1024*1024;
05/27 18:59, 26F

05/27 18:59, , 27F
const size_t NUMBER_OF_CHUNK = 1024;
05/27 18:59, 27F

05/27 18:59, , 28F
void *chunks[NUMBER_OF_CHUNK];
05/27 18:59, 28F

05/27 18:59, , 29F
// allocate without mapping
05/27 18:59, 29F

05/27 18:59, , 30F
for (size_t i=0; i<NUMBER_OF_CHUNK; i++) {
05/27 18:59, 30F

05/27 19:00, , 31F
chunks[i] = malloc(CHUNK_SIZE);
05/27 19:00, 31F

05/27 19:00, , 32F
}
05/27 19:00, 32F

05/27 19:00, , 33F
// use memset to map physical memory
05/27 19:00, 33F

05/27 19:00, , 34F
for (size_t i=0; i<NUMBER_OF_CHUNK; i++) {
05/27 19:00, 34F

05/27 19:00, , 35F
memset(chunks[i], 0x0, CHUNK_SIZE);
05/27 19:00, 35F

05/27 19:00, , 36F
}
05/27 19:00, 36F

05/27 19:00, , 37F
// reclaim allocated chunks
05/27 19:00, 37F

05/27 19:01, , 38F
for (size_t i=0; i<NUMBER_OF_CHUNK; i++) {
05/27 19:01, 38F

05/27 19:01, , 39F
free(chunks[i]);
05/27 19:01, 39F

05/27 19:01, , 40F
}
05/27 19:01, 40F

05/27 19:01, , 41F
return 0;
05/27 19:01, 41F

05/27 19:01, , 42F
}
05/27 19:01, 42F

05/27 19:02, , 43F
有興趣的自己跑跑看上面,看會不會被 OOM killer 砍掉
05/27 19:02, 43F

05/27 19:04, , 44F
順便檢查一下 malloc 的回傳值,是否都是 non-NULL
05/27 19:04, 44F

05/27 19:07, , 45F
加個 if (!chunks[i]) throw std::bad_alloc(); 之類的
05/27 19:07, 45F

05/27 19:08, , 46F
看到底是 OOM killer 砍掉還是 NULL dereference 掛掉
05/27 19:08, 46F

05/27 19:09, , 47F
這三個迴圈一定要分開跑,寫成一個迴圈就不一定會被砍
05/27 19:09, 47F

05/27 19:16, , 48F
OOM不會選擇砍別的process嗎?如果在preempt RT kernel
05/27 19:16, 48F

05/27 19:16, , 49F
的話加上mlockall就會砍別的process吧?
05/27 19:16, 49F

05/27 19:18, , 50F
有評分機制,可參考 oom_kill.c https://goo.gl/vVqyeo
05/27 19:18, 50F

05/27 19:20, , 51F
有可能別的 process 會被砍,假設它 oom_score 更高
05/27 19:20, 51F

05/27 19:20, , 52F
只是上面的範例,通常會是被砍的那一個... :)
05/27 19:20, 52F

05/27 20:32, , 53F
要寫的文章又多一篇 XD
05/27 20:32, 53F
文章代碼(AID): #1NGmr-FQ (C_and_CPP)