Есть ли способ увеличить размер стека в С#?

Я возвращаюсь к программированию после того, как несколько лет ничего не делал, и создал игру судоку, чтобы снова намочить ноги. Я написал рекурсивную функцию для перебора решения, и она делает это для простых состояний доски, но в большинстве случаев сталкивается с переполнением стека для более сложных состояний доски. Я знаю, что этого можно избежать с помощью циклов или более эффективного алгоритма, но, поскольку это делается ради обучения, я хочу знать, как выделить больше памяти в стек (признаю, что это не лучшее решение проблемы).

Ответ здесь: Как изменить размер стека для программы .NET?, который рекомендует использовать поток с большим объемом выделенной памяти, не работает, так как из того, что я могу прочитать, .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 применяется к сборкам с частичным доверием. Однако сборки, созданные и используемые на вашем компьютере, должны получить полное доверие. Вы уверены, что это причина того, что он не работает?

zneak 08.02.2019 01:12

Это хороший вопрос, на который я не могу дать четкого ответа, но я, вероятно, признаю, что рекурсия — это не просто не лучшее решение, это совершенно неправильное решение. Было бы лучше использовать ваше время, чтобы переработать свой код, чтобы использовать управляемый стек произвольного размера, используя Stack<T> для глубины сначала или Queue<T> для ширины.

AlphaDelta 08.02.2019 01:13

При этом, что касается EDITBIN.EXE, я считаю, что они предлагают запустить это в скомпилированном исполняемом файле (расположенном в папке bin вашего проекта после сборки). Вы можете автоматически запустить его, добавив его как событие после сборки (щелкните правой кнопкой мыши проект> Свойства> События сборки> Командная строка события после сборки)

AlphaDelta 08.02.2019 01:15

Да, я проверял, когда он переполняется, и алгоритм все еще работает должным образом и не зацикливается бесконечно. Я также намеренно создал состояния, которые легко решать, и они быстро проходят через них. Когда я не использую поток, он переполняется после ~3000 вызовов, когда я использую поток, он переполняется после ~900 вызовов, независимо от того, сколько памяти я выделяю при создании потока.

Jacob Hall 08.02.2019 01:17

@JacobHall - Если вы столкнулись с переполнением стека, похоже, у вас проблема с кодированием - простое увеличение стека - это временное решение, которое может не решить вашу проблему. Ваш код может просто использовать весь стек независимо от его размера. Не могли бы вы опубликовать свой код, чтобы мы могли видеть?

Enigmativity 08.02.2019 01:28

@Enigmativity код функции слишком длинный, чтобы вставить его сюда. Здесь строка 297: github.com/jacobhall88/SudokuForms/blob/RecursiveSolution/…

Jacob Hall 08.02.2019 01:45

@mjwills Я прочитал это здесь: docs.microsoft.com/en-us/dotnet/api/… Там указано, что это было только для частично доверенных приложений, но мне трудно понять, происходит ли это здесь.

Jacob Hall 08.02.2019 01:47
I read that here Возможно, будет полезнее указать эту ссылку в вашем вопросе.
mjwills 08.02.2019 02:56

Выяснение того, как заставить программу не использовать рекурсию, вероятно, было бы хорошим учебным упражнением также, а также выяснение того, как увеличить стек. И то, и другое стоит изучить, но на практике удаление рекурсии будет встречаться чаще.

Jon Hanna 18.02.2019 17:41
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
9
9
7 087
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Возможно, установите размер резерва стека в Visual Studio:

Проект -> Свойства -> Свойства конфигурации -> Компоновщик -> Система -> Размер резерва стека

Это также можно сделать через командную строку или программно при создании потока:

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

Я использую VS 2017, и в Project -> Properties у меня нет подкатегории «Свойства конфигурации». В свойствах у меня есть следующие параметры: imgur.com/a/QqxxzQL Я читал, что это может быть связано с тем, что я вижу свойства решения, а не свойства проекта, но я также вижу это, когда щелкаю правой кнопкой мыши проект в обозревателе решений и перехожу к свойствам, которые способ

Jacob Hall 08.02.2019 01:22

Я считаю, что этот ответ применим только к проектам C++, не так ли?

AlphaDelta 08.02.2019 01:23

Да, @AlphaDelta верна. Это управляет переключателями командной строки, передаваемыми link.exe, который используется только для сборок C и C++. Вы даже не будете использовать эти параметры видеть в свойствах проекта для приложения, предназначенного для .NET.

Cody Gray 08.02.2019 03:01
Ответ принят как подходящий

Большое плохое предупреждение

Если вы используете рекурсию в программе и достигаете точки, когда наличие StackOverflowException представляет собой реальную угрозу, не рассматривайте увеличение размера стека как допустимое решение.

Если вы столкнулись с StackOverflowException, вы делаете что-то очень неправильно; вместо этого вы должны использовать Stack<T> для обработки в глубину или Queue<T> для обработки в ширину. Пример.


Решение

Этого можно добиться с помощью editbin.exe, который устанавливается вместе с этим пакетом; VC++ tools

Найдите адрес editbin.exe, мой находился по адресу C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.14.26428\bin\Hostx64\x64\editbin.exe, я бы предложил использовать Все от voidtools вместо ужасного поиска Microsoft, чтобы найти это.

Установите размер стека вручную

Перейдите в папку 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

Который устанавливает размер резерва стека на 2 МБ.

Благодаря этому я смог вдвое увеличить уровень рекурсии; (Резерв стека 1 МБ слева, резерв стека 2 МБ справа).

Recursion level

Установите размер стека автоматически

Щелкните правой кнопкой мыши свой проект и выберите «Параметры», затем нажмите «События сборки» и добавьте следующее к событиям после сборки:

"<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. Однако вы все равно увидите двукратное увеличение уровня рекурсии при переходе от резерва стека 1 МБ к резерву стека 2 МБ.

Это именно то, что мне было нужно! На самом деле я не буду использовать рекурсивную функцию для решения, ее неэффективность просто привела меня к этой новой вещи, которую я хотел изучить. Спасибо!

Jacob Hall 08.02.2019 02:28

Изменение стека управляемой программы — это то, что я, честно говоря, никогда не рассматривал, так что это определенно был достаточно интересный вопрос, чтобы его изучить, хотя бы просто так. В конце концов я узнал, что в чем разница между резервом стека и фиксацией стека был.

AlphaDelta 08.02.2019 02:36

Я получил ту же ошибку, когда путь к editbin.exe был неправильным, хотя я думаю, что это может быть общая ошибка. Попробуйте запустить команду в cmd, чтобы получить реальную ошибку.

AlphaDelta 08.02.2019 03:06

@AlphaDelta Максимальный уровень рекурсии, который я получаю, составляет 24080 с консольным приложением C#, .NET Core 3.1. Это намного меньше, чем у вас. любая идея? код: ` public void GetStackLimit (int index) { Console.WriteLine (index); Получитьлимит стека(++индекс);} `

Pingpong 03.10.2021 12:21

Другие вопросы по теме