news 2026/9/3 6:39:41

【C++笔记】堆与栈内存|对象实例化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【C++笔记】堆与栈内存|对象实例化

这篇笔记将详细解释关于C++中堆与栈内存以及从堆中实例化和从栈中实例化的区别与联系

一、核心基础概念:什么是堆、栈、堆栈?

1、栈(Stack)

栈是编译器自动管理的内存区域,遵循后进先出的(LIFO)规则,是程序最基础的内存空间

存储的内容主要有:局部变量、函数参数、栈实例化对象、函数返回地址

核心特性:

  • 自动分配、自动释放,无需人工干预

  • 空间极小(系统默认仅几MB)

  • 分配速度极快,内存连续,缓存友好

  • 生命周期严格绑定代码作用域{}

注意:上述提到的“空间极小”这个特性,后续会涉及到“内存溢出”,以及堆的内存相对较大的优势,望读者在此多留意。

栈的空间极小,这在嵌入式开发中,尤其是涉及UI人机交互界面的开发中,才要尤其注意这个问题,不然很容易出现栈溢出的问题,这个问题是十分隐患的。

由于上述提及到栈溢出,所以我不得不说一下要如何检测发现“栈溢出”

(一)、先分清:堆溢出和栈溢出

  • 栈溢出局部变量、递归把栈撑爆,程序直接闪退 / 段错误
  • 堆溢出new/malloc开辟的堆空间,写超了边界(数组越界、缓冲区越写越大) 堆溢出极其隐蔽,编译不报错、运行不一定立刻崩溃,属于最难查的内存 BUG。

(二)、堆溢出的典型现象(肉眼判断)

  1. 程序偶尔崩溃、随机崩溃不是必现
  2. 变量值莫名被篡改、逻辑突然乱掉
  3. free/delete的时候直接报错、断言失败
  4. 内存越用越高,伴随隐性内存破坏

原理:堆内存是一块块碎片化管理的,你写超当前堆块,会覆盖下一块堆的管理头信息,破坏堆链表,后期释放时才炸。

(三)、四种实用检测方法

1. 代码自查(最快)

重点检查:

  • 堆数组是否下标越界
  • strcpymemcpy不做长度限制
  • 动态申请空间过小,但写入数据过大

2. IDE 调试(VS/CLion)

堆溢出普通断点很难查,但可以看:

  • 崩溃是否发生在delete / free内部
  • 调用栈显示 heap corruption = 明确堆溢出

