测试栈空间的大小

比如局部变量是保存在栈空间中的,今天突然在想栈的上限是多大呢,什么时候才会栈溢出?

ulimit 命令

linux下使用ulimit 命令可以查看系统的很多上限值。

  • ulimit -a 查看所有

这里写图片描述

  • ulimit -s 查看栈空间的大小

这里写图片描述

可以看到系统设置栈的上限是8M

测试

现在我们写个程序测试一下

两种方法:

1、第一种方法:最简单的是在函数或直接在main()函数里定义多个局部变量。

这里写图片描述

局部变量一定要初始化,不然可能不会给分配内存

2、第二种方法:使用递归申请栈空间更合适,测试方便。

#include<stdio.h>
#include<stdlib.h>

int   i = 1;  //记录申请的次数

void func()
{
    char   a[1048576];  //一次申请 1 M 便于计算

    printf("NO.%d  %ld 字节 %p\n",i,sizeof(a),a);

    i++;
    func();
}

main()
{
    func();
}

这里写图片描述

共申请了7M ,第8次申请失败,因为栈里面还保存着其他数据,如main函数的参数,所以第8次申请不足1M,说明运行结果和ulimit查看的信息吻合。

3、物理内存有4G,那栈空间可以申请4G么?

设置栈的上限为4G

这里写图片描述

再次运行程序:
这里写图片描述

竟然真的申请了4G,这时候就想到了虚拟内存机制和linux交换空间。显然,这4G真的要物理内存给提供那是不够的,因为除了我们申请的4G ,还有其他进程,内核也需要内存空间,所以肯定使用了交换空间,物理内存不够的时候,把暂时不用的内存数据临时保存到了交换空间。我的交换空间分配了2G 。
申请成功了,但是明显感觉到了系统卡顿,有时侯在申请了接近3G 的时候会卡一下,此时应该是因为物理内存压力太大,正在往交换空间置换数据吧,然后剩下1G 申请完成。

4、冲动了!这次我们设置栈的上限为 7G > 4G物理内存+2G swap

这里写图片描述

运行:
这里写图片描述

成功了。特别卡,第一次直接系统死机了。

但是有点不解了,物理内存+交换空间 = 6G , 为何能申请7G ? 但是有时候又会中途失败,不能分配7G。

5、寻址空间

需不需要担心申请内存过多,比如像上面递归定义了超多字符串,那地址够用么,变量太多,首地址会不会重复呢?

这个不必担心,64位系统CPU的寻址空间是 2^64 , 0 ~ 0xffffffffffffffff 系统根本用不完这么大的地址空间。况且我们个人电脑的物理内存才是仅仅4G。

所以,64位系统会有很大一部分地址范围用不到,据说这个叫AMD64空洞

6、申请堆空间

#include<stdio.h>
#include<stdlib.h>

int   i = 1;  //记录申请的次数

void func()
{
    char   a[1048576] = {'1'};  //一次申请 1 M 便于计算
    char   *p = NULL;

    p = (char*)malloc(1048576);

    printf("NO.%d  %ld 字节 %p --><-- %p\n",i,sizeof(a),a,p);

    i++;
    func();
}

main()
{
    func();
}

这里写图片描述


这里有个问题:经多次测试,如果malloc动态申请的内存特别大时,如上图,依次分配的内存地址是递减的,好像不符合堆向上增长的特点。

7、32位环境测试

上面的代码以gcc -m32 编译运行


这里也有个问题:

堆内存地址开始是减小的:

这里写图片描述

减到足够小时又开始增大:

这里写图片描述

有时侯会出现,栈后面用到的地址空间是前面动态申请过的(并未释放)

8、有时候ulimit -s 设置数值过大时会失败,还会导致之后设置任何数值都不会成功。注销重新设置就可以了

这里写图片描述


总之,还是不要随便更改系统设置的栈大小,不然就会出现上面遇到的几个问题。一般系统默认设置栈段为8M、4M、2M或1M。

