揭秘程序栈:你的代码在幕后是怎么运行的?

计算机科学中,许多概念和原理可能会让开发者感到头疼,比如程序栈。这个看似晦涩的概念,实对我们理解程序运行至关重要。本文将以通俗易懂的方式,带你深入理解程序栈的工作原理和优化策略。

一、为什么需要栈?

栈是一种特殊的数据结构,它只允许在一端(称为栈顶)进行操作,比如插入(压栈)和删除(弹栈)。程序栈主要解决了两个问题:多层函数嵌套返回的问题,以及一些参数、临时变量的资源管理。

1.1 多层函数嵌套返回的问题

想象一下,你正在玩一个益智游戏,每个关卡你都需要记住一些信息,比如道具位置、怪物数量等。你通过一个关卡后,进入下一个,再下一个……这时,你需要返回前几个关卡,你还记得每个关卡的信息吗?答案很可能是不记得。

函数调用就像这个游戏,每次调用一个函数,都会进入一个新的"关卡",需要记住一些信息,比如返回地址、参数、局部变量等。这就需要一种结构来保存这些信息,以便函数返回时可以正确恢复状态。程序栈就扮演了这个角色。

1.2 参数、临时变量的资源管理

程序在运行过程中,会产生很多临时变量和函数参数,这些数据在使用完毕后就不再需要。如果手动管理这些资源,不仅麻烦,而且容易出错。程序栈提供了一种自动管理这些资源的机制:当函数返回时,程序栈中保存的函数参数、局部变量等就会被自动销毁,相应的资源也会被回收。

二、栈的工作原理

2.1 在内存为程序创建栈:线程栈

我们知道CPU的时间片是分配在线程上的,也就是说程序的执行实际上是在线程中进行的,那么函数的调用自然也是在线程中处理的,所以本文提到的程序栈也是需要关联到具体线程的,关联到具体线程的程序栈称为线程栈。

线程栈是每个线程独立拥有的一块栈内存空间,用于存储这个线程所调用的函数的栈帧。每个线程都有自己的程序栈。这是因为每个线程都是独立运行的,它们之间需要隔离,不能互相干扰。所以,线程栈就是存储程序栈的物理空间,而程序栈则是在这个物理空间中实现函数调用的逻辑结构。

那么,为什么不把栈放在CPU的寄存器中呢?

首先,CPU寄存器的容量有限,而且寄存器的主要任务是执行计算,如果寄存器用来存储栈,那么就可能影响CPU的计算效率。其次,现代CPU因为缓存的使用,其访问内存的速度已经非常快,几乎可以和访问寄存器相媲美。此外,把栈放在寄存器中,可能会使CPU设计变得复杂,提高CPU的成本,同时也需要修改指令集和各种编译器,这会带来很高的应用成本。

那么,栈的大小是多少呢?在Windows系统中,线程栈的默认大小是1M,可以由编译器指定;在Linux系统中,线程栈的默认大小是8M,可以由操作系统环境设置。

2.2 压栈和出栈

每次函数调用时,都会在栈上为这个函数创建一个栈帧,这个过程称为压栈。栈帧包含了函数运行所需的所有信息,比如返回地址、参数、局部变量等。当函数运行结束后,这个栈帧就会被销毁,这个过程称为出栈。

在压栈和出栈的过程中,需要进行rbp(register base pointer,栈底指针)和rsp(register stack pointer,栈顶指针)的切换。rbp用来指向栈底,rsp用来指向栈顶。

栈帧(Stack Frame)就像一个小的“盒子”,用来存放这个函数的一些重要信息。

每个栈帧通常包含以下几部分内容:

  1. 函数的参数:当我们调用一个函数时,需要向它传递一些参数。这些参数就会被存放在栈帧中。
  2. 局部变量:在函数内部定义的变量是局部的,它们的生命周期只在函数执行期间,所以这些局部变量也会被存放在栈帧中。
  3. 返回地址:当函数执行完毕后,CPU需要知道接下来应该跳转到哪里继续执行,这个“跳转到哪里”就是返回地址,它也会被存放在栈帧中。
  4. 保存的寄存器值:在函数调用过程中,可能会改变一些寄存器的值,为了在函数返回后能恢复这些寄存器的原始值,需要将这些寄存器的原始值保存在栈帧中。

