📌 1. 野指针 (Wild Pointer)

什么是野指针?

野指针指的是未初始化的指针变量。它指向的内存地址是随机的、未知的。

产生原因

cpp

int* ptr; // 野指针!未初始化,指向随机地址
*ptr = 10; // 危险!可能破坏系统内存char* str; // 同样是野指针
std::cout << *str; // 未定义行为

危险后果

  1. ** segmentation fault**:访问受保护的内存区域

  2. 数据损坏:意外修改了其他程序或系统的数据

  3. 难以调试:错误表现随机,难以重现

解决方法

总是初始化指针

cpp

int* ptr = nullptr; // 初始化为空指针
int* ptr2 = new int(10); // 初始化为有效内存
int* ptr3 = &someVariable; // 指向已有变量

📌 2. 悬空指针 (Dangling Pointer)

什么是悬空指针?

悬空指针指的是指针指向的内存已被释放,但指针本身仍然保存着原来的地址。

产生原因

情况1:释放后未置空

cpp

int* ptr = new int(10);
delete ptr; // 内存已释放
// 现在ptr是悬空指针!
*ptr = 20; // 危险!访问已释放内存
情况2:局部变量返回

cpp

int* createArray() {int arr[5] = {1, 2, 3, 4, 5};return arr; // 返回局部数组的指针
} // arr的内存被释放int* danglingPtr = createArray(); // 悬空指针!
std::cout << danglingPtr[0]; // 未定义行为
情况3:多个指针指向同一内存

cpp

int* ptr1 = new int(100);
int* ptr2 = ptr1; // 两个指针指向同一内存delete ptr1; // 释放内存
// 现在ptr1和ptr2都是悬空指针!
std::cout << *ptr2; // 危险!

危险后果

  1. ** use-after-free**:使用已释放的内存

  2. 数据损坏:可能覆盖新分配的内存数据

  3. 安全漏洞:可能被利用进行攻击

解决方法

方法1:释放后立即置空

cpp

int* ptr = new int(10);
delete ptr;
ptr = nullptr; // 重要!释放后立即置空if (ptr != nullptr) { // 安全检查*ptr = 20;
}
方法2:使用智能指针(推荐)

cpp

#include <memory>
std::shared_ptr<int> ptr1 = std::make_shared<int>(10);
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权// 不需要手动delete,自动管理生命周期
// 当所有shared_ptr都超出作用域时,内存自动释放
方法3:避免返回局部变量地址

cpp

// 错误
int* createArray() {int arr[5] = {1, 2, 3, 4, 5};return arr; // 不要这样做!
}// 正确:动态分配
int* createArray() {int* arr = new int[5]{1, 2, 3, 4, 5};return arr; // 调用者需要负责delete[]
}// 更正确:使用vector
std::vector<int> createArray() {return {1, 2, 3, 4, 5}; // 安全!
}

📌 一、什么是内存对齐?

内存对齐指的是数据在内存中的存储地址必须是某个值(通常是2、4、8、16等2的幂次方)的整数倍。

简单例子

cpp

struct Example {char a;      // 1字节int b;       // 4字节  short c;     // 2字节
};

没有对齐时(假设从地址0开始):

text

地址: 0   1   2   3   4   5   6   7   8   9
数据: [a][b][b][b][b][c][c][ ][ ][ ]
总大小: 7字节?但实际上...

实际对齐后(在64位系统 typical alignment):

text

地址: 0   1   2   3   4   5   6   7   8   9   10  11
数据: [a][ ][ ][ ][b][b][b][b][c][c][ ][ ]
总大小: 12字节!(因为有填充字节)

📌 二、为什么需要内存对齐?

1. 硬件要求(最主要的原因)

现代CPU不是以字节为单位访问内存,而是以字长(word size)为单位(通常为4字节或8字节)。

不对齐访问的代价

cpp

// 假设int需要4字节对齐,但存储在地址0x3
int* ptr = (int*)0x3; 
int value = *ptr; // CPU需要2次内存访问!

CPU需要:

  1. 读取地址0x0-0x3的4字节

  2. 读取地址0x4-0x7的4字节

  3. 拼接出需要的4字节数据

2. 性能优化

对齐的内存访问只需要1次内存操作,而不是2次或更多。

性能对比

访问类型CPU操作次数性能影响
对齐访问1次⚡ 最快
不对齐访问2次🐢 慢2倍
严重不对齐多次🚫 极慢

3. 平台兼容性

某些架构(如ARM、SPARC)根本不允许未对齐的内存访问,会导致硬件异常。

cpp

