2011-12-30 68 views
5

我们如何获取此信息?我想这是依赖于操作系统,我运行Windows,所以我的问题是指Windows API。我们如何查询堆栈状态 - 未使用(可用)内存

是否有任何函数可以为我们做 - 为调用线程获取剩余的堆栈内存?

另外,如果我们可以找出以下信息,我们就可以计算出我们自己:

  1. 获取线程栈基址。必须有一些函数以线程标识符作为参数,并返回一些关于它的信息(如...堆栈基址?)
  2. 获取线程堆栈大小。如果线程是由我们启动的,我们可以知道它(因为我们在调用CreateThread时指定了它)。但是,如果它是主线程(由操作系统为我们的程序启动,或者其他任何线程没有明确启动),我们如何找到它?
  3. 获取当前堆栈指针。那么,这很容易。我们可以用esp来检查它,或者取一个局部变量的地址来得到一个很近的位置。

这是用于教育目的,但我想它可以用来阻止递归算法导致堆栈溢出 - 而不是使用任何最大深度限制函数。

+0

请注意[Itanium有*两个堆栈*](http://blogs.msdn.com/b/oldnewthing/archive/2005/04/21/410397.aspx)。到目前为止,每个人都只关注本地变量栈,但也有寄存器栈(这是返回地址去哪里等)。所以这种技术不足以防止堆栈溢出,因为您只检查其中一个堆栈。 – 2011-12-31 15:49:39

+0

有趣......但如果这个“寄存器堆栈”实际上是与硬件相关的,那么如果寄存器堆栈已满,那么不应该由Intel或者实现它的人使用常规调用堆栈来进行局部变量分配;毕竟,使用寄存器来存储局部变量不是必须的,不是吗? – Jong 2011-12-31 17:17:53

+0

没有法律规定所有处理器必须与x86相同。 (Itanium几乎不是唯一带有注册窗口的处理器。) – 2011-12-31 18:14:04

回答

5

您可以使用NtCurrentTeb(),它可以让你的指针TEB这有NT_TIB作为它的第一个成员。 :

typedef struct _NT_TIB 
{ 
    PEXCEPTION_REGISTRATION_RECORD ExceptionList; 
    PVOID StackBase; 
    PVOID StackLimit; 
    PVOID SubSystemTib; 
    // .... 
} NT_TIB, *PNT_TIB; 
+0

什么是TEB,无论如何? – 2011-12-30 20:23:39

+2

线程环境块(http://msdn.microsoft.com/en-us/library/windows/hardware/ms686708)从我错过了NT_TIB ... :-( – alk 2011-12-30 20:27:06

+0

@Pete Wilson:线程环境块,其中存储了各种线程专有数据 – wj32 2011-12-30 20:28:21

-1

编辑:这是一个出色的教育目的出色的问题!有一个upvote。堆栈空间是固定的编辑:在进程或线程开始执行的点;例如,我认为你一定是指从内存中动态分配内存的堆(例如malloc()。关于这个问题在这里有很好的讨论on MSDN。我没有看到你正在寻找的确切的API调用:你“将不得不闲逛为该;它也不能离得太远

HTH

+0

调用'CreateThread'时,您可以指定线程的堆栈大小。也许这个空间一旦确定就固定了,但是每个线程可能有另一个堆栈大小。 .NET线程通常具有〜1MB的堆栈,我认为C程序的通常线程有64KB。我做了很多不同大小的实验,甚至1GB(允许的最大窗口是1.4GB),它可以让你做..无限递归,非常多。总之,堆栈大小是**不是**固定的。 – Jong 2011-12-30 19:58:13

+0

通常,在创建线程时,可以为该线程指定堆栈大小。例如,如果使用pthread_create(),则可以传递包含堆栈大小的属性。您还可以分别使用pthread_attr_getstackaddr()和pthread_attr_getstacksize()获取当前线程的堆栈基址和大小。不过,我不知道在Windows上。 – user1118321 2011-12-30 20:00:10

+0

@Jong:是的。实际上有两个堆栈“大小”:保留和提交。只要有足够的地址空间,堆栈可以轻松调整大小。 – wj32 2011-12-30 20:15:42

1

没有直接回答OP的问题,而是指在提到它的想法是结尾:” ......它可以用来ŧ Ø从导致堆栈溢出停止递归算法 - 而不是使用任何最大深度限制功能”

在Windows API提供的方法SetThreadStackGuarantee(),允许定义的最小堆栈大小,以保持投掷的堆栈溢出时例外。此方法与VC运行时库方法_resetstkoflw()一起可能使从堆栈溢出中恢复成为可能。

有关详细信息,请参阅MSDN上的this

1
  1. 获取线程堆栈基地址:作为wj32 showed,使用线程信息块的StackBase

  2. 获取线程堆栈大小:确定线程保留堆栈大小(它是最大大小)是不同的。 StackLimit显示的是lowest commited address,它可以显示堆栈有多大,而不是限制。也不是您传递给CreateThread的堆栈大小是初始提交大小,除非您通过STACK_SIZE_PARAM_IS_A_RESERVATION标志,否则不是预留大小。程序的堆栈大小由linker parameter指定,如果不指定,则默认为1MB。所以很可能所有的线程都有1MB的堆栈保留。

    由于最后一页的stack is a guard page你可以想见,从StackPage开始,检查每一个较低的栈页面VirtualQuery找到gaurd网页,将在堆栈的结束。这当然完全依赖于实现定义的行为。

  3. 获取当前堆栈指针:您可以使用StackLimit,让您的堆栈的最大大小COMMITED,但是这是不一样的当前指针。 esp显然是当前的堆栈位置,可能会高于StackLimit

关于保留vs提交的注意事项。在Windows中保留意味着虚拟地址已被保留供使用,并且不能用于其他目的。保留地址不消耗任何物理或虚拟内存。一旦它被提交,地址将被映射到物理或虚拟内存并且可以被使用。 Windows用户线程具有固定的堆栈保留大小 - 为堆栈保留地址空间并且不能增加和变量提交大小 - 堆栈将仅使用(提交)内存,因为它需要它。

编辑

我在检查gaurd页面将无法正常工作的想法。我编写了一个测试程序,警卫页面被设置为提交限制,所以这是行不通的。但是我确实发现,在堆栈上的任何位置运行VirtualQuery将给出堆栈中最低地址的AllocationBase,因为保留区大小一次分配。以下示例显示了这个动作:

#include <windows.h> 
#include <WinNT.h> 
#include <stdio.h> 

DWORD GetThreadStackSize() 
{ 
    SYSTEM_INFO systemInfo = {0}; 
    GetSystemInfo(&systemInfo); 

    NT_TIB *tib = (NT_TIB*)NtCurrentTeb(); 
    DWORD_PTR stackBase = (DWORD_PTR)tib->StackBase; 

    MEMORY_BASIC_INFORMATION mbi = {0}; 
    if (VirtualQuery((LPCVOID)(stackBase - systemInfo.dwPageSize), &mbi, sizeof(MEMORY_BASIC_INFORMATION)) != 0) 
    { 
     DWORD_PTR allocationStart = (DWORD_PTR)mbi.AllocationBase; 
     return stackBase - allocationStart; 
    } 
    return 0; 
} 

DWORD WINAPI ThreadRtn(LPVOID param) 
{ 
    DWORD stackSize = GetThreadStackSize(); 
    printf("%d\n", stackSize); 
    return 0; 
} 

int main() 
{ 
    ThreadRtn(NULL); 
    HANDLE thread1 = CreateThread(NULL, 65535, ThreadRtn, NULL, 0, NULL); 
    WaitForSingleObject(thread1, -1); 
    HANDLE thread2 = CreateThread(NULL, 65535, ThreadRtn, NULL, STACK_SIZE_PARAM_IS_A_RESERVATION, NULL); 
    WaitForSingleObject(thread2, -1); 

    return 0; 
} 

此输出:

,它应。

+0

确实如此。我设法做了一些更多的测试,例如一个方法需要多少个字节,以及给定点上堆栈中剩余的字节数。 – Jong 2011-12-31 19:10:24