所以,程序栈就是由一连串的栈帧组成,每个栈帧中保存了函数执行所需要的所有信息。

2.3 为什么从高地址向低地址分配?

一般来说,栈是从高地址向低地址分配的,但这并不是绝对的。在不同的操作系统和处理器架构下,栈的分配方式可能会不同。在Linux/x86架构中,栈确实是从高地址向低地址分配的。这里说的是栈帧的分配方式,栈内的数据分配方向则由编译器决定。

三、栈的优化策略

3.1 函数内联

函数内联是一种优化策略,它可以减少函数调用的开销。如果一个函数只被调用一次,或者函数体非常小,那么在编译时,编译器可以把这个函数的代码直接插入到调用它的地方,这就是函数内联。

函数内联可以减少压栈和出栈的开销,但是如果滥用,可能会导致程序体积过大。因此,一般只对"叶子函数"(即没有调用其他函数的函数)进行内联。

3.2 避免栈溢出

栈溢出是一种常见的程序错误,主要有两个原因:无限递归和栈中非常占内存的变量。

无限递归就是函数自己调用自己,且没有适当的终止条件,导致函数调用层次无限增加,最终导致栈溢出。解决方法是设置适当的递归终止条件。

栈中非常占内存的变量,比如大数组,也可能导致栈溢出。解决方法是尽量避免在栈上分配大量内存,可以考虑使用动态内存分配。

四、总结

程序栈是程序运行的重要基础,它解决了函数调用和资源管理的问题。理解程序栈的工作原理,可以帮助我们更好地理解程序的运行过程,也有助于我们编写出更高效的代码。希望本文的内容,能帮助你对程序栈有更深入的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/632926.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Diffusion Models

DDPM x 0 ∼ q ( x 0 ) x_0 \sim q(x_0) x0​∼q(x0​)是真实数据分布,扩散模型学习一个分布 p θ ( x 0 ) p_\theta(x_0) pθ​(x0​)去逼近真实数据分布。 p θ ( x 0 ) : ∫ p θ ( x 0 : T ) d x 1 : T (1) p_\theta(x_0) : \int p_\theta(x_{0:T})dx_{1:T} \…

chatgpt的实用技巧四temperature 格式

四、temperature 格式 GPT3.5 temperature 的范围为:0-0.7; GPT4.0 temperature 的范围为:0-1; 当 temperature 为 0 时候,结果可稳定。 当 temperature 为 0.7/1 时候,结果发散具备创力。 数值越大&a…

设计模式的学习笔记

设计模式的学习笔记 一. 设计模式相关内容介绍 1 设计模式概述 1.1 软件设计模式的产生背景 设计模式最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 1977 年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任 Christopher Alexander 在…

【Redis数据类型】String实现及应用场景

文章目录 String1、介绍2、内部实现整数值embstr 编码字符串raw编码字符串 3、常用命令4、应用场景缓存对象常规计数分布式锁共享session信息 参考:小林Coding Redis九种数据类型 Redis 提供了丰富的数据类型,常见的有五种:String&#xff08…

Debian 10.13.0 安装图解

引导和开始安装 这里直接回车确认即可,选择图形化安装方式。 选择语言 这里要区分一下,当前选中的语言作为安装过程中安装器所使用的语言,这里我们选择中文简体。不过细心的同学可能发现,当你选择安装器语言之后,后续安…

电力能源三维可视化合集 | 图扑数字孪生

电力能源是现代社会发展和运行的基石,渗透于工业、商业、农业、家庭生活等方方面面,它为经济、生活质量、环境保护和社会发展提供了巨大的机会和潜力。图扑软件应用自研 HT for Web 强大的渲染引擎,助力现代化的电力能源数字孪生场景&#xf…

运筹说 第95期 | 非线性规划奠基人——库恩与塔克

经过之前的学习,相信大家已经对运筹学的网络计划的内容有了一定的了解,接下来小编将带你学习新一章——非线性规划的内容,让我们先来了解一下非线性规划的诞生和发展历程,然后共同走近非线性规划领域的代表人物——库恩和塔克&…

2.控制语句

