什么是栈溢出
一、什么是栈溢出
栈溢出(Stack Overflow)就是指在程序运行过程中,往栈里存放的数据超过了栈所能容纳的最大容量,从而导致程序出现异常行为的情况。这就好比一个箱子本来只能装一定数量的物品,硬要往里面塞更多的东西,最后东西就会 “溢出来”,影响到箱子周围的其他物品甚至整个存放系统的正常运作。
在计算机程序的运行时内存布局中,栈是一种用于存储局部变量、函数调用信息(如返回地址、参数等)的数据结构。它按照后进先出(LIFO)的原则进行操作。当程序不断地向栈中压入数据(比如不断地调用函数,每个函数调用都会在栈上分配空间用于存储局部变量和调用相关信息),而没有及时进行出栈操作释放空间,一旦栈的使用超过了其所能容纳的最大范围,就会发生栈溢出。
二、什么情况下会栈溢出(以 C 语言举例)
(一)递归调用过深
当一个函数在其内部不断地调用自身,并且没有合理的终止条件或者终止条件很难达到时,就会导致栈不断地增长,最终可能引发栈溢出。以下是一个计算阶乘的递归函数示例,当输入的数值较大时,就可能出现栈溢出的情况:
#include <stdio.h>int factorial(int n) {if (n == 0 || n == 1) {return 1;}return n * factorial(n - 1); }int main() {int result = factorial(10); // 如果这里传入一个较大的值,可能导致栈溢出printf("阶乘结果: %d\n", result);return 0; }在上述代码中,当调用factorial函数时,每一次递归调用都会在栈上为当前函数调用的局部变量(这里主要就是参数n)以及返回地址等信息分配空间。如果传入一个较大的n值,递归次数过多,栈空间就会被耗尽,从而引发栈溢出。
(二)局部变量占用过多栈空间
如果在一个函数内部定义了大量的局部变量,尤其是一些占用较大内存空间的变量,比如大型数组等,就容易使栈空间快速被占用,进而导致栈溢出。
#include <stdio.h>void functionWithLargeArray() {int largeArray1[10000]; // 定义几个较大的数组作为局部变量int largeArray2[10000]; int largeArray3[10000]; // 其他代码逻辑 }int main() {functionWithLargeArray();return 0; }在这个例子中,functionWithLargeArray函数内部定义了三个能容纳 10000 个整数的数组largeArray作为局部变量。当调用这个函数时,栈需要为这些大型数组分配足够的空间,如果栈的剩余空间不足,就会发生栈溢出。
(三)无限循环中的函数调用
在一个无限循环体内不断地进行函数调用,且每个函数调用都会在栈上分配空间,这样持续下去也会导致栈溢出。
#include <stdio.h>void someFunction() {// 这里可以有一些函数内部的逻辑,每次调用会在栈上分配空间 }int main() {while (1) {someFunction();}return 0; }在上述代码中,main函数中的无限循环会不断地调用someFunction函数,而每次调用someFunction函数都会在栈上为其分配空间用于存放局部变量、返回地址等信息,随着循环的不断进行,栈空间最终会被耗尽,引发栈溢出。
三、如何避免栈溢出
(一)优化递归算法
对于因递归调用过深导致栈溢出的情况,可以通过优化递归算法来解决。
1.1、尾递归优化:如果编译器支持尾递归优化,可以将递归函数改写成尾递归的形式。尾递归是指递归调用是函数的最后一个操作,这样编译器可以将其优化为迭代形式,从而避免栈的过度增长。
例如,将前面计算阶乘的递归函数改写成尾递归形式如下:
#include <stdio.h>int factorialTail(int n, int accumulator) {if (n == 0 || n == 1) {return accumulator;}return factorialTail(n - 1, n * accumulator); }int main() {int result = factorialTail(10, 1);printf("阶乘结果: %d\n", result);return 0; }在这个尾递归版本的函数中,每次递归调用时,不需要保存当前函数调用的额外状态信息在栈上,编译器可以将其优化为类似迭代的形式,从而大大减少了栈的使用量。
1.2、采用迭代方式替代递归:很多情况下,也可以直接将递归算法改写成迭代算法,完全避免了递归调用栈的积累。
例如,再次改写计算阶乘的函数为迭代形式:
#include <stdio.h>int factorialIterative(int n) {int result = 1;for (int i = 1; i <= n; i++) {result *= i;}return result; }int main() {int result = factorialIterative(10);printf("阶乘结果: %d\n", result);return 0; }这种迭代形式的函数在计算阶乘时,只使用了有限的栈空间(主要就是用于存放函数内部的局部变量result和i),不会因为递归调用而导致栈溢出。
(二)合理管理局部变量
尽量减少在函数内部定义大量占用空间的局部变量。如果确实需要使用较大的数据结构,可以考虑将其定义在堆空间(通过动态内存分配机制,如malloc等),而不是栈空间。
例如,对于前面定义大型数组导致栈溢出的例子,可以将其修改为如下形式:
#include <stdio.h>void functionWithLargeArray() {int *largeArray = (int *)malloc(sizeof(int) * 10000);// 其他逻辑代码free(largeArray); }int main() {functionWithLargeArray();return 0; }在这个修改后的代码中,通过malloc函数在堆空间动态分配了一个能容纳 10000 个整数的数组largeArray,这样就不会占用过多的栈空间,避免了因局部变量占用过多栈空间而导致的栈溢出。
(三)检查循环逻辑
确保在循环体内进行函数调用等操作时,循环是有终止条件的,并且不会因为逻辑错误导致无限循环从而引发栈溢出。在编写代码时要仔细分析循环的终止条件是否合理,以及是否会出现意外情况导致循环无法正常终止。
比如,对于前面无限循环中函数调用导致栈溢出的例子,可以将其修改为如下有终止条件的循环:
#include <stdio.h>void someFunction() {//其他逻辑代码 }int main() {for (int i = 0; i < 10; i++) {someFunction();}return 0; }在这个修改后的代码中,通过设置循环的终止条件为i < 10,使得循环只会执行有限次,每次调用someFunction函数也只会在栈上分配有限的空间,从而避免了栈溢出。
四、栈溢出会有什么严重的后果
(一)程序崩溃
栈溢出最常见的后果就是导致程序异常终止,也就是崩溃。当栈溢出发生时,程序可能会因为访问到了非法的内存区域(超出了栈的范围)而触发操作系统的保护机制。
在 Windows 系统中,可能会弹出 “程序已停止工作” 的提示;在 Linux 系统中,可能会生成相应的错误核心文件,并且程序进程会终止运行,无法继续完成预期的任务。
(二)数据损坏
由于栈溢出可能会导致内存中的数据被意外修改,尤其是栈附近的其他数据结构(如其他函数的局部变量、函数调用的返回地址等)可能会受到影响。这就有可能导致程序后续的运行出现错误的结果,比如计算结果错误、逻辑判断失误等,因为相关的数据已经被不正确地改变了。
相关文章:
什么是栈溢出
一、什么是栈溢出 栈溢出(Stack Overflow)就是指在程序运行过程中,往栈里存放的数据超过了栈所能容纳的最大容量,从而导致程序出现异常行为的情况。这就好比一个箱子本来只能装一定数量的物品,硬要往里面塞更多的东西&…...
在linux中arm-linux-gcc和/usr/bin/gcc有啥区别
在Linux中,arm-linux-gcc和/usr/bin/gcc都是编译器,但它们之间存在显著的区别,主要体现在编译目标、使用场景以及编译生成的二进制文件的可执行性上。而软链接则是Linux文件系统中的一种特殊文件类型,用于创建一个文件的别名。 a…...
常用环境部署(二十二)——MySQL的数据库迁移到另一个机器上
1、导出原数据库的数据 mysqldump -u [用户名] -p[密码] [数据库名] > database_dump.sql 命令示例: mysqldump -u root -p123456 wd > /opt/wd.sql 2、在新机器上创建数据库 mysql -u [用户名] -p -e "CREATE DATABASE [新数据库名]" 命令示…...
两台主机只能单方向ping通
可能性比较大的原因时ping不通的那台主机安装了个人防火墙。 在共享上网的机器中,出于安全考虑,大部分主机都安装个人防火墙软件。几乎所有个人防火墙软件默认不允许其他机器ping本机。一般的做法是将来自外部的ICMP请求报文滤掉,对本机出去的…...
redis windows 5.0 下载
Redis 简介 Redis 是一个高性能的 key-value 数据库,广泛应用于缓存、消息队列、实时分析等场景。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,并且提供了丰富的操作命令,能够满足各种复杂的数据处理需求。 下载…...
视频转gif怎么转换?6种视频格式转换简单方法分享,附操作截图!
gif动图凭借其简洁而生动的特点,已成为互联网交流中不可或缺的一部分。尽管gif和视频在技术上有所不同,但两者都能以短小的帧展现动作,而gif通常不带声音,具备循环播放的特性。因此,出于创建gif动图、存储更多媒体文件…...
StructRAG简介
StructRAG是一种新型的框架,旨在提升大型语言模型(LLMs)在知识密集型推理任务中的性能。它通过推理时的混合信息结构化机制,根据任务需求以最合适的格式构建和利用结构化知识。 以下是StructRAG的核心组成部分和工作流程ÿ…...
java脚手架系列12-mongoDB
之所以想写这一系列,是因为之前工作过程中有几次项目是从零开始搭建的,而且项目涉及的内容还不少。在这过程中,遇到了很多棘手的非业务问题,在不断实践过程中慢慢积累出一些基本的实践经验,认为这些与业务无关的基本的…...
python四舍五入保留两位小数
在 Python 中,你可以使用内置的 round() 函数来对数字进行四舍五入并保留两位小数。round() 函数有两个参数:要四舍五入的数字和要保留的小数位数。以下是一个简单的示例: # 示例数字 number 3.14159# 四舍五入保留两位小数 rounded_number…...
期权懂|有什么期权交易策略能够稳赚不赔的?
期权小懂小编每日分享期权知识,帮助期权新手及时有效地掌握即市趋势与新资讯! 有什么期权交易策略能够稳赚不赔的? 期权交易具有风险性,没有任何一种策略能够保证稳赚不赔。 以下是一些常见的期权交易策略,虽不能保证盈利&#…...
笔记本脱机状态
先是显示脱机,请尝试其他方法登录 1.按照联想客服,进入高级选项里面,清除两个更新项目,没有卸载成功 2.安装wepe,先是能检测到U盘,但是进不去,然后我淘宝淘帮我做盘,我自己重新装了一…...
Node.js:模块 包
Node.js:模块 & 包 模块module对象 包npm安装包配置文件镜像源 分类 模块 模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组合、分解和更换的单元。 简单来说,就是把一个…...
油动无人机动力测试台-60公斤级-Flight Stand 60 ICE
产品简介 通过Flight Stand 60 ICE测试台对内燃机和螺旋桨的拉力,扭矩,转速,燃油流量,温度,功率和螺旋桨效率的测量,帮助用户精准地描述和评估其性能参数,以不断地优化和提升燃油动力系统性能。…...
给grasshopper中的python脚本电池加个标签
ghenv.Component.Message test使用python脚本创建的电池,也可以保存起来。 File – Create User Object...
别被忽悠了 Lua 数组真的也可以从 0 开始索引?
先前我说 Lua 数组从 1 开始不太爽,很多人来纠正我说也可以从 0 开始,比如: local m { [0] 100, 101, 102, 103 }然后访问时 m[0] 也可以正常访问到第 0 个元素,所以 “Lua 给你充分自由度,让你可以从任意下标索引数…...
docker占用磁盘过多问题
我在windows系统上用docker,安装在C盘环境下,我发现C盘占用了大量的空间,查找后发现是docker的映像文件占用的,于是开始清理,中间还踩个坑,记录一下,下次需要的时候方便找。 踩坑 我本想移动映…...
[实时计算flink]使用Python依赖
您可以在Flink Python作业中使用自定义的Python虚拟环境、第三方Python包、JAR包和数据文件等,本文为您介绍如何在Python作业中使用这些依赖。 使用自定义的Python虚拟环境 说明 VVR 4.x仅支持3.7版本的Python虚拟环境,VVR 6.x及以上的版本无此限制&a…...
MySql如何实现分布式锁
本篇我们使用mysql实现一个分布式锁。 环境:mysql8,navicat,maven,springboot2.3.11,mybatis-plus 分布式锁的功能 1,分布式锁使用者位于不同的机器中,锁获取成功之后,才可以对共享资源进行操作 2,锁具有重入的功能:即一个使用…...
「行内揭秘」 SQLynx数据库界的“小众宝藏”?
数据库界的“小众宝藏”?Navicat老大哥地位稳如泰山,但这位“SQLynx”小弟也不容小觑!👀 别看它小众,SQLynx在处理数据库事务上那可是丝毫不含糊,无论你是Windows Linux和Mac,甚至银河麒麟统信都…...
【已解决】【MySQL】IDEA配置数据库 报错 未配置SQL方言 无法使用SQL提示
IDEA配置数据库的步骤 下载插件 添加数据源 新建--->选择数据源MySQL 页面展示: 主机名:一般都是localhost不用改端口:填写自己的端口号用户:填写自己的用户名密码:填写自己设置的密码数据库:填写需要…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)
目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关࿰…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
Mysql8 忘记密码重置,以及问题解决
1.使用免密登录 找到配置MySQL文件,我的文件路径是/etc/mysql/my.cnf,有的人的是/etc/mysql/mysql.cnf 在里最后加入 skip-grant-tables重启MySQL服务 service mysql restartShutting down MySQL… SUCCESS! Starting MySQL… SUCCESS! 重启成功 2.登…...
c++第七天 继承与派生2
这一篇文章主要内容是 派生类构造函数与析构函数 在派生类中重写基类成员 以及多继承 第一部分:派生类构造函数与析构函数 当创建一个派生类对象时,基类成员是如何初始化的? 1.当派生类对象创建的时候,基类成员的初始化顺序 …...
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