// 在ARM架构上可能直接崩溃!
int* misaligned_ptr = (int*)(char_buffer + 1);
int value = *misaligned_ptr; // 硬件异常!

📌 三、对齐规则和示例

基本对齐规则

每个数据类型都有自然的对齐要求:

  • char:1字节对齐

  • short:2字节对齐

  • int:4字节对齐

  • float:4字节对齐

  • double:8字节对齐

  • 指针:4字节(32位)或8字节(64位)对齐

结构体对齐示例

cpp

struct MyStruct {char a;      // 1字节,偏移0// 3字节填充(因为int需要4字节对齐)int b;       // 4字节,偏移4  short c;     // 2字节,偏移8// 2字节填充(使整体大小为最大成员的整数倍)
}; // 总大小:12字节

内存布局

text

偏移: 0   1   2   3   4   5   6   7   8   9   10  11
数据: [a][pad][pad][pad][b][b][b][b][c][c][pad][pad]

📌 四、如何控制内存对齐?

1. 编译器指令(通用)

cpp

// 强制4字节对齐
struct alignas(4) MyStruct {char a;int b;short c;
}; // 大小:8字节(而不是12字节)// 或者使用pragma(编译器特定)
#pragma pack(push, 1) // 强制1字节对齐(无填充)
struct TightPacked {char a;int b;  short c;
}; // 大小:7字节
#pragma pack(pop) // 恢复默认对齐

2. C++11 alignas 关键字

cpp

#include <iostream>struct alignas(16) AlignedStruct {int a;double b;char c;
};int main() {std::cout << "Alignment: " << alignof(AlignedStruct) << std::endl;std::cout << "Size: " << sizeof(AlignedStruct) << std::endl;return 0;
}

3. 动态内存对齐

cpp

#include <cstdlib>
#include <iostream>// C11/C++17 的动态对齐分配
void* aligned_memory = std::aligned_alloc(64, 1024); // 64字节对齐,分配1KB
// 使用...
std::free(aligned_memory);

在 C++ 中,智能指针是一种封装了原始指针的类模板,用于自动管理动态内存,避免内存泄漏。它们通过 RAII(资源获取即初始化)机制,在离开作用域时自动释放所指向的内存。C++ 标准库提供了三种主要的智能指针:unique_ptrshared_ptr 和 weak_ptr,它们各自有不同的特性和用途。

1. unique_ptr:独占所有权的智能指针

  • 特性:同一时间内,只能有一个 unique_ptr 指向某块内存,即所有权是独占的。
  • 行为
    • 不允许拷贝(copy),但允许移动(move),即所有权可以转移。
    • 当 unique_ptr 离开作用域或被销毁时,会自动释放所指向的内存。
  • 适用场景
    • 管理单个对象的动态内存,且不需要共享所有权。
    • 作为函数的返回值或参数(通过移动语义传递)。
  • 示例

    cpp

    运行

    #include <memory>
    int main() {std::unique_ptr<int> ptr1(new int(10));  // 独占指向10的内存// std::unique_ptr<int> ptr2 = ptr1;  // 错误:不能拷贝std::unique_ptr<int> ptr2 = std::move(ptr1);  // 正确:转移所有权(ptr1变为空)return 0;
    }  // ptr2离开作用域,自动释放内存
    

2. shared_ptr:共享所有权的智能指针

  • 特性:允许多个 shared_ptr 指向同一块内存,通过引用计数跟踪所有者数量。
  • 行为
    • 当一个 shared_ptr 被拷贝时,引用计数加 1;当被销毁时,引用计数减 1。
    • 当引用计数减为 0 时,自动释放所指向的内存。
  • 适用场景
    • 需要多个对象共享同一资源的所有权(例如:树结构中父节点和子节点互相引用)。
  • 注意
    • 避免循环引用(如两个 shared_ptr 互相指向对方),会导致引用计数无法归零,内存泄漏。此时需配合 weak_ptr 解决。
  • 示例

    cpp

    运行

    #include <memory>
    int main() {std::shared_ptr<int> ptr1(new int(20));std::shared_ptr<int> ptr2 = ptr1;  // 引用计数变为2{std::shared_ptr<int> ptr3 = ptr1;  // 引用计数变为3}  // ptr3销毁,引用计数变为2return 0;
    }  // ptr1和ptr2销毁,引用计数变为0,内存释放
    