1.分支语句/判断语句 if 语句 if(boolean_expression) { /* 如果布尔表达式为真将执行的语句 */ } if…else 语句 if(boolean_expression) { /* 如果布尔表达式为真将执行的语句 / } else { / 如果布尔表达式为假将执行的语句 */ } if…else if…else语句 if(boolean_expressi…

【BERT】详解

BERT 简介 BERT 是谷歌在 2018 年时提出的一种基于 Transformer 的双向编码器的表示学习模型,它在多个 NLP 任务上刷新了记录。它利用了大量的无标注文本进行预训练,预训练任务有掩码语言模型和下一句预测,掩码语言模型指的是随机地替换文本中…

Python基础学习:同步异步阻塞与非阻塞

嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 一、状态介绍 在了解其他概念之前,我们首先要了解进程的几个状态。 在程序运行的过程中,由于被操作系统的调度算法控制,程序…

k8s---pod控制器

pod控制器发的概念: 工作负载,workload用于管理pod的中间层,确保pod资源符合预期的状态。 预期状态: 1、副本数 2、容器重启策略 3、镜像拉取策略 pod出故障的出去等等 pod控制器的类型: 1、replicaset&#xf…

可达性分析

可达性分析 这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC …

力扣22. 括号生成

回溯 思路&#xff1a; 定义函数 dfs(item, open, close, n) 表示当前 item 有左括号个数 open 和右括号个数 close &#xff1b;使用递归&#xff0c;长度为 n 的序列就是在长度为 n - 1 的序列后加左括号或者右括号&#xff1a; 先放左括号&#xff0c;只要其个数 < n&am…

SSL证书自动化管理有什么好处?如何实现SSL证书自动化?

SSL证书是用于加密网站与用户之间传输数据的关键元素&#xff0c;在维护网络安全方面&#xff0c;管理SSL证书与部署SSL证书一样重要。定期更新、监测和更换SSL证书&#xff0c;可以确保网站的安全性和合规性。而自动化管理可以为此节省时间&#xff0c;并避免人为错误和不必要…

微信原生小程序上传与识别以及监听多个checkbox事件打开pdf

1.点击上传并识别 组件样式<van-field border"{{ false }}" placeholder"请输入银行卡卡号" model:value"{{bankNo}}" label"卡号"><van-icon bindtap"handleChooseImg" slot"right-icon" name"sca…

IDEA中启动项目报堆内存溢出或者没有足够内存的错误

1.报错现象 java.lang.OutOfMemoryError: Java heap space 或者 Could not reserve enough space for object heap 2.解决办法 在运行配置中VM选项后加下面的配置&#xff1a; -server -XX:MaxHeapSize256m -Xms512m -Xmx512m -XX:PermSize128M -XX:MaxPermSize256m 3.JVM虚…

单表查询练习

目录 题目&#xff1a; 制定约束&#xff1a; 添加表格信息&#xff1a; 所需查询的信息&#xff1a; 实验步骤&#xff1a; 第一步&#xff1a;制作表格 创建新的数据库 创建表格约束&#xff1a; 为表格加入数据&#xff1a; 第二步&#xff1a;查询信息 题目&…

Red Hat Enterprise Linux 7.9 安装图解

引导和开始安装 选择倒计时结束前&#xff0c;通过键盘上下键选择下图框选项&#xff0c;启动图形化安装过程。 安装语言选择 这里要区分一下&#xff0c;当前选中的语言作为安装过程中安装器所使用的语言&#xff0c;这里我们选择中文简体。不过细心的同学可能发现&#xff0c…

OpenGL ES之深入解析如何绘制“跳动的心“特效

最近在浏览技术网站时,偶然间发现如下这个"跳动的心"特效,觉得蛮好玩的,当得知这个特效是用纯代码实现(GLSL 实现)的,确实又被惊到:追溯该特效最初的来源,最终在 ShaderToy 网站看到它的原始实现,另外在 ShaderToy 上还发现了无数类似惊人的特效,并且这些特…

JVM:性能监控工具分析和线上问题排查实践

前言 在日常开发过程中&#xff0c;多少都会碰到一些jvm相关的问题&#xff0c;比如&#xff1a;内存溢出、内存泄漏、cpu利用率飙升到100%、线程死锁、应用异常宕机等。 在这个日益内卷的环境&#xff0c;如何运用好工具分析jvm问题&#xff0c;成为每个java攻城狮必备的技能…