3. 最强神器:AddressSanitizer(推荐学者使用

检测堆溢出最强、百分百命中编译加参数:

-fsanitize=address

能精准查出:

  • 堆缓冲区溢出
  • 堆释放后野指针使用
  • 重复释放、内存泄漏

比普通调试强无数倍,学生 / 工程开发首选

接下来我对最强神器:AddressSanitizer(ASAN)进行举例说明,以方便学者理解:

(1). 什么场景属于「堆溢出」

new/malloc申请了10 字节,但是你写了 20 字节,越界覆盖后面堆内存。

普通运行:大概率不报错、偶尔闪退、诡异 BUG(极难查)

开 ASAN直接精准报行数、越界大小

(2). 错误代码(存在堆溢出)

#include <iostream> using namespace std; int main() { // 只申请 10 个字节堆内存 char *p = new char[10]; // 堆溢出:越界写入!写入 20 个字符 for(int i = 0; i < 20; i++) { p[i] = 'a'; } delete[] p; return 0; }

上述的代码的问题:

  • 堆空间只有10 字节
  • 循环硬写0~19,直接踩坏后面的堆管理块 →标准堆溢出

如果开启 ASAN 编译(检测堆溢出神器),将会是这样的效果:

g++ main.cpp -o main -fsanitize=address ./main

ASAN 直接精准爆出错误:

ERROR: AddressSanitizer: heap-buffer-overflow

明确告诉你:

  1. heap-buffer-overflow→ 堆缓冲区溢出
  2. 精确到第几行代码越界
  3. 告诉你:申请 10 字节、却访问了第 10/11... 位

接下来我再补充两个ASAN 专属可检测的堆错误举例

举例一:堆释放后野指针(Use After Free)

// 在堆上申请10字节连续内存,p指针(栈上)指向这块堆内存 char *p = new char[10]; // 手动释放p指向的堆内存,堆内存已归还给系统,彻底失效 delete[] p; // 【严重错误:Use After Free】 // 内存已经释放,属于非法悬空堆内存,强行写入数据 // 普通编译大概率不报错,开启ASAN精准检测出:Use After Free(释放后复用) p[0] = 'x';

ASAN 直接报:Use After Free

举例 2:重复释放堆内存(Double Free)

// 堆上开辟10字节内存,指针p绑定该堆空间 char *p = new char[10]; // 第一次释放:正常释放堆内存,堆空间已回收 delete[] p; // 【严重错误:Double Free】 // 内存已释放,再次对同一块内存执行释放操作 // 破坏堆内存管理链表,开启ASAN精准检测出:Double Free(重复释放) delete[] p;

ASAN 直接报:Double Free

4. 嵌入式 / 单片机堆溢出检测

嵌入式裸机 / RTOS 堆溢出特征:

  • 随机死机、任务卡死
  • 内存池数据错乱

检测方式:

  • 堆内存水印标记

原理

单片机初始化时,在每一块堆内存首尾手动写入固定标记值(水印)。 正常运行:水印不变。 堆溢出越界:会擦掉、篡改水印 → 判定堆溢出。

伪代码示例

// 1. 申请堆内存 char *buf = (char*)malloc(32); // 2. 手动打【首尾水印】 buf[-4] = 0xAA; // 头部水印 buf[32] = 0xBB; // 尾部水印 // 3. 故意堆溢出(越界写) for(int i=0; i < 64; i++){ buf[i] = 1; } // 4. 巡检判断 if(buf[-4] != 0xAA || buf[32] != 0xBB) { // 水印被篡改 = 发生堆溢出 }
  • 监控堆首尾校验值是否被篡改
  • 查看剩余堆内存持续异常下降(隐性堆溢出特征举例如下)
while(1) { // 实时读取系统剩余堆大小(RTOS自带API) int freeHeap = xPortGetFreeHeapSize(); // 如果剩余堆持续断崖式下跌、不回升 // 大概率:堆越界破坏堆链表 / 隐性内存泄漏 }
  • 内存池数据错乱(典型堆溢出 BUG)举例如下:
  • 你申请堆内存存传感器数据
  • 堆越界,踩坏旁边的内存池控制块
  • 现象:传感器数值随机跳变、任务频繁卡死、偶尔死机
  • 无代码报错,属于嵌入式最典型隐形堆溢出

最后总结堆溢出的特征:

  1. PC 端堆溢出:ASAN 直接报行数、精准定位
  2. 单片机堆溢出:无报错、随机死机、数据乱跳,只能靠水印校验 + 剩余内存监控排查

2、堆

堆是程序员手动管理的内存,由操作系统管理。

  • 存放:new/malloc出来的数据、堆实例化对象本体。
  • 分配释放:手动。new申请;delete释放。不 delete 就内存泄漏。
  • 大小:非常大,远大于栈,受物理内存限制。
  • 分配规则:没有固定顺序,随意申请、随意释放。
  • 速度:比栈慢,要调用系统接口。
  • 风险:内存泄漏、野指针、内存碎片。
Dog* p = new Dog(); // p这个指针变量本身在栈; // new出来的Dog对象本体,放在堆。 delete p;

❗重点:指针变量本身是局部变量,存在栈;指针指向的数据,才在堆。

对比维度栈 Stack堆 Heap
管理者编译器自动管理程序员手动管理 new /delete
生命周期离开作用域{}自动回收直到手动 delete 才释放
空间大小小(几 MB)很大,取决于内存
分配速度很快较慢
存放内容局部变量、函数参数、栈对象new 创建的对象、动态内存
内存特征连续,后进先出分散,容易产生碎片
主要风险栈溢出内存泄漏、野指针

3、“堆栈” 到底是什么?

  1. 汇编 / 操作系统术语:堆栈 = Stack,单指栈
  2. C/C++ 中文口语 “堆栈”:很多人偷懒,把堆 + 栈合在一起叫堆栈,是口语叫法,这时候它同时指代两块内存

⚠️做题、看书时要区分语境:

  • 教材讲操作系统:堆栈 = stack,就是栈
  • C++ 面向对象口语:“堆栈上实例化对象”= 区分栈实例、堆实例。

通俗比喻

  • 栈:酒店客房。进入函数办理入住,出函数自动退房;房间数量有限,住不下大件行李。
  • 堆:自己买房子。需要自己申请购买,不用了必须自己卖房;房子数量多,但是手续麻烦,忘了卖房就一直占资源。

二、两种对象实例化方式(核心实操)

1. 栈中实例化对象(局部对象)

代码写法

Dog dog1; // 栈实例化对象

核心特点

  • 对象本体完全存储在栈内存

  • 出当前代码作用域{},系统自动调用析构函数、回收内存

  • 成员访问方式:.(点运算符)

  • 无内存泄漏风险,代码简洁

2. 堆中实例化对象(动态对象)

Dog* dog2 = new Dog(); // 堆实例化对象 delete dog2; // 必须手动释放内存 dog2 = nullptr; // 置空避免野指针

核心特点

  • 指针变量dog2:本身存储在栈内存

  • new Dog()创建的对象本体:存储在堆内存

  • 生命周期不受{}限制,不执行 delete 则永久占用内存

  • 成员访问方式:->(箭头运算符)

  • 必须手动释放,否则造成内存泄漏、野指针问题

三、栈、堆实例化对象 优缺点对比

1、栈实例化Dog dog1;

优点

  1. 速度快:栈内存分配仅移动栈指针,开销极小。
  2. 不用手动释放,离开大括号自动调用析构回收内存,不会内存泄漏
  3. 使用简单,不需要指针,直接用.访问成员,代码简洁。
  4. 内存连续,缓存友好。

缺点

  1. 栈空间容量很小(通常只有几 MB),不能存放体积很大的对象。
  2. 生命周期受作用域{}严格限制:出了当前大括号,对象直接销毁,不能跨函数返回对象本体。
  3. 不能动态创建:编译阶段就要确定对象大小,运行时不能灵活决定要不要创建对象。

2、堆实例化Dog *dog2 = new Dog();

对象本体在堆;指针变量本身在栈,必须手动delete释放

优点

  1. 堆内存空间很大,可以创建体积巨大的对象。
  2. 生命周期灵活,不受{}作用域约束:只要不 delete,对象就存活,可以跨函数返回、长期保存。
  3. 运行时动态创建:程序运行过程中,根据条件决定是否 new 对象。
  4. 适合动态数组、链表、树等复杂数据结构。

缺点

  1. 速度比栈慢:new 需要向操作系统申请堆内存,有一定开销。
  2. 必须手动 delete 释放,忘记释放 →内存泄漏
  3. delete之后如果继续使用指针,产生野指针,程序崩溃。
  4. 使用指针,要用->访问,代码稍微复杂;还需要注意判空。
  5. 频繁 new/delete 容易产生内存碎片
对比项栈实例化(局部对象)堆实例化(new)
分配速度较慢
内存大小很小,不适合大对象空间大,支持大对象
内存释放自动,出{}销毁手动 delete,否则内存泄漏
生命周期绑定作用域{}自由,直到 delete
使用方式.运算符指针->
风险几乎无泄漏风险内存泄漏、野指针、内存碎片
适用场景小对象、临时局部变量大对象、需要跨函数存活、动态创建
  • 对象不大、只在当前函数内部使用 →优先栈实例化
  • 对象很大,或者需要跨函数保存、运行时才确定是否创建 →用堆 new,记得 delete

补充:C++11 以后推荐用智能指针std::unique_ptr管理堆对象,就不用手动写 delete,规避内存泄漏问题。

我是那个一直在奔跑路上的小小_扫地僧,愿我们能够携手同行,一起成长,顶峰相见!

敬请关注,持续更新中~~~

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/3 6:39:33

模型数据进入PB级时代:高效下载、版本锁定与语料清洗实战

上周 Hugging Face 平台单周上传模型相关数据超过 4PB 的消息&#xff0c;在开发者圈子里传得比较快。这个量级是什么概念&#xff1f;如果按普通硬盘折算&#xff0c;相当于上千块 4TB 企业盘&#xff1b;如果按千兆网络连续下载&#xff0c;也足够拉好几天。作为长期靠 huggi…

作者头像 李华
网站建设 2026/9/3 6:38:40

【Windows运维】网络故障排查全套命令手册

排错核心逻辑&#xff1a;本机协议栈 → 网卡配置 → 内网连通 → 网关路由 → 外网链路 → DNS解析 → 端口检测 → 网络重置 → 日志溯源1. ping 127.0.0.1&#xff08;回环地址检测&#xff09;命令格式&#xff1a;ping 127.0.0.1作用&#xff1a;不经过网卡、网线&#xf…

作者头像 李华
网站建设 2026/9/3 6:37:38

手写文字智能擦除:基于U-Net++的图文分离与背景重建方案

简介&#xff1a;本资源是AI图像处理挑战赛「手写文字擦除」赛道冠军方案的完整复现包&#xff0c;面向计算机视觉方向的研究者、算法工程师及深度学习实践者&#xff0c;聚焦于复杂试卷场景下多色手写体、手绘线条、污渍脏点与印刷文字重叠等真实干扰的精准擦除任务。压缩包共…

作者头像 李华
网站建设 2026/9/3 6:35:49

毫米波雷达生命体征信号模拟系统:Matlab实现与算法验证指南

简介&#xff1a;这是一套面向电子工程、生物医学工程及信号处理方向高年级本科生与初研人员的毫米波雷达生命体征信号仿真工具&#xff0c;解决无硬件条件下开展呼吸/心跳微动信号建模、滤波与特征提取算法验证的实践难题。资源包共5个文件&#xff08;122KB&#xff09;&…

作者头像 李华
网站建设 2026/9/3 6:34:47

从Jeff Dean创业看AI基础设施变革:开发者如何应对新范式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华