<think>我们想要测试栈空间大小,可以通过递归函数导致栈溢出,通过计算递归次数来估算栈空间大小。 注意:不同的系统和编译器设置可能有不同的栈大小,所以这个测试是平台相关的。 思路: 1. 编写一个递归函数,每次递归时在栈上分配一定大小的空间(比如一个局部数组),并记录递归深度。 2. 当发生栈溢出时(即出现段错误或栈溢出异常),程序会崩溃。我们可以通过捕获信号(如SIGSEGV)来在崩溃前输出当前的递归深度。 3. 但是,在C语言中,我们可以通过setjmp/longjmp来尝试从信号处理函数中恢复,但这样可能不可靠。另一种方法是让程序在崩溃前将深度写入文件,或者我们直接运行并观察崩溃时的深度。 然而,更简单且安全的方法是在程序崩溃前打印出递归深度(但可能来不及打印),或者使用静态变量记录深度,然后在信号处理函数中打印。 这里我们采用信号处理的方法来捕获栈溢出信号(通常是SIGSEGV),并在信号处理函数中打印递归深度。 注意:栈溢出可能触发SIGSEGV信号,我们可以为这个信号注册一个处理函数。 步骤: 1. 包含必要的头文件:<stdio.h>, <stdlib.h>, <signal.h>, <setjmp.h>(可选,这里我们不用setjmp,因为栈溢出后栈可能已经被破坏,恢复比较困难,我们只打印信息然后退出) 2. 定义一个全局变量(或静态变量)来记录递归深度。 3. 编写递归函数,每次递归深度加1,并在栈上分配一些空间(比如一个char数组,大小可以自定义,比如1KB,这样我们可以计算栈空间大小)。 4. 编写信号处理函数,在接收到SIGSEGV时打印当前递归深度,然后退出程序。 注意:在信号处理函数中,我们只能调用异步信号安全的函数。直接使用printf可能不安全,但在这里我们为了简单使用,因为程序即将退出。更安全的做法是使用write系统调用。 示例代码: 但是,请注意,递归函数中分配一个较大的局部数组,每次递归都会在栈上分配这个数组,这样我们可以通过每次递归消耗的栈空间(数组大小+函数调用开销)乘以深度来估算栈大小。 假设我们每次递归分配一个大小为alloc_size的数组,那么每次递归大约消耗alloc_size(加上一些其他开销,但我们可以忽略或估算)的栈空间。 因此,栈大小 ≈ 递归深度 * alloc_size 这里我们设置alloc_size为1024字节(1KB)。 另外,注意:信号处理函数中访问全局变量是安全的,因为我们的递归函数在深度递增后就会分配数组,然后递归,所以当栈溢出时,全局变量depth的值就是导致溢出的深度。 但是,栈溢出时,栈已经被破坏,所以我们在递归函数中不要做太多事情,以免在递增深度后还没来得及分配数组就溢出。所以我们将深度递增放在递归函数的开始,然后分配数组。 然而,在信号处理函数中,我们可能无法安全地调用exit,但我们可以使用_exit(它是异步信号安全的)来立即终止。 我们使用write系统调用来输出信息,因为它是异步信号安全的。 代码结构: 注意:由于栈溢出时栈空间不足,信号处理函数本身可能无法在栈上分配空间,所以我们需要确保信号处理函数不使用栈(或者使用备用栈,但这里不展开)。因此,我们尽量让信号处理函数简单。 这里我们使用一个简单的信号处理函数,通过write向标准错误输出信息。 另外,我们也可以使用静态变量来存储深度,这样在信号处理函数中可以访问。 但是,由于栈溢出可能发生在任何递归深度,我们需要确保在信号处理函数中能够访问到当前的深度值。 我们使用全局变量depth。 代码示例:</think>以下是一个用于测试栈空间大小的C语言代码示例,通过递归函数耗尽栈空间并捕获溢出信号: ```c #include <stdio.h> #include <signal.h> #include <stdlib.h> #include <setjmp.h> static jmp_buf env; static volatile int depth = 0; static const int ALLOC_SIZE = 1024; // 每次递归分配的栈空间大小(字节) void handle_sigsegv(int sig) { printf("\n
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值