【小技巧】爆栈?栈的大小不够用怎么办?

爆栈?栈的大小不够用怎么办?

Linux系统栈帧大小

我们先从一段代码开始:

#include<stdio.h>

const int N = 4*1024*1024;

int func(){
    int buf[N];
    printf("%d\n", N);
    printf("%d\n", sizeof(buf));
    return 0;
}

编译执行上述代码,显而易见,报Segment Fault错误。

用如下指令查看系统栈帧的最大size:

ulimit -s
8192

好的,我们看到系统支持的最大栈帧大小为8192KB,也就是8MB。而上述代码中,我们开的数组大小达到了16MB,自然就爆栈了。

那么我们应该怎么办呢?

修改系统栈帧大小

使用如下代码将栈帧大小修改为102400KB。

ulimit -s 102400
【奇技淫巧】将栈转移到堆中

如果我们不想或者没有修改系统栈帧大小的权限,怎么办?

首先,来复习一下x86-64架构的函数栈帧:

在这里插入图片描述

我们可以看到,rbp指针指向栈底,rsp指针指向栈顶。当发生子函数调用的时候,将父函数rbp压栈,新函数的rbp为现在的rsp。当函数返回时,将子函数rbp赋值给rsp,然后父函数rbp出栈。

即在子函数调用发生前,只需要合理地修改rsp寄存器的值,就能够改变子函数的栈帧位置。

了解这些之后,我们考虑先在堆上申请一大块的内存区域,在main函数调用子函数之前,将rsp寄存器的内容替换为我们之前申请的内存区域的地址。这里需要注意的是,栈帧是从高地址向低地址生长的,而我们申请的内存区域是从低地址开始的,所以我们需要将rsp的值设置为申请的内存区域的最后一个地址。

具体代码,由于需要操作寄存器,所以用到了内联汇编:

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

const int N = 4*1024*1024;

int func(){
    int buf[N];
    printf("%d\n", N);
    printf("%d\n", sizeof(buf));
    return 0;
}

int main(){
    int bytes = 32*1024*1024;
    void *stack_top = malloc(bytes);            //申请一块比我们所需栈帧大小更大的内存区域
    void *stack_bottom = stack_top + bytes - 1;
    void *rsp;
    __asm__("movq %%rsp, %0":"=m"(rsp)::);  //先记录下当前的rsp值
    __asm__("movq %0, %%rsp"::"m"(stack_bottom):);  //修改rsp值
    func();                     
    __asm__("movq %0, %%rsp"::"m"(rsp):);   //恢复rsp值
    return 0;
}

编译运行,完美运行!

需要注意的是,系统给栈帧设置大小的一个原因是为了不让栈帧不断生成的数据最终破坏我们的其它数据,因此不能够让栈帧无限增长,在栈帧区域的最顶端有一个保护帧,当我们访问到保护帧时,会触发Segment Fault异常。所以当我们使用上述技巧替换栈帧位置的时候,要确保栈帧的大小始终都在设置的范围内,而不会影响其它的数据。

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ACM的,你懂得 ACM做题过程中的一些小技巧。 1.一般用C语言节约空间,要用C++库函数或STL时才用C++; cout、cin和printf、scanf最好不要混用。 大数据输入输出时最好不要用cin、cout,防止超时。 2.有时候int型不够用,可以用long long或__int64型(两个下划线__)。 值类型表示值介于 -2^63 ( -9,223,372,036,854,775,808) 到2^63-1(+9,223,372,036,854,775,807 )之间的整数。 printf("%I64d",a); //__int64 一般VC编译器使用(虽然有的OJ用g++,但是动态链接库用的windows的,所以要用%I64d输入输出) printf("%lld",a); //long long 一般g++编译器使用 3.OJ判断是只看输出结果的,所以不要要多余的提示输出。 所以大部分题处理一组数据后可以直接输出,就不需要用数组保存每一个Case的数据。 while(case--) { scanf(...); ...... printf(...); } 4.纯字符串用puts()输出。 数据大时最好用scanf()、printf()减少时间。 先用scanf(),再用gets()会读入回车。所以在中间加一个getchar(); scanf("%c%c",&c1,&c2)会读入空格;建议用%s读取字符串,取第一个字符。 5. 读到文件的结尾,程序自动结束 while( ( scanf(“%d”, &a) ) != -1 ) while( ( scanf(“%d”, &a) ) != EOF) while( ( scanf(“%d”, &a) ) == 1 ) while( ~( scanf(“%d”, &a) ) ) 读到一个0时,程序结束 while( scanf(“%d”, &a) , a) while( scanf(“%d”, &a)!=EOF && a) 读到多个0时,程序结束 while( scanf(“%d%d%d”, &a, &b, &c), a+b+c ) //a,b,c非负 while( scanf(“%d%d%d”, &a, &b, &c), a|b|c ) 6.数组定义int a[10] = {0};可以对其全部元素赋值为0; 数组太大不要这样,防止CE。 全局变量,静态变量自动初始化为0; 函数中定义的变量存储在空间中,数组太大需要定义为全局变量(存储在堆空间中)。 7.有很多数学题是有规律的,直接推公式或用递归、循环。 8.圆周率=acos(-1.0) 自然对数=exp(1.0) 9.如果要乘或除2^n,用位移运算速度快。a>>n;a<b?a:b; } int gcd(int m,int n) { return n?gcd(n,m%n):m; } int abs(int a) { return an; } sort(a,a+n,cmp); 14.有的题数据范围小但是计算量大可以用打表法 先把结果算出来保存在数组里,要用时直接取出来。 15.浮点数比较时最好控制精度 #define eps 1e-6 fabs(a-b)<eps 16.有些字符串与整型的转换函数是非标准的 可以使用sscanf()和sprintf()代替 sscanf(s,"%d",&n);//从字符串s中读入整数n sprintf(s,"%d",n);//将n转换为字符串s
JVM内存模型是Java虚拟机在运行时对内存的组织和管理方式。它主要包括堆、、方法区、程序计数器和本地方法等不同的内存区域。 堆是Java程序运行时动态分配对象的区域,存放的是实例对象。堆可以进一步细分为新生代和老年代等不同的区域,用于实现垃圾回收机制。 是线程私有的,用于存储线程的局部变量、方法参数以及方法调用的状态等。是一个后进先出(LIFO)的数据结构。 方法区是用于存储已被加载的类信息、常量、静态变量、编译器编译后的代码等数据。在Java 8之前,方法区被实现为永久代(PermGen),而在Java 8之后,它被实现为元空间(Metaspace)。 程序计数器是每个线程私有的,用于指示当前线程执行的字节码行号。 本地方法类似于,用于存储本地方法的信息。 对于内存优化方面,以下是一些常见的优化技巧: 1. 减少对象创建:避免过多地创建临时对象,尽量使用基本数据类型或复用对象。 2. 合理使用缓存:将经常使用的数据缓存起来,减少对磁盘或网络的访问。 3. 使用适当的数据结构和算法:选择合适的数据结构和算法可以提高程序的性能。 4. 避免过度同步:合理使用同步机制,避免过多地使用锁,可以提高程序的并发性能。 5. 对资源的正确释放:及时释放不再使用的资源,如关闭文件、数据库连接等。 6. 配置合理的堆大小和垃圾回收参数:根据应用程序的需求和硬件环境,调整堆大小和垃圾回收参数,以提高垃圾回收的效率。 7. 使用性能分析工具:使用性能分析工具来帮助定位和解决性能瓶颈问题。 请注意,这些只是一些常见的内存优化技巧,具体的优化策略还需要根据具体的应用场景和需求进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值