什么是栈溢出
一、什么是栈溢出
栈溢出(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不用改端口:填写自己的端口号用户:填写自己的用户名密码:填写自己设置的密码数据库:填写需要…...
CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型
CVPR 2025 | MIMO:支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题:MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者:Yanyuan Chen, Dexuan Xu, Yu Hu…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...