局部变量太大导致栈溢出
2016-07-17 12:18
253 查看
局部变量太大导致栈溢出
问题:昨天,有同学遇到栈溢出的问题。在做大三小学期项目时,需要一个750x750的矩阵。于是在栈中定义了一个二维数组。为了说明问题,做如下简化:
/* 测试环境: window平台 vs2013 */ int main() { //占用栈内存,局部变量,太大,栈溢出 double test[750][750]; return 0; }
这看似没有问题,定义了一个变量,不大,才4.5M左右。可是,当运行时出现了栈溢出。什么情况?
编译器和操作系统背后的原理:
操作系统为例实现对用户程序的管理,使用进程+线程来运行我们的程序。涉及到线程,必须得考虑操作系统和编译器。
操作系统,例如Linux,使用
vm_area_struct结构体来管理用户空间,在加载一个elf或其他格式的可执行文件时总是会参考文件中给出的信息来设置这个结构体中的内容。其中代码和只读数据段可能就由同一个
vm_area_struct来管理、栈区由另一个
vm_area_struct来管理、堆区也有一个
vm_area_struct、共享区有一个
vm_area_struct、映射文件有自己
vm_area_struct。所有的
vm_area_struct按地址大小连成一个
vm_area_struct链表。如果
vm_area_struct过多,貌似超过32个时,操作系统就会为其建立一个红黑树,加快查找过程。
在
task_struct中有一个数组
rlim用来记录一个对进程分配资源的限制,数组的第
RLIMIT_STRACK项记录了栈的大小限制。
struct task_struct { ... struct signal_struct *signal; ... } struct signal_struct { ... /* * We don't bother to synchronize most readers of this at all, * because there is no reader checking a limit that actually needs * to get both rlim_cur and rlim_max atomically, and either one * alone is a single word that can safely be read normally. * getrlimit/setrlimit use task_lock(current->group_leader) to * protect this instead of the siglock, because they really * have no need to disable irqs. */ struct rlimit rlim[RLIM_NLIMITS];//记录了对RLIM_NLIMITS份资源的限制 }
struct rlimit
struct rlimit { unsigned long rlim_cur; //软限制。用户可以更改,但上限是硬限制。 unsigned long rlim_max; //硬限制。除了超级用户之外,其他用户不可更改。 };
init_task
那么,所有进程的
rlimit数组什么时候被设置呢?由用户创建的每个进程都继承其父进程
rlimit数组的内容。
在系统静态的创建第一个内核进程时,为
idle[init_task]进程静态的初始化了一个
rlimit数组:
#define INIT_RLIMITS \ { \ [RLIMIT_CPU] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_FSIZE] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_DATA] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_STACK] = { _STK_LIM, _STK_LIM_MAX }, \ [RLIMIT_CORE] = { 0, RLIM_INFINITY }, \ [RLIMIT_RSS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_NPROC] = { 0, 0 }, \ [RLIMIT_NOFILE] = { INR_OPEN, INR_OPEN }, \ [RLIMIT_MEMLOCK] = { MLOCK_LIMIT, MLOCK_LIMIT }, \ [RLIMIT_AS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_LOCKS] = { RLIM_INFINITY, RLIM_INFINITY }, \ [RLIMIT_SIGPENDING] = { MAX_SIGPENDING, MAX_SIGPENDING }, \ [RLIMIT_MSGQUEUE] = { MQ_BYTES_MAX, MQ_BYTES_MAX }, \ } /* * Limit the stack by to some sane default: root can always * increase this limit if needed.. 8MB seems reasonable. */ #define _STK_LIM (8*1024*1024) #ifndef RLIM_INFINITY #define RLIM_INFINITY (~0UL)//0取反,自然是能表示的最大值。 #endif #ifndef _STK_LIM_MAX #define _STK_LIM_MAX RLIM_INFINITY #endif
如果超级用户不做更改,所有其他进程的
rlimit最初的祖先就是这个进程的
rlimit【在创建一个进程时,在
do_fork中会调用
copy_signal函数将父进程的
rlimit拷贝到子进程】。
分配栈内存
应该可以这么理解:进程的栈有一个
vm_area_struct,在编译器中可以设置他的大小:
可以看到指定虚拟内存中栈分配的合计大小默认为1MB。不过,我们可以在
堆栈保留大小选项中调整大小。对于
linux操作系统,栈的
vm_area_struct的大小由
rlim[RLIMIT_STACK].rlim_cur限制。在代码中可以更改
rlim[RLIMIT_STACK].rlim_cur的大小,甚至可以将其值设置为无穷大,完全不对栈的大小设置限制。
栈溢出
以下内容参考自《Linux内核代码情景分析》.2.5节:
假设在进程运行过程中,栈已经增长到了
vm_area_struct的边界,在下一次压栈操作时,访问的虚拟地址落在了
空洞中,正常情况下就会产生栈溢出,报出段错误。但是栈区是个特例。
这个落在空洞中的push访存操作导致缺页异常,缺页异常发现地址就在栈区附近。所以,异常处理程序会检查
rlim[RLIMIT_STACK].rlim_cur的限制,如果对这个地址的访问不超出这个限制,就使用
expand_stack()扩展
vm_area_struct的边界,将这个地址包含进去。否则,访问出错,报出段错误。
好了,说明白了原理?下面分析出错原因。
我们看到上面的代码只是定义了一个局部变量,并没有显示的进程内存访问。按理说没有访存就不会有段错误啊?
其实是有访存的,可以参考动态分配栈内存之alloca内幕。 在代码需要一块局部栈变量时,他会在代码中调用
__alloca_probe在栈中申请内存,最后还要对申请的内存测试
test dword ptr [eax],eax,这里就存在访存操作,如果内存访问不合理,程序就会报出栈溢出错误。这样就能在代码测试的早期定位出错位置,而不至于运行到某个某名奇妙的地方才报出错误。
怎么解决
明白是栈内存不够,接下来怎么办呢?
增大栈大小,默认可以无限制的增大。
使用堆内存。
使用静态区内存【全局变量或者静态变量】。
references:
Change stack size for a C++ application in Linux during compilation with GNU compiler动态分配栈内存之alloca内幕
相关文章推荐
- IE7降低内存和降低CPU的几个技巧
- 如何高效的使用内存
- DOS下内存的配置
- XP/win2003下发现1G的内存比512M还慢的解决方法
- PowerShell实现动态获取当前脚本运行时消耗的内存
- C#实现把dgv里的数据完整的复制到一张内存表的方法
- SQL语句实现查询SQL Server内存使用状况
- C语言内存对齐实例详解
- C++基于栈实现铁轨问题
- c语言全局变量和局部变量问题及解决汇总
- 深入探讨C语言中局部变量与全局变量在内存中的存放位置
- 深入学习C语言中memset()函数的用法
- sqlserver 局部变量的使用
- 浅析C语言中堆和栈的区别
- C语言栈的表示与实现实例详解
- C语言实现颠倒栈的方法
- 全局变量与局部变量在内存中的区别详细解析
- 算法系列15天速成 第十天 栈
- VB读取线程、句柄及写入内存的API代码实例