【C语言】“栈”顶到底是上面还是下面?高地址还是低地址?
【C语言】“栈”顶到底是上面还是下面?高地址还是低地址?
我在看《程序是怎样跑起来的》这本书时,看到下面这幅图突然有些疑惑
这个是栈
的分配,针对栈我记得两个结论,
- 栈是从高地址向低地址增长。
- 栈是自顶向下增长。
可是书里为什么是自底向上增长?还有就是不是说是从高地址往地址增长,我记得上面是高地址,这个怎么就从低地址往高地址增长了?难道是书写错了?突然就懵了。。。
之后查阅了好半天才捋清楚。
首先要明确一个概念:栈的实际形状是一个杯子。杯子的底部永远称栈底,杯子的顶部永远称栈顶。
而这个杯子在实际的内存存储情况是倒放的,如下:
栈和堆
这两个杯子相对,一个倒放,一个正放。所以对于栈来说上面是栈底下面是栈顶,而对于堆来说,上面是堆顶下面是堆底。
- 栈是系统自动分配和释放,而堆需要程序员手动分配释放。所以总体上栈的分配效率要比堆高。
- 栈中存放的内容包括函数返回地址、相关参数、局部变量(这个用最多)和寄存器内容等。当主函数调用另外一个函数的时候,要对当前函数执行断点进行保存。而堆中具体存放内容是由程序员来填充的。
- 每个进程拥有的栈的大小要远远小于堆的大小。
#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主要有下图的这些寄存器:
其中和栈有关的主要是ebp
和esp
两个寄存器。ebp总是指向栈底,b表示base嘛,esp总是指向栈顶。
一开始会让esp
的值等于ebp
的值,随着函数的局部变量往栈中保存,cpu会自动让esp会逐渐往低地址方向移动来分配栈空间,而当需要进行栈清理时,只需要再次将esp的值等于ebp,就能从逻辑上将栈中的数据清除。(一般局部变量会优先往寄存器中保存,寄存器不够用时才会开始考虑栈)
在数据结构中的栈和内存中的栈讲的不是一个东西,不过数据结构中的栈同样类似于一个杯子,特点就是先进后出,因为先进去的数据被压到杯底不好取出来嘛。而数据结构的堆则和内存中的堆就完全不同了,数据结构中的堆是个树形结构。这块不能混淆。
栈大致就是这么个情况。