当前位置: 首页 > news >正文

【C语言】“栈”顶到底是上面还是下面?高地址还是低地址?

【C语言】“栈”顶到底是上面还是下面?高地址还是低地址?

我在看《程序是怎样跑起来的》这本书时,看到下面这幅图突然有些疑惑
在这里插入图片描述
这个是的分配,针对栈我记得两个结论,

  1. 栈是从高地址向低地址增长。
  2. 栈是自顶向下增长。

可是书里为什么是自底向上增长?还有就是不是说是从高地址往地址增长,我记得上面是高地址,这个怎么就从低地址往高地址增长了?难道是书写错了?突然就懵了。。。

之后查阅了好半天才捋清楚。

首先要明确一个概念:栈的实际形状是一个杯子。杯子的底部永远称栈底,杯子的顶部永远称栈顶。

而这个杯子在实际的内存存储情况是倒放的,如下:
在这里插入图片描述
栈和这两个杯子相对,一个倒放,一个正放。所以对于栈来说上面是栈底下面是栈顶,而对于堆来说,上面是堆顶下面是堆底。

  1. 栈是系统自动分配和释放,而堆需要程序员手动分配释放。所以总体上栈的分配效率要比堆高。
  2. 栈中存放的内容包括函数返回地址、相关参数、局部变量(这个用最多)和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存。而堆中具体存放内容是由程序员来填充的。
  3. 每个进程拥有的栈的大小要远远小于堆的大小。
