有没有办法增加c#中的堆栈大小?

Posted

技术标签:

【中文标题】有没有办法增加c#中的堆栈大小?【英文标题】:Is there a way to increase the stack size in c#? 【发布时间】:2019-07-02 05:05:50 【问题描述】:

在几年没有做过编程之后,我又回到了编程领域,并创建了一个数独游戏来让我的脚再次湿透。我编写了一个递归函数来蛮力解决方案,它会为简单的板状态执行此操作,但对于更复杂的板状态,大多数时间都会遇到堆栈溢出。我知道可以使用循环或更有效的算法来避免这种情况,但由于这是为了学习,我想知道如何为堆栈分配更多内存(承认这不是解决问题的最佳方法)。

这里的答案:How to change stack size for a .NET program? 建议使用分配更多内存的线程不起作用,因为据我所知,.NET 4.0 不会让您将线程的最大分配内存增加到默认值之外。

另一种解决方案建议使用 EDITBIN.EXE,但我是 Visual Studio 的新手,还没有找到我理解的解释。

同样,我发现您可以使用 .def 文件来指示更大的默认堆栈大小,但没有找到我理解的关于如何创建/实现的解释。

谁能提供 EDITBIN.EXE 或 .def 文件实现的新手级解释,或提供另一种增加堆栈大小的方法?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace Sudoku

    //object keeps track of the value of all numbers currently on the board using an array

    class BoardState
    
        int testcount = 1;
        //3d array of ints representing all values on the board, represted as region, column, row
        int[,,] boardVals;

        //3d array of bools representing if a number is immutable (true cannot be changed, false can be)
        bool[,,] fixedVals;

        //create a blank board if no initial values are provided
        public BoardState()
        
            boardVals = new int[9, 3, 3];
            fixedVals = new bool[9, 3, 3];
        

        //create a board with the listed initial values as immutable
        public BoardState(int[,,] inputVals)
        
            boardVals = inputVals;
            fixedVals = new bool[9,3,3];
            for (int i = 0; i < 9; ++i)
                for (int j = 0; j < 3; ++j)
                    for (int k = 0; k < 3; ++k)
                        if (boardVals[i, j, k] > 0) fixedVals[i, j, k] = true;
        

        //update the state of the board using the coordinates of a single value
        //**note** method has not been implemented and tested yet
        public void updateState(int region, int column, int row, int val)
        
            if (!fixedVals[region, column, row])
            
                boardVals[region, column, row] = val;
            
        

        //update the state of the board to match the state of another board
        public void updateState(int[,,] newState)
        
            boardVals = newState;
        

        public int[,,] getVals()
        
            return boardVals;
        

        public bool[,,] getFixed()
        
            return fixedVals;
        

        //set all non-zero values to be immutable
        public void setFixed()
        
            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++) 
                        if (boardVals[i, j, k] != 0)
                            fixedVals[i, j, k] = true;
                        else
                            fixedVals[i, j, k] = false;
                    
        

        //test method
        public void testState()
        
            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                        Console.WriteLine(boardVals[i, k, j]);
        

        //accepts a 3d array representing the current board state.
        //returns a 3d bool array denoting whether any region, row, or column is invalid (true=invalid)
        //first value of array designates the region, row, or column respectively
        //second value designates which of those is invalid
        public bool[,] validateBoard()
        
            bool[,] valid = new bool[3, 9];
            int[,] rows = makeRows(boardVals);
            int[,] cols = makeCols(boardVals);

            //compare each value in each row to each other value in that row
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 9; j++)
                

                    //only validate an entry if it has been assigned a value
                    if (rows[i, j] != 0)
                    
                        for (int k = 0; k < 9; k++)
                        
                            //if two values are not the same entry and are equal, set that entry to invalid
                            if (j != k && rows[i, j] == rows[i, k])
                                valid[1, i] = true;
                        
                    
                
            

            //compare each value in each column to each other value in that column
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 9; j++)
                

                    //only validate an entry if it has been assigned a value
                    if (cols[i, j] != 0)
                    
                        for (int k = 0; k < 9; k++)
                        
                            //if two values are not the same entry and are equal, set that entry to invalid
                            if (j != k && cols[i, j] == cols[i, k])
                                valid[2, i] = true;
                        
                    
                
            

            //compare each value in each region to each other value in that region
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 3; j++)
                
                    for (int k = 0; k < 3; k++)
                    
                        //only validate an entry if it has been assigned a value
                        if (boardVals[i, j, k] != 0)
                        
                            for (int l = 0; l < 3; l++)
                            
                                for (int m = 0; m < 3; m++)
                                
                                    //if two values are not the same entry and are equal, set that entry to invalid
                                    if (!(l == j && m == k) && boardVals[i, j, k] == boardVals[i, l, m])
                                        valid[0, i] = true;
                                
                            
                        
                    
                
            

            return valid;
        

        public bool isValid()
        
            bool retFlag = true;
            bool[,] valid = new bool[3, 9];
            int[,] rows = makeRows(boardVals);
            int[,] cols = makeCols(boardVals);

            //for (int i = 0; i < 9; i++)
            //    for (int j = 0; j < 3; j++)
            //        for (int k = 0; k < 3; k++)
            //            Console.Write(boardVals[i, j, k]);

            //compare each value in each row to each other value in that row
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 9; j++)
                

                    //only validate an entry if it has been assigned a value
                    if (rows[i, j] != 0)
                    
                        for (int k = 0; k < 9; k++)
                        
                            //if two values are not the same entry and are equal, set that entry to invalid
                            if (j != k && rows[i, j] == rows[i, k])
                            
                                retFlag = false;
                            

                        
                    
                
            

            //compare each value in each column to each other value in that column
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 9; j++)
                

                    //only validate an entry if it has been assigned a value
                    if (cols[i, j] != 0)
                    
                        for (int k = 0; k < 9; k++)
                        
                            //if two values are not the same entry and are equal, set that entry to invalid
                            if (j != k && cols[i, j] == cols[i, k])
                            
                                retFlag = false;
                            
                        
                    
                
            

            //compare each value in each region to each other value in that region
            for (int i = 0; i < 9; i++)
            
                for (int j = 0; j < 3; j++)
                
                    for (int k = 0; k < 3; k++)
                    
                        //only validate an entry if it has been assigned a value
                        if (boardVals[i, j, k] != 0)
                        
                            for (int l = 0; l < 3; l++)
                            
                                for (int m = 0; m < 3; m++)
                                
                                    //if two values are not the same entry and are equal, set that entry to invalid
                                    if (!(l == j && m == k) && boardVals[i, j, k] == boardVals[i, l, m])
                                    
                                        retFlag = false;
                                    
                                
                            
                        
                    
                
            

            return retFlag;
        

        //returns an array of all the values in each row
        public int[,] makeRows(int[,,] boardState)
        
            int[,] rows = new int[9, 9];

            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 9; j++)
                    rows[i, j] = boardState[j / 3 + ((i / 3) * 3), j % 3, i - ((i / 3) * 3)];

            return rows;
        

        //returns an array of all values in each column
        public int[,] makeCols(int[,,] boardState)
        
            int[,] cols = new int[9, 9];

            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 9; j++)
                    cols[i, j] = boardState[((j / 3) * 3) + (i / 3), i - ((i / 3) * 3), j % 3];

            return cols;
        

        //update the board state to a state read in from a file
        public void updateFromFile(Stream update)
        
            int[,,] newVals = new int[9, 3, 3];
            int[,,] newFixed = new int[9, 3, 3];

            StreamReader file = new StreamReader(update);

            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                        boardVals[i, j, k] = (int)char.GetNumericValue((char)file.Read());
                    
                
            

            for (int i = 0; i < 9; i++)
                for (int j = 0; j < 3; j++)
                    for (int k = 0; k < 3; k++)
                        fixedVals[i, j, k] = (0 != ((int)char.GetNumericValue((char)file.Read())));
                    
                
            

            file.Close();
        

        public void Solve(int entry, int val)
        
            Console.WriteLine("This is call number " + ++testcount);

            //returns if all values are filled and valid
            if (entry == 81)
            
                Console.WriteLine("Solved!");
                return;
            

            //creating reference coordinates based on entry value
            int reg = entry / 9;
            int col = (entry - (reg * 9)) % 3;
            int row = (entry - (reg * 9)) / 3;


            //if current entry being checked is a fixed value, go the next value
            if (!fixedVals[reg, row, col])
            
                Console.WriteLine("");
                Console.WriteLine("Making an attempt at entry " + entry + " using value " + val);
                Console.WriteLine("This entry is at region " + reg + ", col " + col + ", row " + row);

                //assign entry the value to be tested
                boardVals[reg, row, col] = val;

                //if the value is valid, go to the next entry
                if (isValid())
                
                    Console.WriteLine("Entry Valid at " + entry);
                    val = 1;
                    entry++;
                    Console.WriteLine("Trying the next entry at " + entry);
                    Solve(entry, val);
                

                //if the value is invlid and all 9 values have not been tried,
                //increment value and call again at same entry
                if (!isValid() && val < 9)
                
                    Console.WriteLine("Entry Invalid at " + entry + " with value " + val);
                    ++val;
                    Console.WriteLine("Trying again with value " + val);
                    Solve(entry, val);
                

                //if the value in invalid and all 9 values have been tried,
                //zero out the entry and go back to the previous non-fixed entry
                if (!isValid() && val == 9)
                
                    do
                    
                        boardVals[reg, row, col] = 0;

                        Console.WriteLine("Reached Value 9 and was still invalid");
                        --entry;
                        Console.WriteLine("Trying again at entry " + entry);
                        Console.WriteLine("The value at that entry is " + boardVals[reg, row, col]);
                        reg = entry / 9;
                        col = (entry - reg * 9) % 3;
                        row = (entry - reg * 9) / 3;
                        if (fixedVals[reg, row, col])
                            Console.WriteLine("But that's a fixed value, so I'll go back one more");
                        Console.WriteLine("");
                     while (boardVals[reg, row, col] == 9 || fixedVals[reg, row, col]);
                    val = boardVals[reg, row, col] + 1;

                    Solve(entry, val);

                
            
            else Solve(++entry, val);
        
    

