数据结构:栈(Stack)和堆(Heap)

article/2025/6/22 6:42:56

目录

内存(Memory)基础

程序是如何利用主存的?

🎯 静态内存分配 vs 动态内存分配 

栈(stack) 

程序执行过程与栈帧变化

 堆(Heap)

程序运行时的主存布局 


内存(Memory)基础

计算机运行程序时,需要在某个地方存储数据和指令,这个地方就是内存,也叫主存或 RAM(随机存取存储器)。

内存被划分为一个个小的单位,每个单位称为字节(byte)。

  • 1字节 = 8位(bit),每个位可以是0或1。

  • 每个字节都有一个唯一的地址,CPU通过这些地址来访问内存中的数据。

  • 内存的地址通常从0开始,依次递增。例如,主存64KB的内存,地址范围是0到65,535(因为64KB = 64 × 1024 = 65,536字节)。

你可以把内存想象成一个超大的格子仓库,每个格子能存放一个数据单位(字节),而程序运行时会不断地从这个仓库取数据、放数据。

程序是如何利用主存的?

当一个程序被加载进内存运行时,主存会被划分成几个不同的区域来完成不同的任务,常见的有三部分:

+-------------------+ ← 低地址(地址编号小)
| 代码区(Text)     | ← 存储编译后的程序指令
+-------------------+
| 栈区(Stack)      | ← 自动变量、函数调用相关的数据
|                   |
|  ↓  向下增长       |
+-------------------+
|                   |
|  ↑  向上增长       |
| 堆区(Heap)       | ← 动态分配的内存(malloc/new)
+-------------------+ ← 高地址(地址编号大)
区域作用
代码区存放程序编译后的“机器指令”,CPU会按顺序执行这些代码。
栈区存储局部变量,如函数里的变量,随着函数调用和退出自动分配和释放。
堆区程序运行时用来“手动”申请的内存区域,由程序员自己管理(使用 newmalloc 等)。

 我们来写一段简单的代码:

#include <iostream>
using namespace std;int main() {int a = 10;        // 栈上的 int 变量float* p = new float(3.14f); // 堆上的 float 变量return 0;
}

这段代码里:

🎯1. int a = 10;

  • a 是一个局部变量,在 main() 函数中声明。

  • 它会被分配在 栈区 中。

  • 如果 int 占 4 字节,比如从地址 0x1000 开始,则 a 占用地址 0x1000 ~ 0x1003

🎯2. new float(3.14f)

  • 这是通过 new 申请的内存,分配在 堆区。

  • 它返回一个指针 p,指向堆上某块内存(比如地址 0x5000),那里存着 3.14

  • p 本身是一个局部变量(指针变量),也放在 栈区,但它指向的内容在 堆区。

🔍 内存示意图 

地址        内容                所属区域
0x1000      10                 a(int 类型,栈上)
0x1004      0x5000             p(指向堆内 float 的地址,栈上)
...
0x5000      3.14               堆上 float

🎯 静态内存分配 vs 动态内存分配 

在 C/C++ 中,int 类型的大小不是写死的,而是取决于以下几个因素:

决定因素说明
平台架构32 位系统中通常是 4 字节(32 位);64 位系统也通常是 4 字节(但不一定)。
编译器的实现不同编译器(如 gcc、MSVC)可能会有不同的默认设置。
数据模型(如 LP64)C/C++ 标准没有规定 int 的精确大小,只规定了大小关系(比如 intshort)。

静态内存分配(Static Memory Allocation)

定义:在编译时确定变量的大小和存储位置,程序运行时自动分配和释放。通常发生在栈区或全局/静态区。

示例:int a = 10;

  • 变量 a 是局部变量,分配在栈上。

  • 编译器知道它的类型(int),所以知道它需要 4 个字节。

  • 编译时就决定:a 需要多大、存哪里、何时释放。

动态内存分配(Dynamic Memory Allocation)

定义:程序运行时,向堆区申请内存。程序员需要自己手动释放。