#include <iostream>/// 保留区             0地址开始C 库
/// 代码区             .text 函数代码                                  高地址
/// 常量区             .rodata 字符串
/// 全局(静态)变量区   .data 非0区和.bass 未初始化(全局 静态)
/// 堆区                                                                 ↑
/// 栈区                                                                 ↓
/// 命令行参数空间
/// 内核(windows2G/linux1G)                                          低地址/// 整体 上面的就像倒着的杯子 看待  整体开始内存不连续/// 堆 向下增长 (低地址 向 高地址) 倒是连续的 但是 和 内存对齐的策略有点关系
/// 栈 向上增长 (高地址 向低地址)constexpr int g0 = 4;
int           g1; ///< 全局变量 未初始化
int           g2  = 0;
int           g3  = 1;
int           g4  = 2;
static int    sg1 = 3;
int           main(int argc, char* argv[])
{{static int s1 = 5;std::cout << "memory address! cppds.com" << std::endl;/// 代码区 .textstd::cout << "代码区 main =" << main << std::endl;/// 常量区std::cout << "常量区 g0 =" << &g0 << std::endl;std::cout << "全局未初始化  g1=" << &g1 << std::endl;/// &g1 + 4 = &g2std::cout << "全局初始化为0 g2=" << &g2 << std::endl;/// 但是 &g2 - 28 =  &g3  g3 = 0/// 0 不一样std::cout << "全局初始化为1 g3=" << &g3 << std::endl;/// 从这个开始 后面一次依次向右4地址std::cout << "全局初始化为2 g4=" << &g4 << std::endl;std::cout << "静态全局初始化为3 sg1=" << &sg1 << std::endl;std::cout << "静态局部初始化为5 s1=" << &s1 << std::endl;/// 全局区 静态区 是在一块内存///int* p1 = new int;int* p2 = new int;int* p3 = new int;int* p4 = new int;std::cout << "堆空间地址 p1=" << p1 << std::endl;std::cout << "堆空间地址 p2=" << p2 << std::endl;std::cout << "堆空间地址 p3=" << p3 << std::endl;std::cout << "堆空间地址 p4=" << p4 << std::endl;std::cout << "指针变量的栈空间地址 &p1=" << &p1 << std::endl;std::cout << "指针变量的栈空间地址 &p2=" << &p2 << std::endl;delete p1;delete p2;delete p3;delete p4;p1 = nullptr;p2 = nullptr;p3 = nullptr;p4 = nullptr;int i1 = 100;int i2 = 101;int i3 = 102;int i4 = 103;std::cout << "栈空间地址 i1=" << &i1 << std::endl;std::cout << "栈空间地址 i2=" << &i2 << std::endl;std::cout << "栈空间地址 i3=" << &i3 << std::endl;std::cout << "栈空间地址 i4=" << &i4 << std::endl;}getchar();return 0;
}
memory address! cppds.com
代码区 main =00007FF6574211F4
常量区 g0 =00007FF65742BBDC
全局未初始化  g1=00007FF65742F4C0
全局初始化为0 g2=00007FF65742F4C4
全局初始化为1 g3=00007FF65742F000
全局初始化为2 g4=00007FF65742F004
静态全局初始化为3 sg1=00007FF65742F008
静态局部初始化为5 s1=00007FF65742F010
堆空间地址 p1=000001EC52B19280
堆空间地址 p2=000001EC52B19CC0
堆空间地址 p3=000001EC52B18E80
堆空间地址 p4=000001EC52B196C0
指针变量的栈空间地址 &p1=000000A61FAFF828
指针变量的栈空间地址 &p2=000000A61FAFF848
栈空间地址 i1=000000A61FAFF874
栈空间地址 i2=000000A61FAFF894
栈空间地址 i3=000000A61FAFF8B4
栈空间地址 i4=000000A61FAFF8D4

然后接着说,

在很多书里,他们喜欢把栈这个杯子倒过来,这样下面就是栈底,上面就变成栈顶。同样下面就变成了高地址而上面就变成了低地址,而永远要记住,不论怎样变,栈的增长方向永远是从杯底到杯顶。

之后再看上面的结论,会发现不论书里还是网上都没有错。只不过有些人介绍栈的时候喜欢给栈换个方向罢了。另外,对于栈是自顶向下增长,这里的顶和下说的就是方向,就是从上往下增长,是从实际内存空间栈分布情况说的。如果把栈倒过来,这种说法就会看起来不太准确了。而栈底和栈顶永远是杯底和杯顶,这个底和顶就和方向没关系了。

对于x86的cpu主要有下图的这些寄存器:
在这里插入图片描述
其中和栈有关的主要是ebpesp两个寄存器。ebp总是指向栈底,b表示base嘛,esp总是指向栈顶。

一开始会让esp的值等于ebp的值,随着函数的局部变量往栈中保存,cpu会自动让esp会逐渐往低地址方向移动来分配栈空间,而当需要进行栈清理时,只需要再次将esp的值等于ebp,就能从逻辑上将栈中的数据清除。(一般局部变量会优先往寄存器中保存,寄存器不够用时才会开始考虑栈)

在数据结构中的栈和内存中的栈讲的不是一个东西,不过数据结构中的栈同样类似于一个杯子,特点就是先进后出,因为先进去的数据被压到杯底不好取出来嘛。而数据结构的堆则和内存中的堆就完全不同了,数据结构中的堆是个树形结构。这块不能混淆。

栈大致就是这么个情况。


文章转载自:

http://3m9QvPpF.gpmrj.cn
http://J3DDgxy8.gpmrj.cn
http://H7glKl2A.gpmrj.cn
http://z6BtQGVG.gpmrj.cn
http://6luRRQAh.gpmrj.cn
http://h7SqQRzA.gpmrj.cn
http://CiwqblTi.gpmrj.cn
http://V7ysOvwp.gpmrj.cn
http://r89oS8m5.gpmrj.cn
http://gFwntmCQ.gpmrj.cn
http://gPg2UPZW.gpmrj.cn
http://jXUXEUuF.gpmrj.cn
http://pqRTFR7R.gpmrj.cn
http://BuJjIr9L.gpmrj.cn
http://32yTrwER.gpmrj.cn
http://dFJTuaD7.gpmrj.cn
http://H06qt5FP.gpmrj.cn
http://4iHSF9qd.gpmrj.cn
http://JShL7znh.gpmrj.cn
http://6MuxOkUH.gpmrj.cn
http://6G4qw7oE.gpmrj.cn
http://2PpvoIyj.gpmrj.cn
http://mW6JDSk9.gpmrj.cn
http://PB9Dyfmx.gpmrj.cn
http://Vwi91Dwj.gpmrj.cn
http://nqO6vKrH.gpmrj.cn
http://jtSfkweL.gpmrj.cn
http://E4sO5KXK.gpmrj.cn
http://kyYg1wO1.gpmrj.cn
http://krnpfzdZ.gpmrj.cn
http://www.dtcms.com/a/381141.html

相关文章:

  • 3种光伏设计方式,哪个最适合你?
  • 移动考勤软件如何选?GPS和离线打卡两大功能解析
  • 代码随想录学习摘抄day8(二叉树21-31)
  • 0~1构建一个mini blot.new(无AI版本)
  • Nuitka 将 Python 脚本封装为 .pyd 或 .so 文件
  • 解决Arthas 端口冲突问题
  • linux执行systemctl enable xxxxx 报 Failed to execute operation: Bad message
  • linux C 语言开发 (八) 进程基础
  • Oracle SQL调优技巧实战指南
  • B1013 PAT乙级JAVA题解 数素数
  • oracle字符转time
  • 阿里巴巴开放开放平台商品详情接口技术实现:详情数据深度解析方案
  • python使用pip安装的包与卸载
  • 题目:快乐数
  • Leecode hot100 - 287. 寻找重复数
  • SQL优化分析学习
  • Thinking Machines的博客
  • Linux命令行的核心理念与实用指南
  • 单板挑战4路YOLOv8!米尔瑞芯微RK3576开发板性能实测
  • 硬件(九)寄存器、外设与中断机制
  • 《常见的设计模式——单例、代理与适配器》
  • 海龟交易策略
  • MySQL 事务
  • claude code使用小窍门
  • Recaptcha2 图像识别 API 对接说明
  • Spring中 @Value注解设置默认值
  • Linux / Windows 下连续发送多帧 8 字节指令,下位机只响应第一帧,第二帧“丢失”。
  • RStudio 教程:以抑郁量表测评数据分析为例
  • 驱动程序介绍及其安装说明
  • Day03 前缀和 | 1248. 统计「优美子数组」、53. 最大子数组和