【问题讨论】:

Thread 最大堆栈大小对具有部分信任的程序集强制执行。但是,在您的计算机上构建和使用的程序集应该得到完全信任。你确定这是它不起作用的原因吗? 这是一个很好的问题,我不能给出一个可靠的答案,但我可能会承认递归不仅仅是不是最好的解决方案,它是完全错误的解决方案。使用Stack&lt;T&gt; 深度优先或Queue&lt;T&gt; 广度优先使用任意大小的托管堆栈会更好地利用您的时间。 话虽如此,关于 EDITBIN.EXE,我相信他们建议在编译的可执行文件上运行它(在构建后位于项目的 bin 文件夹中)。您可以通过将其添加为构建后事件来自动运行它(右键单击项目 > 属性 > 构建事件 > 构建后事件命令行) 是的,我已经检查过它何时溢出并且算法仍在正常运行,并且没有无限循环。我还故意创建了易于解决的状态,并且可以快速通过它们。当我不使用线程时,它会在 ~3000 次调用后溢出,当我使用线程时,它会在 ~900 次调用后溢出,无论我在创建线程时分配了多少内存。 @JacobHall - 如果您遇到堆栈溢出,那么听起来您遇到了编码问题 - 只是增加堆栈是一种创可贴,可能无法解决您的问题。无论大小,您的代码都可能只是使用所有堆栈。你能把你的代码贴出来让我们看看吗? 【参考方案1】:

