在C#中检查堆栈大小

有没有办法在C#中检查线程堆栈大小?

这是一个如果你不得不问,你负担不起的情况 (Raymond Chen首先说的。)如果代码依赖于有足够的堆栈空间到必须首先检查的程度,那么它可能是值得的重构它以使用显式的Stack对象。 约翰关于使用分析器的评论是有用的。

也就是说,事实certificate有一种方法可以估算剩余的堆栈空间。 它不精确,但它足够有用,可以评估你的底部有多接近底部。 以下内容主要基于Joe Duffy撰写的精彩文章 。

我们知道(或将做出假设):

  1. 堆栈存储器分配在一个连续的块中。
  2. 堆栈从较高地址向较低地址“向下”增长。
  3. 系统需要在分配的堆栈空间底部附近留出一些空间,以便优雅地处理堆栈外exception。 我们不知道确切的保留空间,但我们会试图保守地约束它。

通过这些假设,我们可以调用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)。 鉴于1MB的默认堆栈大小,计数似乎是合理的。

 969332 969256 969180 969104 969028 968952 968876 968800 968724 968648 

为简洁起见,上面的代码假定页面大小为4K。 虽然这适用于x86和x64,但对于其他受支持的CLR架构可能不正确。 您可以将其转换为GetSystemInfo以获取计算机的页面大小( SYSTEM_INFO结构的dwPageSize)。

请注意,这种技术不是特别便携,也不是未来的证据。 pinvoke的使用限制了此方法对Windows主机的实用性。 关于CLR堆栈的连续性和增长方向的假设可能适用于当前的Microsoft实现。 但是,我(可能有限)读取CLI标准 (公共语言基础结构,PDF,长读取)似乎并不需要那么多的线程堆栈。 就CLI而言,每个方法调用都需要一个堆栈帧; 但是,如果堆栈向上增长,如果局部变量堆栈与返回值堆栈分开,或者堆栈帧是否在堆上分配,那么它可能并不在意。