2010-05-24 132 views
10

有沒有辦法在C#中檢查線程堆棧大小?在C中檢查堆棧大小#

+3

你爲什麼想要? – 2010-05-25 00:03:23

+0

據我所知,你不能。至少不使用本地方法。 – Propeng 2010-05-25 00:18:04

+0

我想知道在某個時間點使用多少堆棧。比方說,我稱爲遞歸方法10次,我想知道在那個點使用(或左)多少堆積 – Gjorgji 2010-05-25 00:33:43

回答

14

這是一個if you have to ask, you can't afford it的情況(Raymond Chen先說。)如果代碼依賴於有足夠的堆棧空間以至於必須首先檢查,則可能需要重構它以使用明確的Stack<T>對象。 John的評論中有關於使用探查器的評論。

這就是說,事實證明有一種方法可以估計剩餘的堆棧空間。這並不準確,但它足以用來評估你是多麼接近底部。以下內容主要基於excellent article by Joe Duffy

我們知道(或將使假設)說:

  1. 棧內存在一個連續的塊分配。
  2. 堆棧從較高地址向較低地址增長'向下'。
  3. 系統在分配的堆棧空間底部附近需要一些空間,以便優先處理堆棧外的異常。我們不知道確切的預留空間,但我們會嘗試保守地限制它。

在這些假設下,我們可以的PInvoke 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行(英特爾64, .NET 4.0,調試)。鑑於1MB的默認堆棧大小,計數似乎是合理的。

969332 
969256 
969180 
969104 
969028 
968952 
968876 
968800 
968724 
968648 

爲簡潔起見,上面的代碼假設頁面大小爲4K。儘管這對x86和x64有效,但對於其他支持的CLR體系結構可能並不正確。您可以調入GetSystemInfo以獲取機器的頁面大小(SYSTEM_INFO結構的dwPageSize)。

請注意,這種技術不是特別便攜,也不是未來的證明。使用pinvoke會限制此方法對Windows主機的實用性。關於CLR棧的連續性和增長方向的假設對於現在的Microsoft實現可能適用。但是,我的(可能有限的)CLI standard(通用語言基礎結構,PDF,長讀)似乎並不需要儘可能多的線程堆棧。就CLI而言,每個方法調用都需要一個堆棧幀;但是,如果堆棧向上增長,如果局部變量堆棧與返回值堆棧分離,或者堆棧分配在堆棧上,它並不在意。

+0

+1的解釋和細節。 – 2010-05-25 08:16:53

+1

如果有人要求一個常數,「一個程序可以安全使用多少堆棧」,我會同意「IYHTA,YCAI」的理念。另一方面,如果有人正在寫一些像解析器一樣的地方,那麼可以使用遞歸來處理輸入上任何期望的嵌套結構級別,那麼遞歸檢查剩餘堆棧空間並且調用「嵌套太深「如果它不足,則是例外,而不是對嵌套施加任意限制。 – supercat 2011-04-29 21:04:40

+1

這種檢查在調試中也可能非常有用,可以在您正在運行堆棧溢出的情況下設置斷點。一個斷點將允許你去到調用堆棧的開始處並檢查每個變量。一旦拋出StackOverflowException,Visual Studio就不能再讀取變量,這太遲了。 – ygoe 2014-06-07 19:01:11