14

I'm coming back to programming after having done none for several years, and created a Sudoku game to get my feet wet again. I've written a recursive function to brute-force a solution, and it will do so for simple board states, but runs into a stack overflow most of the time for more complicated board states. I know that this could be averted using loops or a more efficient algorithm, but since this is for the sake of learning I want to know how to allocate more memory to the stack (acknowledging this isn't the best solution to the problem).

An answer here: How to change stack size for a .NET program? that recommends using a thread with more memory allocated isn't working, as from what I can read .NET 4.0 will not let you increase a thread's maximum allocated memory beyond the default.

Another solution recommends using EDITBIN.EXE, but I am new to Visual Studio and have not found an explanation I understand of how to do so.

Similarly, I've found that you can use a .def file to indicate a larger default stack size, but have not found an explanation I understand of how to create/implement one.

Can anyone offer newbie-level explanations of either EDITBIN.EXE or .def file implementations, or offer another way to increase the stack size?

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);
        }
    }
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jacob Hall
  • 165
  • 1
  • 1
  • 7
  • The `Thread` maximum stack size is enforced on assemblies that have partial trust. However, assemblies that are built and used on your computer should get full trust. Are you sure that this is the reason that it doesn't work? – zneak Feb 08 '19 at 00:12
  • 3
    This is a good question, one I can't give a solid answer for, but I would probably acknowledge that recursion isn't just _not the best solution_, it's the complete wrong solution. It would be a better use of your time to rework your code to use a managed stack of arbitrary size using `Stack` for depth first, or `Queue` for breadth-first. – Prime Feb 08 '19 at 00:13
  • 1
    That being said, regarding EDITBIN.EXE, I believe they're suggesting to run this on the compiled executable (located in the bin folder of your project after building). You could automatically run it by adding it as a post-build event (Right click on project > Properties > Build Events > Post-build event command line) – Prime Feb 08 '19 at 00:15
  • Yes, I've checked when it overflows and the algorithm is still functioning as it should, and is not stuck looping infinitely. I've also created intentionally easy to solve states, and it gets through them quickly. When I don't use a thread, it overflows after ~3000 calls, when I do use a thread it overflows after ~900 calls, no matter how much memory I allocate when the thread is created. – Jacob Hall Feb 08 '19 at 00:17
  • @JacobHall - If you are hitting a stack overflow then it sounds like you have a coding problem - just increasing the stack is a band-aid that may not fix your problem. Your code may just be using all the stack no matter the size. Can you please post your code so that we can see? – Enigmativity Feb 08 '19 at 00:28
  • @Enigmativity the code for the function is too long to put in the reply here. It's line 297 here: https://github.com/jacobhall88/SudokuForms/blob/RecursiveSolution/Sudoku/BoardState.cs – Jacob Hall Feb 08 '19 at 00:45
  • @mjwills I read that here: https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.-ctor?view=netframework-4.7.2#System_Threading_Thread__ctor_System_Threading_ThreadStart_System_Int32_ It indicated this was only for partially trusted applications, but am having difficulty figuring out if that is what is happening here. – Jacob Hall Feb 08 '19 at 00:47
  • `I read that here` It may be more helpful to put that link in your question. – mjwills Feb 08 '19 at 01:56
  • Figuring out how to make the program not use recursion is likely something that would *also* be a good learning exercise, as well as figuring out how to increase the stack. Both are worth learning, but removing recursion is going to come up more often in practice. – Jon Hanna Feb 18 '19 at 16:41

2 Answers2

18

The big bad warning

If you use recursion in a program and reach a point where having a StackOverflowException is an actual threat, please do not consider increasing the stack size as a valid solution.

If you encounter a StackOverflowException you are doing something very wrong; you should instead be using a Stack<T> for depth-first processing, or a Queue<T> for breadth-first processing. Example.


The solution

This can be achieved by using editbin.exe, which is installed with this package; VC++ tools

Find the location of editbin.exe, mine was located at C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\Hostx64\x64\editbin.exe, I would suggest using Everything by voidtools in lieu of Microsoft's awful search to find this.

Set the stack size manually

Navigate to your bin folder and execute the following:

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

For example I executed this:

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

Which set the stack reserve size to 2MB.

With this I was capable of reaching twice the recursion level; (1MB stack reserve on left, 2MB stack reserve on right).

Recursion level

Set the stack size automatically

Right click on your project and select 'Options', then click on 'Build Events' and add the following to your post-build events:

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

For example I added

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

This will run editbin.exe every time you build your executable.

Note: You will see a lot lower level of recursion reached when running your program from Visual Studio as you will from running it explicitly via explorer or cmd. You will still however see a 2x increase in the level of recursion met if moving from a 1MB stack reserve to a 2MB stack reserve.

Prime
  • 2,410
  • 1
  • 20
  • 35
  • 1
    This is exactly what I needed! I will not actually use a recursive function for solving, its inefficiency just lead me to this new thing I wanted to learn. Thank you! – Jacob Hall Feb 08 '19 at 01:28
  • Changing the stack of a managed program is something I've honestly never considered, so it was definitely an interesting enough question to be perused even if just for the heck of it. In the end I got to learn [what the difference between the stack reserve and the stack commit](https://stackoverflow.com/a/24260816/1481699) was. – Prime Feb 08 '19 at 01:36
  • I received that same error when the path to editbin.exe was wrong, I think it might be a generic error though. Try running the command in cmd to get the actual error. – Prime Feb 08 '19 at 02:06
  • @AlphaDelta The max recursion level I get is 24080 with a C# console app, .NET Core 3.1. It is much fewer than yours. any idea? code: ` public void GetStackLimit(int index) { Console.WriteLine(index); GetStackLimit(++index);} ` – Pingpong Oct 03 '21 at 10:21
  • Just a thank you for recommending the Everything tool!!! Windows continues to make things difficult and a lot of the time, useless. Like Explorer! Now off to configure a shortcut key for this gem of an app. Hmmm, maybe Windows Key + E, lol. – kstubs May 03 '23 at 20:59
1

Perhaps set the Stack Reserve Size in Visual Studio:

Project -> Properties -> Configuration Properties -> Linker -> System -> Stack Reserve Size

This can also be done via the command-line or programatically when a thread is created:

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

jspcal
  • 50,847
  • 7
  • 72
  • 76
  • 1
    I'm using VS 2017, and in Project -> Properties I do not have a 'Configuration Properties' subcategory. The options I have under properties are these: https://imgur.com/a/QqxxzQL I read that this could be because I'm seeing the solution properties and not the project properties, but I also see this when I right click the project in the solution explorer and go to properties that way – Jacob Hall Feb 08 '19 at 00:22
  • 3
    I believe this answer only applies to C++ projects, doesn't it? – Prime Feb 08 '19 at 00:23
  • 2
    Yes, @AlphaDelta is correct. This controls command-line switches passed to `link.exe`, which is only used for C and C++ builds. You won't even *see* these options in the project properties for an application targeting .NET. – Cody Gray - on strike Feb 08 '19 at 02:01