或许可以在 Visual Studio 中设置 Stack Reserve Size:

项目 -> 属性 -> 配置属性 -> 链接器 -> 系统 -> 堆栈保留大小

这也可以通过命令行或在创建线程时以编程方式完成:

https://docs.microsoft.com/en-us/cpp/build/reference/stack-stack-allocations?view=vs-2017

【讨论】:

我使用的是 VS 2017,在项目 -> 属性中我没有“配置属性”子类别。我在属性下的选项是:imgur.com/a/QqxxzQL 我读到这可能是因为我看到的是解决方案属性而不是项目属性,但是当我在解决方案资源管理器中右键单击项目并转到这样的属性 我相信这个答案只适用于 C++ 项目,不是吗? 是的,@AlphaDelta 是正确的。这控制传递给link.exe 的命令行开关,它仅用于 C 和 C++ 构建。您甚至不会在面向 .NET 的应用程序的项目属性中看到这些选项。【参考方案2】:

严重的警告

如果您在程序中使用递归并达到 ***Exception 成为实际威胁的程度,请不要将增加堆栈大小视为有效解决方案。

如果你遇到 ***Exception 异常,你就做错了;您应该改为使用Stack&lt;T&gt; 进行深度优先处理,或使用Queue&lt;T&gt; 进行广度优先处理。 Example.


