题
我正在使用 MinGW 和 GCC 3.4.5 (mingw-special vista r3)。
我的 C 应用程序使用大量堆栈,所以我想知道是否有任何方法可以通过编程方式告诉剩余多少堆栈,以便在发现堆栈即将用完时我可以干净地处理这种情况。
如果不是,还有哪些其他方法可以解决可能耗尽堆栈空间的问题?
我不知道我将从多大的堆栈开始,所以也需要以编程方式识别它。
其他提示
getrusage 函数可以获取当前的使用情况。(看 man getrusage
).
这 getrlimit
在 Linux 中将有助于获取堆栈大小 RLIMIT_STACK
范围。
#include <sys/resource.h>
int main (void)
{
struct rlimit limit;
getrlimit (RLIMIT_STACK, &limit);
printf ("\nStack Limit = %ld and %ld max\n", limit.rlim_cur, limit.rlim_max);
}
请看一下 man getrlimit
。可以通过以下方式获取相同的信息 ulimit -s
或者 ulimit -a
堆栈大小行。还可以看看 setrlimit
允许设置限制的函数。但正如其他答案中提到的,如果您需要调整堆栈,那么您可能应该重新考虑您的设计。如果你想要一个大数组,为什么不从堆中获取内存呢?
从堆栈中取出局部变量的地址是可行的。然后在更嵌套的调用中,您可以减去另一个本地的地址以找到它们之间的差异
size_t top_of_stack;
void Main()
{
int x=0;
top_of_stack = (size_t) &x;
do_something_very_recursive(....)
}
size_t SizeOfStack()
{
int x=0;
return top_of_stack - (size_t) &x;
}
如果您的代码是多线程的,那么您需要在每个线程的基础上存储 top_of_stack 变量。
检查你的编译器是否支持 stackavail()
假设您知道完整堆栈的大小,您可能可以添加一些汇编代码来读取 ESP。
如果您读取 ESP 并将其保存在 main 函数中,您可以将当前的 ESP 与 main 中的 ESP 进行比较,看看 ESP 发生了多少变化。这将告诉您使用了多少堆栈。
这是一个我已经放弃的问题。通过大量的黑客攻击和(主要是)祈祷,您可以获得在给定时间在给定机器上运行的解决方案。但总的来说,似乎没有合适的方法来做到这一点。
您必须从程序外部获取堆栈位置和大小(在 Linux 上,您可能会从 /proc/<pid>/maps
)。在你的程序中,你必须以某种方式测试你在堆栈中的位置。使用局部变量是可能的,但不能真正保证它们实际上位于堆栈上。您还可以尝试使用某些程序集从堆栈指针寄存器获取值。
现在您已经知道了堆栈的位置、其大小和当前位置,并且您假设您知道堆栈的增长方向。你什么时候进入堆栈溢出模式?你最好不要在接近结束时这样做,因为你的估计(即。局部变量的地址或堆栈指针的值)可能有点过于乐观;对堆栈指针之外的内存进行寻址并不罕见。此外,您不知道任何给定函数(及其调用的函数)需要多少堆栈空间。所以最后你必须留出一些空间。
我只能建议你不要陷入这种混乱,并尽量避免非常深的递归。您可能还想增加堆栈大小;我相信,在 Windows 上你必须将其编译成可执行文件。
对于窗户:我在使用 Kernel32.dll 中的 VirtualQuery 函数之前已经完成了此操作。我只有一个 C# 示例,但它演示了该技术:
public static class StackManagement
{
[StructLayout(LayoutKind.Sequential)]
struct MEMORY_BASIC_INFORMATION
{
public UIntPtr BaseAddress;
public UIntPtr AllocationBase;
public uint AllocationProtect;
public UIntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
};
private const long STACK_RESERVED_SPACE = 4096 * 16;
public unsafe static bool CheckForSufficientStack(UInt64 bytes)
{
MEMORY_BASIC_INFORMATION stackInfo = new MEMORY_BASIC_INFORMATION();
UIntPtr currentAddr = new UIntPtr(&stackInfo);
VirtualQuery(currentAddr, ref stackInfo, sizeof(MEMORY_BASIC_INFORMATION));
UInt64 stackBytesLeft = currentAddr.ToUInt64() - stackInfo.AllocationBase.ToUInt64();
return stackBytesLeft > (bytes + STACK_RESERVED_SPACE);
}
[DllImport("kernel32.dll")]
private static extern int VirtualQuery(UIntPtr lpAddress, ref MEMORY_BASIC_INFORMATION lpBuffer, int dwLength);
}
顺便提一句:这段代码也可以在 StackOverflow 上的另一个问题上找到,这个问题是我在尝试修复代码中的错误时提出的: 算术运算导致不安全的 C# 溢出在此输入链接描述
也许这仅对 Windows 平台有帮助:
在你的exe的PE头(IMAGE_NT_HEADERS)中有一些记录,例如:
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; typedef struct _IMAGE_OPTIONAL_HEADER { ... DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; ... }
有一个简单的方法可以获得这些值:使用 GetModuleHandle(NULL) 将为您提供模块的映像库(句柄)、地址,您将在其中找到 IMAGE_DOS_HEADER 结构,该结构将帮助您找到 IMAGE_NT_HEADERS 结构(imagebase+IMAGE_DOS_HEADER.e_lfanew)-> IMAGE_NT_HEADERS,然后您就可以了将找到这些字段: 堆栈保留大小 和 堆栈提交大小.
操作系统将为堆栈分配的最大空间量是 SizeOfStackReserve。
如果您考虑尝试此操作,请告诉我,我会为您提供帮助。有一种方法可以获取某个点使用的堆栈大小。
在Linux上,您将调用GetRusage,并检查返回的结构Rusage的RU_ISRSS成员(积分Unshared堆栈尺寸)。
从 MINGW 站点及其 sourceforge 站点对补丁的跟踪中,我看到 2008 年 5 月围绕 getrusage 完成了一些补丁,看起来它已经得到了普遍支持很长一段时间了。您应该仔细检查 MinGW 支持多少典型 Linux 功能的任何警告。