C# · 12月 30, 2021

在C#中检查堆栈大小

有没有办法在C#中检查线程堆栈大小?解决方法 这是 if you have to ask,you can’t afford it的一个例子(Raymond Chen首先表示).如果代码依赖于有足够的堆栈空间到必须首先检查的程度,可能值得重构以使用显式的 Stack<T>对象.约翰对于使用分析器的评论有其优点.

也就是说,事实证明,有一种方法来估计剩余的堆栈空间.这不是精确的,但它足以用于评估您接近底部的目的.以下是基于excellent article by Joe Duffy.

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

>堆栈内存被分配在一个连续的块中.
>堆栈向下增长,从较高的地址到较低的地址.
>系统在分配的堆栈空间底部附近需要一些空间,以便优化处理堆栈外的异常.我们不知道确切的保留空间,但是我们将尝试保守地约束它.

有了这些假设,我们可以引导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,调试).给定1MB默认堆栈大小,计数似乎合理.

969332969256969180969104969028968952968876968800968724968648

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

请注意,这种技术不是特别便携式的,也不是未来的证明.使用pinvoke将此方法的用途限制在Windows主机上.关于CLR堆栈的连续性和增长方向的假设可能适用于当前的Microsoft实现.然而,我(可能有限的)读取CLI standard(通用语言基础设施,PDF,长读)似乎并不要求尽可能多的线程堆栈.就CLI而言,每个方法调用都需要一个堆栈帧;但是,如果堆栈向上增长,如果本地变量堆栈与返回值堆栈分离,或堆栈帧是否在堆上分配,则它不在乎.