[英]how many recursive function calls causes stack overflow?
我正在研究一個用 c 編寫的模擬問題,我的程序的主要部分是一個遞歸函數。 當遞歸深度達到大約 500000 時,似乎發生堆棧溢出。
Q1 : 我想知道這正常嗎?
Q2 :一般有多少遞歸函數調用會導致堆棧溢出?
Q3 : 在下面的代碼中,去除局部變量neighbor
可以防止堆棧溢出嗎?
我的代碼:
/*
* recursive function to form Wolff Cluster(= WC)
*/
void grow_Wolff_cluster(lattic* l, Wolff* wolff, site *seed){
/*a neighbor of site seed*/
site* neighbor;
/*go through all neighbors of seed*/
for (int i = 0 ; i < neighbors ; ++i) {
neighbor = seed->neighbors[i];
/*add to WC according to the Wolff Algorithm*/
if(neighbor->spin == seed->spin && neighbor->WC == -1 && ((double)rand() / RAND_MAX) < add_probability)
{
wolff->Wolff_cluster[wolff->WC_pos] = neighbor;
wolff->WC_pos++; // the number of sites that is added to WC
neighbor->WC = 1; // for avoiding of multiple addition of site
neighbor->X = 0;
///controller_site_added_to_WC();
/*continue growing Wolff cluster(recursion)*/
grow_Wolff_cluster(l, wolff, neighbor);
}
}
}
我想知道這正常嗎?
是的。 只有這么多的堆棧大小。
在下面的代碼中,刪除局部變量鄰居可以防止堆棧溢出嗎?
不。即使沒有變量和返回值,函數調用本身也必須存儲在堆棧中,以便最終可以展開堆棧。
例如...
void recurse() {
recurse();
}
int main (void)
{
recurse();
}
這仍然會溢出堆棧。
$ ./test
ASAN:DEADLYSIGNAL
=================================================================
==94371==ERROR: AddressSanitizer: stack-overflow on address 0x7ffee7f80ff8 (pc 0x00010747ff14 bp 0x7ffee7f81000 sp 0x7ffee7f81000 T0)
#0 0x10747ff13 in recurse (/Users/schwern/tmp/./test+0x100000f13)
SUMMARY: AddressSanitizer: stack-overflow (/Users/schwern/tmp/./test+0x100000f13) in recurse
==94371==ABORTING
Abort trap: 6
一般來說,有多少遞歸函數調用會導致堆棧溢出?
這取決於您的環境和函數調用。 在 OS X 10.13 上,我默認限制為 8192K。
$ ulimit -s
8192
這個帶有clang -g
簡單示例可以遞歸 261976 次。 使用-O3
我不能讓它溢出,我懷疑編譯器優化已經消除了我的簡單遞歸。
#include <stdio.h>
void recurse() {
puts("Recurse");
recurse();
}
int main (void)
{
recurse();
}
添加一個整數參數,它是 261933 次。
#include <stdio.h>
void recurse(int cnt) {
printf("Recurse %d\n", cnt);
recurse(++cnt);
}
int main (void)
{
recurse(1);
}
添加一個雙參數,現在是 174622 次。
#include <stdio.h>
void recurse(int cnt, double foo) {
printf("Recurse %d %f\n", cnt, foo);
recurse(++cnt, foo);
}
int main (void)
{
recurse(1, 2.3);
}
添加一些堆棧變量,它是 104773 次。
#include <stdio.h>
void recurse(int cnt, double foo) {
double this = 42.0;
double that = 41.0;
double other = 40.0;
double thing = 39.0;
printf("Recurse %d %f %f %f %f %f\n", cnt, foo, this, that, other, thing);
recurse(++cnt, foo);
}
int main (void)
{
recurse(1, 2.3);
}
等等。 但是我可以在這個 shell 中增加我的堆棧大小並獲得兩倍的調用。
$ ./test 2> /dev/null | wc -l
174622
$ ulimit -s 16384
$ ./test 2> /dev/null | wc -l
349385
對於 65,532K 或 64M 的堆棧,我有一個嚴格的上限。
$ ulimit -Hs
65532
是與否 - 如果您在代碼中遇到堆棧溢出,這可能意味着一些事情
您的算法沒有以尊重您獲得的堆棧上的內存量的方式實現。 您可以調整此數量以滿足算法的需要。
如果是這種情況,更常見的是更改算法以更有效地利用堆棧,而不是添加更多內存。 例如,將遞歸函數轉換為迭代函數可以節省大量寶貴的內存。
這是一個試圖吃掉你所有內存的錯誤。 您忘記了遞歸中的基本情況或錯誤地調用了相同的函數。 我們都至少做過2次。
不一定有多少調用會導致溢出 - 它取決於每個單獨調用在堆棧幀上占用多少內存。 每個函數調用都用完堆棧內存,直到調用返回。 堆棧內存是靜態分配的——你不能在運行時改變它(在一個理智的世界里)。 這是幕后的后進先出 (LIFO) 數據結構。
它並沒有阻止它,它只是改變了溢出堆棧內存所需的對grow_Wolff_cluster
調用grow_Wolff_cluster
。 在 32 位系統上,從函數中刪除neighbor
的調用成本grow_Wolff_cluster
4 個字節。 當您將其乘以數十萬時,它會迅速加起來。
堆棧溢出不是由 C 標准定義的,而是由實現定義的。 C 標准定義了一種具有無限堆棧空間(以及其他資源)的語言,但確實有一部分是關於如何允許實現施加限制的。
通常,實際上首先創建錯誤的是操作系統。 操作系統不關心您進行了多少次調用,而是關心堆棧的總大小。 堆棧由堆棧幀組成,每個函數調用一個。 通常,堆棧幀由以下五項的某種組合組成(作為近似值;系統之間的細節可能會有很大差異):
for
循環中++i
指令的地址)。因為其中一些(特別是 1.、4. 和 5.)的大小可能有很大差異,所以很難估計平均堆棧幀有多大,盡管在這種情況下由於遞歸會更容易。 不同的系統也有不同的堆棧大小; 目前看起來默認情況下我可以有 8 MiB 用於堆棧,但嵌入式系統可能會少很多。
這也解釋了為什么刪除局部變量會為您提供更多可用的函數調用; 您減少了 500,000 個堆棧幀中的每一個的大小。
如果您想增加可用的堆棧空間量,請查看setrlimit(2)
函數(在 Linux 上類似於 OP;在其他系統上可能會有所不同)。 不過,首先,您可能想嘗試調試和重構以確保您需要所有堆棧空間。
每次函數重復出現時,您的程序都會在堆棧上占用更多內存,每個函數占用的內存取決於函數和其中的變量。 一個函數可以完成的遞歸次數完全取決於您的系統。
沒有一般的遞歸次數會導致堆棧溢出。
刪除變量“鄰居”將允許函數進一步遞歸,因為每次遞歸占用的內存更少,但最終仍會導致堆棧溢出。
這是一個簡單的 c# 函數,它將顯示您的計算機在堆棧溢出之前可以進行多少次迭代(作為參考,我已運行到 10478):
private void button3_Click(object sender, EventArgs e)
{
Int32 lngMax = 0;
StackIt(ref lngMax);
}
private void StackIt(ref Int32 plngMax, Int32 plngStack = 0)
{
if (plngStack > plngMax)
{
plngMax = plngStack;
Console.WriteLine(plngMax.ToString());
}
plngStack++;
StackIt(ref plngMax, plngStack);
}
在這個簡單的例子中,條件檢查:“if (plngStack > plngMax)”可以被刪除,但是如果你有一個真正的遞歸函數,這個檢查將幫助你定位問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.