3. weak_ptr:弱引用的智能指针

  • 特性:一种 “弱引用”,不拥有所指向内存的所有权,也不影响引用计数。
  • 行为
    • 必须从 shared_ptr 转换而来,无法直接管理内存。
    • 可以通过 lock() 方法获取一个 shared_ptr(若内存未释放),否则返回空。
  • 适用场景
    • 解决 shared_ptr 的循环引用问题。
    • 需访问某资源,但不希望延长其生命周期(例如:缓存、观察者模式)。
  • 示例

    cpp

    运行

    #include <memory>
    struct Node {std::weak_ptr<Node> parent;  // 用weak_ptr避免循环引用// std::shared_ptr<Node> parent;  // 若用shared_ptr,会导致循环引用
    };int main() {std::shared_ptr<Node> child(new Node());std::shared_ptr<Node> parent(new Node());child->parent = parent;  // weak_ptr不增加引用计数return 0;
    }  // 引用计数正常归零,内存释放
    

三者的核心区别总结

智能指针所有权拷贝 / 移动引用计数主要用途
unique_ptr独占禁止拷贝,允许移动管理单个对象,不共享所有权
shared_ptr共享允许拷贝和移动多对象共享同一资源
weak_ptr无所有权允许拷贝和移动不影响解决循环引用,弱引用资源

通过合理使用这三种智能指针,可以大幅减少手动管理内存的错误,使 C++ 代码更安全、更易维护。

在 C++ 中,循环引用(Circular Reference) 是使用shared_ptr时容易出现的问题,它会导致内存泄漏。这一问题的根源在于shared_ptr的引用计数机制与循环依赖的结合,使得引用计数无法归零,最终导致动态内存无法释放。

什么是循环引用?

当两个或多个shared_ptr互相持有对方的引用,形成一个 “闭环” 时,就会发生循环引用。此时,每个shared_ptr的引用计数都无法减到 0,导致它们指向的内存永远不会被释放,造成内存泄漏。

举个具体例子

假设我们有两个类AB,它们互相用shared_ptr引用对方:

cpp

运行

