C# 在C中检查堆栈大小#

C# 在C中检查堆栈大小#,c#,multithreading,stack,C#,Multithreading,Stack,有没有办法检查C#中的线程堆栈大小?这是一个例子(Raymond Chen首先说)。如果代码依赖于有足够的堆栈空间,以至于必须先检查,那么重构它以使用显式对象可能是值得的。John关于改用探查器的评论有其优点 也就是说,事实证明有一种方法可以估计剩余的堆栈空间。这并不精确,但它对于评估你到底有多接近底部是非常有用的。以下内容在很大程度上基于一个实例 我们知道(或将作出假设): 堆栈内存在连续块中分配 堆栈“向下”增长,从较高的地址向较低的地址增长 系统需要在分配的堆栈空间底部附近留出一些空间,以

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

这是一个例子(Raymond Chen首先说)。如果代码依赖于有足够的堆栈空间,以至于必须先检查,那么重构它以使用显式对象可能是值得的。John关于改用探查器的评论有其优点

也就是说,事实证明有一种方法可以估计剩余的堆栈空间。这并不精确,但它对于评估你到底有多接近底部是非常有用的。以下内容在很大程度上基于一个实例

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

  • 堆栈内存在连续块中分配
  • 堆栈“向下”增长,从较高的地址向较低的地址增长
  • 系统需要在分配的堆栈空间底部附近留出一些空间,以允许优雅地处理堆栈外异常。我们不知道确切的保留空间,但我们将尝试保守地限制它
  • 根据这些假设,我们可以pinvoke获得分配堆栈的起始地址,然后从某个堆栈分配变量(使用不安全代码获得)的地址中减去它。进一步减去我们对堆栈底部系统所需空间的估计,我们就可以估计出可用空间

    下面的代码通过调用递归函数并以字节为单位写出剩余的估计堆栈空间来演示这一点:

    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();
            }
        }
    }
    
    为简洁起见,上面的代码假定页面大小为4K。虽然这对x86和x64适用,但对其他受支持的CLR体系结构可能不正确。您可以将pinvoke插入以获取计算机的页面大小(结构的dwPageSize)


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

    我添加这个答案以供将来参考。:-)

    Oren回答了SO的问题(如注释所细化的),但它并没有指出实际为堆栈分配了多少内存。要得到这个答案,可以使用Michael Ganß的答案,我在下面使用了一些更新的C#语法对其进行了更新

    有趣的是(也是我发布这篇文章的原因)使用不同配置运行时的输出。作为参考,我使用.NETFramework4.7.2在Windows10Enterprise(Build1709)64位操作系统上运行它(如果有必要的话)

    释放|任何CPU(首选选中32位选项): 释放|任何CPU(首选未选中的32位选项): 发布| x86: 发布| x64: 这些结果没有什么特别令人震惊的,因为它们与文档一致。不过,有点令人惊讶的是,在版本中运行时,默认堆栈大小为1 MB;未选中首选32位选项的任何CPU配置,这意味着它在64位操作系统上作为64位进程运行。我假设在这种情况下默认的堆栈大小是4MB,就像发行版的| x64配置一样


    在任何情况下,我希望这对像我一样来到这里想知道.NET线程堆栈大小的人都有用。

    据我所知,你不能。至少不使用本机方法。我想知道在某个时间点使用了多少堆栈。假设我调用一个递归方法10次,我想知道在那个点上使用了(或留下了)多少堆栈。为此,使用分析器。不要试图自己去做。你的程序会利用这些信息做些什么吗?在某些情况下,知道堆栈大小会很有用。我之所以研究它,是因为我正在考虑嵌入一种作为编译代码运行的脚本语言,我想在编译后的脚本中插入代码,以监控和限制其自身的内存使用。如果有人要求一个常量,“一个程序可以安全使用多少堆栈”,我会同意“IYHTA,YCAI”的理念。另一方面,如果编写类似于解析器的东西,可以使用递归来处理输入上任何预期级别的嵌套结构,那么让递归检查剩余堆栈空间并调用抛出“嵌套太深”异常(如果不充分)似乎会更干净,而不是对嵌套施加一些任意限制。此检查在调试时也很有用,可以在堆栈溢出的情况下设置断点。断点将允许您转到调用堆栈的开头并检查每个变量。一旦抛出StackOverflowException,Visual Studio就无法再读取变量,为时已晚。感谢您的发现,我还对1MB的任何CPU(首选未选中的32位选项)感到震惊。因此,即使Environment.Is64BitProcess为真,它也是1MB。对于
    net5.0
    (以及.NET Core的早期版本),main的输出是“主堆栈大小:1536KB”。因此.NET核心的堆栈大小增加了50%。但是,当我将配置更改为Release | x64时,该输出不会更改,这是意外的。我在VisualStudio中使用配置管理器进行了实验。
    969332
    969256
    969180
    969104
    969028
    968952
    968876
    968800
    968724
    968648
    
    public static class Extensions
    {
        public static void StartAndJoin(this Thread thread, string header)
        {
            thread.Start(header);
            thread.Join();
        }
    }
    
    class Program
    {
        [DllImport("kernel32.dll")]
        static extern void GetCurrentThreadStackLimits(out uint lowLimit, out uint highLimit);
    
        static void WriteAllocatedStackSize(object header)
        {
            GetCurrentThreadStackLimits(out var low, out var high);
            Console.WriteLine($"{header,-19}:  {((high - low) / 1024),4} KB");
        }
    
        static void Main(string[] args)
        {
            WriteAllocatedStackSize("Main    Stack Size");
    
            new Thread(WriteAllocatedStackSize, 1024 *    0).StartAndJoin("Default Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 *  128).StartAndJoin(" 128 KB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 *  256).StartAndJoin(" 256 KB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 *  512).StartAndJoin(" 512 KB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 * 1024).StartAndJoin("   1 MB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 * 2048).StartAndJoin("   2 MB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 * 4096).StartAndJoin("   4 MB Stack Size");
            new Thread(WriteAllocatedStackSize, 1024 * 8192).StartAndJoin("   8 MB Stack Size");
        }
    }
    
    Main    Stack Size :  1024 KB
    Default Stack Size :  1024 KB // default stack size =   1 MB
     128 KB Stack Size :   256 KB // minimum stack size = 256 KB
     256 KB Stack Size :   256 KB
     512 KB Stack Size :   512 KB
       1 MB Stack Size :  1024 KB
       2 MB Stack Size :  2048 KB
       4 MB Stack Size :  4096 KB
       8 MB Stack Size :  8192 KB
    
    Main    Stack Size :  4096 KB
    Default Stack Size :  4096 KB // default stack size =   4 MB
     128 KB Stack Size :   256 KB // minimum stack size = 256 KB
     256 KB Stack Size :   256 KB
     512 KB Stack Size :   512 KB
       1 MB Stack Size :  1024 KB
       2 MB Stack Size :  2048 KB
       4 MB Stack Size :  4096 KB
       8 MB Stack Size :  8192 KB