解决办法

这可以通过使用editbin.exe来实现,它与这个包一起安装;

找到editbin.exe的位置,我的位置是C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\Hostx64\x64\editbin.exe,我建议使用Everything by voidtools来代替微软糟糕的搜索来找到这个。

手动设置堆栈大小

导航到您的 bin 文件夹并执行以下操作:

"<full path of editbin.exe>" /stack:<stack size in bytes, decimal> <your executable name>

例如我执行了这个:

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\Hostx64\x64\EDITBIN.EXE" /stack:2097152 ExampleProgram.exe

将堆栈保留大小设置为 2MB。

有了这个,我能够达到两倍的递归级别; (左侧为 1MB 堆栈预留,右侧为 2MB 堆栈预留)。

自动设置堆栈大小

右键单击您的项目并选择“选项”,然后单击“构建事件”并将以下内容添加到您的构建后事件中:

"<full path of editbin.exe>" /stack:<stack size in bytes, decimal> "$(TargetPath)"

比如我加了

"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\Hostx64\x64\EDITBIN.EXE" /stack:2097152 "$(TargetPath)"

这将在您每次构建可执行文件时运行 editbin.exe

注意:当您从 Visual Studio 运行程序时,您会看到比通过资源管理器或 cmd 显式运行程序时所达到的递归级别要低得多。但是,如果从 1MB 堆栈保留移动到 2MB 堆栈保留,您仍然会看到递归级别增加了 2 倍。

【讨论】:

这正是我所需要的!我实际上不会使用递归函数来求解,它的低效率只会让我想到我想学习的这个新东西。谢谢! 更改托管程序的堆栈是老实说我从未考虑过的事情,所以这绝对是一个足够有趣的问题,即使只是为了它而仔细阅读。最后我学会了what the difference between the stack reserve and the stack commit是。 当 editbin.exe 的路径错误时,我收到了同样的错误,但我认为这可能是一般错误。尝试在 cmd 中运行命令以获取实际错误。 @AlphaDelta 我使用 C# 控制台应用程序 .NET Core 3.1 获得的最大递归级别为 24080。它比你的要少得多。任何想法?代码:`public void GetStackLimit(int index) Console.WriteLine(index); GetStackLimit(++index); `

以上是关于有没有办法增加c#中的堆栈大小?的主要内容,如果未能解决你的问题,请参考以下文章

BackgroundWorker DoWork 线程的堆栈大小是多少?有没有办法改变它?

有没有办法在递归调用之前检查可用的堆栈大小? (C#)

在 C# 中检查堆栈大小

如何增加 Rust 库可用的堆栈大小?

在 C++ 中增加堆栈大小

在并行 for 循环 C# 中定义堆栈大小