Это случай , если вам нужно спросить, вы не можете себе это позволить (Рэймонд Чен сказал это первым.) Если код зависит от наличия достаточного стекового пространства в той степени, в которой он имеет чтобы проверить сначала, возможно, стоит реорганизовать его для использования явного объекта Stack<T>
. В комментарии Джона есть смысл использовать профилировщик.
Тем не менее, оказывается, что есть способ оценить оставшееся пространство стека. Это не точно, но достаточно полезно для оценки того, насколько вы близки к основанию. Нижеследующее в значительной степени основано на превосходной статье Джо Даффи .
Мы знаем (или сделаем предположения), что:
- Память стека выделяется в непрерывном блоке.
- Стек увеличивается «вниз», от старших адресов к младшим.
- Системе требуется некоторое пространство в нижней части выделенного стекового пространства, чтобы обеспечить плавную обработку исключений вне стека. Мы не знаем точно зарезервированное пространство, но мы попытаемся его консервативно связать.
С этими допущениями мы могли бы определить VirtualQuery , чтобы получить начальный адрес выделенного стека и вычесть его из адреса некоторой переменной, выделенной из стека (полученной с помощью небезопасного кода.) Далее вычесть нашу оценку пространства, необходимого системе в нижней части стека, даст нам оценку доступного пространства.
Приведенный ниже код демонстрирует это, вызывая рекурсивную функцию и записывая оставшееся оценочное пространство стека в байтах по порядку:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace ConsoleApplication1 {
class Program {
private struct MEMORY_BASIC_INFORMATION {
public uint BaseAddress;
public uint AllocationBase;
public uint AllocationProtect;
public uint RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
private const uint STACK_RESERVED_SPACE = 4096 * 16;
[DllImport("kernel32.dll")]
private static extern int VirtualQuery(
IntPtr lpAddress,
ref MEMORY_BASIC_INFORMATION lpBuffer,
int dwLength);
private unsafe static uint EstimatedRemainingStackBytes() {
MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
IntPtr currentAddr = new IntPtr((uint) &stackInfo - 4096);
VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));
return (uint) currentAddr.ToInt64() - stackInfo.AllocationBase - STACK_RESERVED_SPACE;
}
static void SampleRecursiveMethod(int remainingIterations) {
if (remainingIterations <= 0) { return; }
Console.WriteLine(EstimatedRemainingStackBytes());
SampleRecursiveMethod(remainingIterations - 1);
}
static void Main(string[] args) {
SampleRecursiveMethod(100);
Console.ReadLine();
}
}
}
А вот и первые 10 строк вывода (intel x64, .NET 4.0, debug). С учетом размера стека по умолчанию в 1 МБ счетчик выглядит правдоподобно.
969332
969256
969180
969104
969028
968952
968876
968800
968724
968648
Для краткости приведенный выше код предполагает размер страницы 4K. Хотя это справедливо для x86 и x64, это может быть неправильно для других поддерживаемых архитектур CLR. Вы можете ввести GetSystemInfo , чтобы получить размер страницы машины (dwPageSize структуры SYSTEM_INFO ).
Обратите внимание, что эта техника не особенно переносима и не является перспективной. Использование pinvoke ограничивает полезность этого подхода для хостов Windows. Предположения о непрерывности и направлении роста стека CLR могут быть верны для настоящих реализаций Microsoft. Однако мое (возможно ограниченное) чтение стандарта CLI (общеязыковая инфраструктура, PDF, длинное чтение), по-видимому, не требует столько стеков потоков. Что касается CLI, то для каждого вызова метода требуется кадр стека; однако это не должно волновать, если стеки растут вверх, если стеки локальных переменных отделены от стеков возвращаемых значений, или если кадры стека расположены в куче.