#include <memory>class B;  // 前向声明class A {
public:std::shared_ptr<B> b_ptr;  // A持有B的shared_ptr~A() { std::cout << "A被销毁" << std::endl; }
};class B {
public:std::shared_ptr<A> a_ptr;  // B持有A的shared_ptr~B() { std::cout << "B被销毁" << std::endl; }
};int main() {{std::shared_ptr<A> a(new A());std::shared_ptr<B> b(new B());// 形成循环引用:a持有b,b持有aa->b_ptr = b;b->a_ptr = a;}  // 离开作用域,预期A和B被销毁return 0;
}

运行结果:程序不会输出 “A 被销毁” 和 “B 被销毁”,说明AB的内存没有被释放,发生了内存泄漏。

为什么会发生内存泄漏?

我们一步步分析引用计数的变化:

  1. 创建a(指向 A 对象)时,A 的引用计数为 1。
  2. 创建b(指向 B 对象)时,B 的引用计数为 1。
  3. a->b_ptr = b:B 的引用计数变为 2(ba->b_ptr共同引用)。
  4. b->a_ptr = a:A 的引用计数变为 2(ab->a_ptr共同引用)。
  5. 离开作用域时,ab被销毁:
    • a销毁:A 的引用计数从 2 减为 1(剩余b->a_ptr的引用)。
    • b销毁:B 的引用计数从 2 减为 1(剩余a->b_ptr的引用)。
  6. 此时,A 和 B 的引用计数都为 1,永远不会再减为 0,它们的内存永远不会被释放。

如何解决循环引用?

使用weak_ptr可以打破循环引用。weak_ptr是一种 “弱引用”,它持有对对象的引用,但不增加引用计数,因此不会影响对象的生命周期。

修改上面的例子,将其中一个shared_ptr改为weak_ptr

cpp

运行

#include <memory>
#include <iostream>class B;class A {
public:std::shared_ptr<B> b_ptr;  // 仍用shared_ptr~A() { std::cout << "A被销毁" << std::endl; }
};class B {
public:std::weak_ptr<A> a_ptr;  // 改为weak_ptr,不增加引用计数~B() { std::cout << "B被销毁" << std::endl; }
};int main() {{std::shared_ptr<A> a(new A());std::shared_ptr<B> b(new B());a->b_ptr = b;  // B的引用计数变为2b->a_ptr = a;  // A的引用计数仍为1(weak_ptr不增加计数)}  // 离开作用域return 0;
}

运行结果:输出 “A 被销毁” 和 “B 被销毁”,内存正常释放。

原因分析

  1. b->a_ptrweak_ptr,赋值时 A 的引用计数仍为 1(不增加)。
  2. 离开作用域时,a销毁:A 的引用计数从 1 减为 0 → A 被释放。
  3. A 释放后,其成员b_ptr(指向 B)被销毁:B 的引用计数从 2 减为 1。
  4. 随后b销毁:B 的引用计数从 1 减为 0 → B 被释放。

总结

  • 循环引用shared_ptr的常见陷阱,由互相引用的shared_ptr形成闭环导致。
  • 核心问题:引用计数无法归零,内存无法释放。
  • 解决方案:在循环引用的一方使用weak_ptr,打破计数闭环。
  • weak_ptr的适用场景:需要引用对象,但不希望影响其生命周期(如解决循环引用、缓存等)。

通过合理搭配shared_ptrweak_ptr,可以既享受共享所有权的便利,又避免循环引用带来的内存泄漏。

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

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

相关文章

Spring代理的特点

一.Spring代理的特点1.依赖注入和初始化影响的是原始的对象。2.代理和目标是两个对象&#xff0c;二者成员变量不共用数据。二.测试首先准备以下几个类。Bean1package com.example.springdemo.demos.a13;import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.…

嵌入式学习日记(35)TCP并发服务器构建

单循环服务器&#xff1a;服务端同一时刻只能处理一个客户端的任务并发服务器&#xff1a;服务端同一时刻可以处理多个客户端的任务TCP并发服务器构建&#xff1a;TCP&#xff1a; 1. 建立连接&#xff0c;一对一TCP服务端并发模型&#xff1a;1. 多进程进程资源开销大&#x…

【Flask】测试平台开发实战-第二篇

概述&#xff1a;在上一篇我们已经将后端初始化服务后前端的vue框架初始化已经搭建完整&#xff0c;并且可以运行看到界面&#xff0c;服务正常&#xff0c;当然我们看到的前端数据都是mock下的假数据&#xff0c;并不是真实的&#xff0c;这篇我们来开发实现第一个功能&#x…

聊一聊 .NET 的 AssemblyLoadContext 可插拔程序集

一&#xff1a;背景 1. 讲故事 最近在分析一个崩溃dump时&#xff0c;发现祸首和AssemblyLoadContext有关&#xff0c;说实话这东西我也比较陌生&#xff0c;后来查了下大模型&#xff0c;它主要奔着替代 .NetFrameWork 时代的 AppDomain 的&#xff0c;都是用来做晚期加卸载&a…

Java中使用Spring Boot+Ollama实现本地AI的MCP接入

目录结构完善spring bootpom.xml添加依赖application.ymlMCP 工具配置 mcp-servers.json配置类编写API在我的上一篇文章搭建好本地的聊天机器人后&#xff0c;准备接入MCP进一步增强AI的能力&#xff0c;以实现类似手机AI的功能 参考的是第二篇文章链接其内容比较精炼&#x…

C#正则表达式与用法

&#x1f31f; C# 常用正则表达式与用法C# 使用正则需要引用命名空间&#xff1a;using System.Text.RegularExpressions; 常用方法&#xff1a;Regex.IsMatch(input, pattern) → 返回 bool&#xff0c;用于验证Regex.Match(input, pattern) → 返回 Match 对象&#xff0c;可…

从0开始学习Java+AI知识点总结-27.web实战(Maven高级)

一、分模块设计与开发&#xff1a;让项目结构更清晰1.1 为什么需要分模块&#xff1f;单模块开发的痛点在小型项目中&#xff0c;单模块&#xff08;所有代码放在一个工程&#xff09;或许能满足需求&#xff0c;但项目规模扩大后会出现两大核心问题&#xff1a;维护成本高&…

Ferris Wheel (贪心 | 双指针)

题目&#xff1a;思路&#xff1a;本题注意题目的条件即可&#xff0c;题意说一个摩天轮可以坐一个人或者两个人&#xff0c;那么显然我们就可以贪心一下具体的&#xff0c;我们可以让最小的去匹配最大的&#xff0c;如果此时大于 x&#xff0c;那么显然我们根本无法使得 最大的…

课程视频怎么加密?在线教育机构常用的6个课程加密方法

知识付费时代&#xff0c;课程视频是教育机构的核心资产。但是不难发现&#xff0c;课程视频的安全却得不到保障。各大购物平台搜索课程名称&#xff0c;便出现了许多盗版课程。如何有效防止课程被翻录和二次传播&#xff0c;成为急需解决的关键问题。今天这期分享点干货&#…

SOME/IP-SD中”服务器服务组播端点”、“客户端服务组播端点”与“IPv4组播选项的区分

<摘要> AUTOSIP-SD协议中组播端点&#xff08;Multicast Endpoint&#xff09;在不同上下文中的角色与表述差异。准确理解“服务器服务组播端点”、“客户端服务组播端点”与“IPv4组播选项”中配置的端点之间的关系&#xff0c;是正确实现组播事件分发机制的关键。这涉及…

计算机是如何运行的

目录 一&#xff0c;计算机是如何组成的 1.1&#xff0c;CPU中央处理单元 1.1.1&#xff0c;CPU的构成和属性 1.1.2&#xff0c;如何判断cpu的好坏 1.1.3&#xff0c;指令 1.1.4&#xff0c;CPU的缓存 1.2&#xff0c;操作系统 1.2.1&#xff0c;进程 1.2.2&#xff0…

JavaScript性能优化:实战技巧与高效策略

JavaScript性能优化实战技术文章大纲性能优化的重要性解释为什么性能优化对用户体验和业务指标至关重要列举常见性能问题的影响&#xff08;如跳出率、转化率下降&#xff09;代码层面的优化减少全局变量使用&#xff0c;避免内存泄漏使用事件委托减少事件监听器的数量避免频繁…

解决.env.production 写死 IP 的问题:Vue + config.json 运行时加载方案

背景&#xff1a;前端常用 .env.production 在构建时写死 API 地址 场景&#xff1a;运维部署时经常不知道目标主机 IP/域名 问题&#xff1a;每次 IP 变动都要重新编译 → 增加运维成本 引出需求&#xff1a;只修改 IP 就能完成部署&#xff0c;不需要重新打包 目录一、解决方…

如何从三星手机转移到另一部三星手机

三星Galaxy S系列因其出色的设计、令人惊叹的显示屏、惊艳的摄像头、更好的扬声器以及创新的指纹传感器而受到大多数用户的欢迎&#xff0c;获得了良好的声誉。让用户感到满意的是&#xff0c;三星Galaxy S10拥有更美观的设计、令人惊叹的显示屏、令人惊叹的摄像头、更好的扬声…

聚焦建筑能源革新!安科瑞 “光储直柔” 方案护航碳中和目标实现

1、背景在 “双碳” 目标引领下&#xff0c;能源结构转型与建筑能效提升成为重要课题。清华大学江亿院士提出的 “光储直柔” 新型配电系统&#xff0c;为建筑领域绿色发展提供了创新方向。光储直柔得到了业界广泛认同和积极响应&#xff0c;国家、各部委、地区陆续出台相关政策…

Shell 中 ()、(())、[]、{} 的用法详解

文章目录Shell 中 ()、(())、[]、{} 的用法详解一、先明确&#xff1a;四类符号的核心功能定位二、逐个拆解&#xff1a;用法、示例与避坑点1. ()&#xff1a;子 Shell 执行&#xff0c;隔离环境核心用法1&#xff1a;子 Shell 执行命令&#xff0c;隔离变量核心用法2&#xff…

开发避坑指南(41):Vue3 提示框proxy.$modal.msgSuccess()提示文本换行解决方案

需求 由于接口返回的提示信息过长&#xff0c;接口已经在返回提示中加入换行标签了&#xff0c;但是使用proxy.modal.msgSuccess(res.msg)提示没有换行&#xff0c;那么Vue3中proxy.modal.msgSuccess(res.msg)提示没有换行&#xff0c;那么Vue3 中 proxy.modal.msgSuccess(res.…

[Sync_ai_vid] 唇形同步推理流程 | Whisper架构

链接&#xff1a;https://github.com/bytedance/LatentSync/blob/main/docs/syncnet_arch.md docs&#xff1a;LatentSync LatentSync是一个端到端唇语同步项目&#xff0c;能够生成语音与唇形完美匹配的逼真视频。 该项目通过使用*音频条件化3D U-Net*&#xff08;一种生成式…

uniapp中 ios端 scroll-view 组件内部子元素z-index失效问题

发现子组件中的弹窗在ios手机上会被限制在scroll-view里面&#xff0c;安卓手机上不受限制&#xff0c;网上找了好久原因 scroll-view组件内部设置了 -webkit-overflow-scrolling: touch 样式&#xff0c;导致z-index失效&#xff08;safari 3D变换会忽略z-index的层级问题&…

PyTorch图像预处理完全指南:从基础操作到GPU加速实战

引言 图像预处理是模型性能的"隐形基石"&#xff0c;在计算机视觉任务中直接决定模型能否提取有效特征。科学的预处理流程能让基础模型性能提升15%以上&#xff0c;而GPU加速预处理可使数据准备阶段耗时降低60%以上。本文将聚焦PyTorch预处理核心技术&#xff0c;从基…