示例:float* p = new float(3.14f);

  • new float(3.14f) 会在堆上开辟 4 字节(float 大小)的空间。

  • 返回一个地址(比如 0x5000)给 pp 是一个放在栈上的指针变量。

  • 你必须 delete p; 否则会造成内存泄漏。

栈(stack) 

栈是一块在主存中专门用于管理函数调用和局部变量的区域。

它遵循的是一种 “先进后出(FILO)” 的数据结构。

在程序运行时,每次函数调用都会创建一个“栈帧(stack frame)”,这个栈帧保存:

  • 函数的参数(如 int i

  • 函数内部定义的局部变量(如 int afloat b

  • 返回地址(调用完了返回哪)

函数退出后,对应的栈帧就会被销毁,空间自动回收。

 

void fun2(int i)
{int a;
}
void fun1()
{int x;fun2(x);
}
int main()
{int a;float b;fun1();
}

程序执行过程与栈帧变化

假设系统中:

  • intfloat 都是 4 字节

  • 栈从高地址向低地址生长(这是几乎所有平台上的标准)

✅ 步骤 1:程序启动

程序开始执行 main(),栈上创建 main 的栈帧:

main 栈帧:
-----------------------
| float b (4 字节)   |
| int a   (4 字节)   |
-----------------------

✅ 步骤 2:main 调用 fun1()

此时压栈一个新的 fun1 栈帧:

fun1 栈帧:
-----------------------
| int x   (4 字节)   |
-----------------------

✅ 步骤 3:fun1 调用 fun2()

假设 fun2 中传入的参数是某个整数(比如默认是 0),再压一个 fun2 的栈帧:

fun2 栈帧:
-----------------------
| int a   (4 字节)   |
| int i   (4 字节)   | ← 这是参数 i
-----------------------

 此时,整个栈结构如下(高地址在上):

地址       内容(栈帧)     所属函数
0x7ff0     int i           ← fun2 参数
0x7fec     int a           ← fun2 局部变量
-------------------------------------
0x7fe8     int x           ← fun1 局部变量
-------------------------------------
0x7fe4     float b         ← main 局部变量
0x7fe0     int a           ← main 局部变量

函数返回后的栈变化

  • fun2() 执行完毕,它的栈帧会被销毁(ia 消失)

  • 接着 fun1() 返回,它的 x 被销毁

  • 最后 main() 结束,整个栈清空,程序结束

栈的特点总结

特点描述
自动管理函数调用时创建,返回时销毁
生命周期短局部变量只在函数内部有效
空间小但访问快通常 1~8MB,访问速度快于堆
按顺序进出(先进后出)后调用的函数先返回

 堆(Heap)

在程序运行时,堆(heap)是一块用于动态内存分配的区域,由操作系统统一管理。 

  • 与栈不同,堆上的内存不会自动释放,程序员必须手动释放(如 deletefree)。

  • 在 C++ 中,使用 new / new[] 来从堆中申请内存,使用 delete / delete[] 来释放。

 

程序运行时的主存布局 

int main() 
{int* p;p = new int[5];   // 动态申请堆上的数组delete[] p;       // 手动释放堆内存
}
栈区:
--------------------------
| p(指针,8字节)       | ← 指向堆上数组的首地址
--------------------------堆区:
--------------------------
| int[0]                 |
| int[1]                 |
| int[2]                 |
| int[3]                 |
| int[4]                 | ← 共 20 字节
--------------------------

说明:

  • p 是一个局部变量,存储在栈上。

  • new int[5] 在堆上申请了一个连续的数组。

  • p 指向这块堆内内存的起始地址。

💥 如果不 delete[] 会怎样?

如果你忘记 delete[] p;,这块堆内存将不会被释放,就造成了:

  • 内存泄漏(Memory Leak):申请的内存没人管了,程序退出前都无法使用或释放。

在长期运行的程序中,反复申请但不释放,会导致内存耗尽、程序崩溃。


http://www.hkcw.cn/article/lQgQnRDZDr.shtml

相关文章

数字权限管理(DRM):保护数字内容安全的小卫士

《数字权限管理&#xff08;DRM&#xff09;&#xff1a;保护数字内容安全的小卫士》 在当今数字化飞速发展的时代&#xff0c;我们每天都在和各种各样的数字内容打交道&#xff0c;像电子书、音乐、电影、软件等等。然而&#xff0c;这些数字内容的版权保护和访问控制也成为了…

进程同步:生产者-消费者 题目

正确答案&#xff1a; 问题类型&#xff1a; 经典生产者 - 消费者问题 同时涉及同步和互斥。 同步&#xff1a;生产者与消费者通过信号量协调生产 / 消费节奏&#xff08;如缓冲区满时生产者等待&#xff0c;空时消费者等待&#xff09;。互斥&#xff1a;对共享缓冲区的访问需…

【第三十八周】BLIP-2:一种高效的视觉语言预训练框架

BLIP-2 摘要Abstract文章信息引言方法模型结构Stage1:表征学习Stage2:生成学习模型预训练 实验结果总结 摘要 本篇博客介绍了BLIP-2 &#xff0c;这是一种面向通用多模态任务的高效视觉语言预训练框架&#xff0c;其核心思想是在冻结大语言模型的前提下&#xff0c;通过引入一…

算法打卡12天

19.链表相交 &#xff08;力扣面试题 02.07. 链表相交&#xff09; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交**&#xff1a;** 题目数据…

Redis最佳实践——安全与稳定性保障之连接池管理详解

Redis 在电商应用的连接池管理全面详解 一、连接池核心原理与架构 1. 连接池工作模型 #mermaid-svg-G7I3ukCljlJZAXaA {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G7I3ukCljlJZAXaA .error-icon{fill:#552222;}…

无人机+AI视频联网:精准狙击,让‘罪恶之花’无处藏身

引言&#xff1a;禁毒攻坚战&#xff0c;科技是关键 今天是2025年5&#xff0c;正值罂粟等毒株生长关键期。传统人工巡查耗时长、盲区多&#xff0c;而无人机巡检视频AI分析的智慧禁毒方案&#xff0c;正以“高空鹰眼地面AI”的立体化监控网络&#xff0c;实现毒株种植的早发现…

以太网原理与开发802.3

W5500以太网搭建 官方移植库W5500 下载地址:GitCode - 全球开发者的开源社区,开源代码托管平台目录结构Ethernet以太网移植文件文件wizchip_conf 配置 芯片型号 工作模式 wizchip_conf.c配置 临界区片选SPI收发字节配置 自定义注册SPI // 自定义注册SPI相关回调函数 void use…

day5 cpp:,对象的组织(const对象),

1.对象的组织(类比内置类型) const对象 const对象只能调用const成员函数和数据成员&#xff0c;除了四大金刚 若成员函数没有加const(void print() const{}),即便里面没有_ix100修改值&#xff0c;也不能pt2.print()访问&#xff0c;因为是const Point pt2(3,5)--->对象不…

C语言进阶--动态内存管理

学习数据结构重要的三个部分&#xff1a;指针、结构体、动态内存管理&#xff08;malloc、calloc、realloc、free&#xff09;。 1.为什么存在动态内存分配&#xff1f; 1.空间开辟大小是固定的&#xff1b; 2.数组在声明时&#xff0c;必须指定数组的长度&#xff0c;它所需…

Excel如何去除公式保留数值

我们有时候使用Excel在修改一部分数值的时候会导致和该数值相关的通过公式进行计算的数值发生变化&#xff0c;但有时我们不想改变这些数值&#xff0c;同样的有时我们在移动一些数值的时候会导致通过这些数值计算的数值变为#!VALUE&#xff0c;这是我们不想发生的&#xff0c;…

C++学习-入门到精通【11】输入/输出流的深入剖析

C学习-入门到精通【11】输入/输出流的深入剖析 目录 C学习-入门到精通【11】输入/输出流的深入剖析一、流1.传统流和标准流2.iostream库的头文件3.输入/输出流的类的对象 二、输出流1.char* 变量的输出2.使用成员函数put进行字符输出 三、输入流1.get和getline成员函数2.istrea…

一周学会Pandas2之Python数据处理与分析-数据重塑与透视-melt() - 融化 / 逆透视 (宽 -> 长)

锋哥原创的Pandas2 Python数据处理与分析 视频教程&#xff1a; 2025版 Pandas2 Python数据处理与分析 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili melt() 是 pandas 中用于数据重塑的核心方法之一&#xff0c;它可以将 宽格式数据 转换为 长格式数据&#xff0c;特…

设计模式——工厂方法模式(创建型)

摘要 工厂方法模式是一种创建型设计模式&#xff0c;通过定义创建对象的接口&#xff0c;让子类决定实例化哪个类。它包含抽象产品、具体产品、抽象工厂和具体工厂等角色。该模式使类的实例化延迟到子类&#xff0c;具有良好的扩展性和灵活性&#xff0c;适用于多种场景&#…

软件性能之CPU

性能是个宏大而驳杂话题&#xff0c;从代码&#xff0c;到网络&#xff0c;到实施&#xff0c;方方面面都会涉及到性能问题&#xff0c;网上对性能讲解的文章多如牛毛&#xff0c;从原理到方法再到工具都有详细的介绍&#xff0c;本文虽不能免俗&#xff0c;但期望能从另外一个…

腾讯云推出云开发AI Toolkit,国内首个面向智能编程的后端服务

5月28日&#xff0c;腾讯云开发 CloudBase 宣布推出 AI Toolkit&#xff08;CloudBase AI Toolkit&#xff09;&#xff0c;这是国内首个面向智能编程的后端服务&#xff0c;适配 Cursor 等主流 AI 编程工具。 云开发 AI Toolkit旨在解决 AI 辅助编程的“最后一公里”问题&…

当前用户的Git本地配置情况:git config --local --list

通过config命令可以查询当前用户的本地配置情况。这些配置项定义了 Git 在当前仓库中的行为&#xff0c;包括文件权限处理、符号链接处理以及大小写敏感性等。 git config --local --list core.repositoryformatversion0 指定 Git 仓库的格式版本。版本 0 是最初的格式。 cor…

修改 vscode 左侧导航栏的文字大小 (更新版)

1. 起因&#xff0c; 目的: 问题&#xff1a; vscode 左侧的文字太小了&#xff01;&#xff01;&#xff01;我最火的一篇文章&#xff0c;写的就是这个问题。 看来这个问题&#xff0c;是很广泛的一个痛点。我最近更新了 vscode&#xff0c; 这个问题又出现了。再来搞一下。…

Python训练第四十天

DAY 40 训练和测试的规范写法 知识点回顾&#xff1a; 彩色和灰度图片测试和训练的规范写法&#xff1a;封装在函数中展平操作&#xff1a;除第一个维度batchsize外全部展平dropout操作&#xff1a;训练阶段随机丢弃神经元&#xff0c;测试阶段eval模式关闭dropout 昨天我们介绍…

Fine Pruned Tiled Light Lists(精细删减的分块光照列表)

概括 在这篇文章&#xff0c; 我将介绍一种Tiled Light 变体&#xff0c;主要针对AMD Graphics Core Next&#xff08;GCN&#xff09;架构进行优化&#xff0c;我们的方法应用于游戏 古墓丽影:崛起 中&#xff0c;特别是我们在通过光列表生成和阴影贴图渲染之间交错进行异步计…

《信号与系统》第 5 章 离散时间傅里叶变换

5.0 引言 第4章研究了连续时间傅里叶变换&#xff0c;并研究了这种变换的许多特性&#xff0c;这些特性使傅里叶分析方法在分析和理解连续时间信号与系统的性质时具有很大的价值。这一章将介绍并研究离散时间傅里叶变换&#xff0c;这样就完整地建立了傅里叶分析方法。 在第3…