[C++] 智能指针

文章目录

  • 智能指针的使用原因及场景分析
    • 为什么需要智能指针?
      • 异常抛出导致的资源泄漏
      • 问题分析
    • 智能指针与RAII
      • C++常用智能指针
    • 使用智能指针优化代码
      • 优化后的代码
      • 优化点分析
    • 析构函数中的异常问题
      • 解决方法
  • RAII 和智能指针的设计思路详解
    • 什么是 RAII?
      • RAII 的工作原理
    • 智能指针的设计思路
    • RAII 与智能指针的结合示例
      • 示例代码
      • 示例说明
    • 智能指针 VS 原生指针
  • C++ 标准库的智能指针
    • 智能指针概述
    • `std::unique_ptr`
      • 使用示例
    • `std::shared_ptr`
      • 使用示例
    • `std::weak_ptr`
      • 使用示例
    • 删除器的使用
      • 使用自定义删除器
    • `make_shared` 与资源初始化
    • 智能指针的注意事项
    • 总结:
  • 智能指针原理
    • RAII 思想
    • 封装原生指针
    • 资源管理方式
      • 独占式资源管理:`unique_ptr`
      • 共享式资源管理:`shared_ptr`
      • 弱引用:`weak_ptr`
    • 析构器的自动调用
    • 引用计数的实现(`shared_ptr`)
      • 动态分配引用计数
      • 引用计数的增减
      • 循环引用问题
    • 自定义删除器
    • 智能指针的关键实现点
  • C++11中三种智能指针的模拟实现及原理理解
    • C++98 `auto_ptr`
    • C++11`unique_ptr`
    • C++11`shared_ptr`
    • C++11`weak_ptr`
  • `shared_ptr` 循环引用引出 `weak_ptr` 的作用
    • 循环引用的形成
        • 示例代码:
        • 问题分析:
    • 循环引用的理解
    • 使用 `weak_ptr` 解决循环引用
        • 改进后的代码:
        • 改进点:
    • `weak_ptr` 的特性与用法
      • 打破循环引用
      • 提供非强引用(非拥有型引用)
        • 场景示例:
      • 检查资源是否过期
        • 示例代码:
        • 输出结果:
      • 安全访问资源
      • 总结
  • `shared_ptr` 的线程安全问题与解决方案
    • 引用计数的线程安全
        • 问题描述:
        • 默认的 `shared_ptr`:
    • 被管理资源的线程安全
    • 自定义 `shared_ptr` 引用计数的线程安全
        • 原始问题示例(线程不安全):
    • 使用 `std::atomic` 确保引用计数线程安全
    • 使用互斥锁确保线程安全
    • 资源访问的线程安全
        • 示例代码:
        • 输出结果:
        • 分析:
    • 总结
  • 内存泄漏
    • 内存泄漏的危害
    • 如何检测内存泄漏
      • Linux 下内存泄漏检测工具
      • Windows 下内存泄漏检测工具
      • 跨平台工具
    • 如何避免内存泄漏
      • 良好的编码规范
      • 使用智能指针
      • 采用 RAII 思想
        • 定期检查内存泄漏
        • **防止循环引用**
    • 示例:常见内存泄漏场景及解决
        • 场景 1:忘记释放内存
        • 场景 2:循环引用导致内存泄漏
      • 总结

智能指针的使用原因及场景分析

在现代C++开发中,资源管理(包括内存、文件句柄、锁等)是一个至关重要的问题。特别是在异常安全性设计中,如何避免资源泄漏是开发者必须面对的挑战。


为什么需要智能指针?

在C++中,资源的申请与释放通常是手动管理的。比如new分配内存,delete释放内存。但是,以下场景会导致资源管理复杂化:

异常抛出导致的资源泄漏

当程序执行到一半抛出异常时,如果之前分配的资源没有及时释放,就会产生资源泄漏问题。例如:

double Divide(int a, int b)
{if (b == 0){throw "Divide by zero condition!"; // 抛出异常}return static_cast<double>(a) / b;
}void Func()
{int* array1 = new int[10]; // 申请资源1int* array2 = new int[10]; // 申请资源2try{int len, time;std::cin >> len >> time;std::cout << Divide(len, time) << std::endl; // 可能抛出异常}catch (...){std::cout << "delete [] array1" << std::endl;delete[] array1; // 释放资源1std::cout << "delete [] array2" << std::endl;delete[] array2; // 释放资源2throw; // 重新抛出异常}// 手动释放资源delete[] array1;delete[] array2;
}

问题分析

上面的代码逻辑看似解决了资源释放问题,但:

  1. 代码复杂度高:需要频繁捕获异常,手动释放资源,极易出错。
  2. 异常嵌套问题:如果new array2本身抛出异常,array1也会因为没有被释放而造成资源泄漏。
  3. 资源管理分散:释放逻辑需要在多个地方手动维护。

因此,为了解决这些问题,C++引入了RAII(资源获取即初始化)思想,结合智能指针自动管理资源。


智能指针与RAII

**RAII(Resource Acquisition Is Initialization)**是一种设计原则:资源的申请和释放绑定到对象的生命周期中。通过智能指针,将资源管理从手动控制转变为自动化管理。

C++常用智能指针

C++11标准引入了三种常用智能指针:

智能指针包含在头文件<memory>

  • std::unique_ptr:独占式所有权,适用于单个对象。
  • std::shared_ptr:共享式所有权,适用于多个对象共享。
  • std::weak_ptr:弱引用,解决shared_ptr循环引用问题。

以下是使用智能指针优化上面代码的实现。


使用智能指针优化代码

优化后的代码

#include <iostream>
#include <memory>
#include <stdexcept>double Divide(int a, int b)
{if (b == 0){throw std::runtime_error("Divide by zero condition!");}return static_cast<double>(a) / b;
}void Func()
{// 使用智能指针管理动态数组std::unique_ptr<int[]> array1(new int[10]);std::unique_ptr<int[]> array2(new int[10]);try{int len, time;std::cin >> len >> time;std::cout << Divide(len, time) << std::endl;}catch (...){// 不需要手动delete,智能指针会自动释放资源throw; // 重新抛出异常 }
}int main()
{try{Func();}catch (const std::exception& e){std::cout << e.what() << std::endl;}catch (...){std::cout << "未知异常" << std::endl;}return 0;
}

优化点分析

  1. 异常安全性提升
    • 智能指针的析构函数自动释放资源,避免了异常导致的资源泄漏。
  2. 代码简洁性提升
    • 不需要显式调用delete,降低了手动管理资源的复杂度。
  3. 避免重复代码
    • 资源释放逻辑不再需要多次书写,减少了人为错误的可能性。

析构函数中的异常问题

另一个常见问题是析构函数中抛出异常。例如:

class Resource
{
public:~Resource(){// 假设要释放多个资源for (int i = 0; i < 10; ++i){if (i == 5) // 假设释放到一半时出错{throw std::runtime_error("Error during destruction");}}}
};

如果析构函数抛出异常,会导致程序在堆栈展开过程中不知如何处理,可能直接导致程序崩溃。

解决方法

  1. 析构函数中不要抛出异常
    • 析构函数应该尽量捕获所有异常,确保不抛出。
  2. 智能指针辅助管理
    • 使用智能指针将资源的释放交给其析构函数处理。

RAII 和智能指针的设计思路详解

在C++开发中,资源管理(如内存、文件、网络连接等)是一个常见且关键的问题。如果资源没有被正确释放,可能会导致资源泄漏,进而引发性能问题甚至程序崩溃。为了高效且安全地管理资源,C++引入了RAII(资源获取即初始化)设计思想,而智能指针则是RAII思想的一种具体实现。


什么是 RAII?

RAIIResource Acquisition Is Initialization 的缩写,中文翻译为“资源获取即初始化”。其核心思想是将资源的管理与对象的生命周期绑定,通过对象的构造函数获取资源,并在析构函数中释放资源。RAII的优点包括:

  1. 避免资源泄漏:当对象生命周期结束时,资源会被自动释放。
  2. 异常安全性:RAII在异常发生时,仍然可以保证资源正确释放。
  3. 代码简洁性:减少了手动释放资源的复杂逻辑。

RAII 的工作原理

RAII 将资源的获取与对象的初始化绑定。例如:

  1. 在构造函数中获取资源。
  2. 在析构函数中释放资源。
  3. 在对象的生命周期内,确保资源始终处于有效状态。

RAII 管理的资源可以包括:

  • 动态内存(new/delete
  • 文件句柄
  • 网络连接
  • 互斥锁等。

智能指针的设计思路

智能指针是 RAII 的一个典型实现,它不仅符合 RAII 的设计理念,还通过重载运算符模拟指针的行为,使资源的访问更加方便。例如:

  • 通过重载 operator*operator->,可以像普通指针一样操作资源。
  • 通过重载 operator[],可以支持访问数组元素的操作。

以下是一个简单的智能指针类的实现:

template<class T>
class SmartPtr
{
public:// 构造函数:获取资源SmartPtr(T* ptr): _ptr(ptr){}// 析构函数:释放资源~SmartPtr(){std::cout << "delete[] " << _ptr << std::endl;delete[] _ptr; // 确保资源释放}// 重载运算符,方便访问资源T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}private:T* _ptr; // 内部指针,管理资源
};

RAII 与智能指针的结合示例

下面的示例展示了如何使用自定义的智能指针类解决资源管理问题。

示例代码

#include <iostream>
using namespace std;template<class T>
class SmartPtr
{
public:SmartPtr(T* ptr): _ptr(ptr){}~SmartPtr(){cout << "delete[] " << _ptr << endl;delete[] _ptr;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t i){return _ptr[i];}private:T* _ptr;
};double Divide(int a, int b)
{if (b == 0){throw "Divide by zero condition!";}return static_cast<double>(a) / b;
}void Func()
{// 使用智能指针管理动态数组SmartPtr<int> sp1 = new int[10];SmartPtr<int> sp2 = new int[10];for (size_t i = 0; i < 10; i++){sp1[i] = sp2[i] = i;}int len, time;cin >> len >> time;cout << Divide(len, time) << endl;
}int main()
{try{Func();}catch (const char* errmsg){cout << errmsg << endl;}catch (const exception& e){cout << e.what() << endl;}catch (...){cout << "未知异常" << endl;}return 0;
}

示例说明

  1. RAII 的作用
    • SmartPtr 构造时获取资源(new int[10]),析构时释放资源(delete[])。
    • 即使发生异常,析构函数会在堆栈展开时自动调用,确保资源不会泄漏。
  2. 异常安全性
    • 如果Divide函数中抛出异常,SmartPtr对象会在函数退出时调用析构函数,自动释放资源。
  3. 简化资源管理逻辑
    • 不再需要显式调用delete,释放逻辑被封装到智能指针中。
  4. 访问资源的便利性
    • 通过重载运算符,支持数组下标操作(sp1[i])、指针操作(*sp1)等,使用体验接近普通指针。

智能指针 VS 原生指针

特性原生指针智能指针
资源释放手动调用 delete析构函数自动释放
异常安全性容易造成资源泄漏保证异常安全
使用复杂度需要手动管理资源自动化管理,降低复杂度
指针行为支持支持基本指针操作重载运算符,模拟指针行为
循环引用问题(shared_ptr存在通过weak_ptr解决

C++ 标准库的智能指针

C++ 标准库中的智能指针提供了一种安全、高效的资源管理方式,减少了资源泄漏和悬空指针的风险,同时显著提高了代码的异常安全性和可读性。以下是 C++ 标准库智能指针的全面介绍及使用示例。


智能指针概述

智能指针主要有以下几种类型,均定义在 <memory> 头文件中:

  1. std::auto_ptr (C++98,已废弃):
    • 拷贝时会转移资源管理权,但导致被拷贝对象悬空。
    • C++11 后被强烈建议弃用,C++17 已移除。
  2. std::unique_ptr(C++11 引入):
    • 独占式管理资源,不支持拷贝,只支持移动。
    • 非常适合无需共享资源的场景。
  3. std::shared_ptr(C++11 引入):
    • 共享式管理资源,底层通过引用计数实现。
    • 支持拷贝和移动,适用于需要共享资源的场景。
  4. std::weak_ptr(C++11 引入):
    • 弱引用指针,不管理资源,仅观察资源。
    • 用于解决 shared_ptr 循环引用问题。

std::unique_ptr

std::unique_ptr 是一个独占式智能指针,不能被拷贝,但可以通过移动语义转移资源管理权。

使用示例

#include <iostream>
#include <memory>struct Date {int _year, _month, _day;Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}~Date() { std::cout << "~Date()" << std::endl; }
};int main() {std::unique_ptr<Date> up1(new Date(2024, 11, 16));// 不支持拷贝// std::unique_ptr<Date> up2 = up1; // 编译错误// 支持移动std::unique_ptr<Date> up3 = std::move(up1);if (!up1) {std::cout << "up1 is null after move." << std::endl;}if (up3) {std::cout << "up3 owns the resource." << std::endl;}return 0;
}

特性

  • 自动释放资源,无需手动调用 delete
  • 避免资源泄漏和悬空指针。
  • 使用场景:独占资源的管理。

std::shared_ptr

std::shared_ptr 是一个共享式智能指针,底层通过引用计数控制资源生命周期。

使用示例

#include <iostream>
#include <memory>struct Date {int _year, _month, _day;Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}~Date() { std::cout << "~Date()" << std::endl; }
};int main() {std::shared_ptr<Date> sp1(new Date(2024, 11, 16));std::shared_ptr<Date> sp2 = sp1; // 拷贝,引用计数增加std::cout << "sp1 use_count: " << sp1.use_count() << std::endl;sp2->_year = 2025;std::cout << "Year: " << sp1->_year << std::endl; // sp1 和 sp2 共享资源return 0;
}

特性

  • 引用计数控制资源生命周期。
  • 当最后一个 shared_ptr 被销毁时,资源才会被释放。
  • 使用场景:多个对象共享同一资源。

std::weak_ptr

std::weak_ptr 是一种非 RAII 的弱引用智能指针,设计用于解决 shared_ptr 的循环引用问题。

使用示例

#include <iostream>
#include <memory>struct Node {std::shared_ptr<Node> next;std::weak_ptr<Node> prev; // 弱引用,防止循环引用~Node() { std::cout << "~Node()" << std::endl; }
};int main() {auto node1 = std::make_shared<Node>();auto node2 = std::make_shared<Node>();node1->next = node2;node2->prev = node1; // 弱引用避免循环引用return 0;
}

特性

  • 不增加引用计数。
  • 用于观察 shared_ptr 管理的资源。
  • 可通过 lock() 方法获取 shared_ptr

删除器的使用

默认情况下,智能指针使用 deletedelete[] 释放资源。如果资源不是通过 new 分配的,可以通过自定义删除器指定释放方式。

使用自定义删除器

#include <iostream>
#include <memory>
#include <cstdio>void fileCloser(FILE* file) {std::cout << "Closing file." << std::endl;fclose(file);
}int main() {std::shared_ptr<FILE> file(fopen("example.txt", "w"), fileCloser);if (file) {std::cout << "File opened successfully." << std::endl;}return 0;
}

make_shared 与资源初始化

使用 make_shared 可以直接构造 shared_ptr 对象,性能更高,异常安全性更强。

#include <iostream>
#include <memory>struct Date {int _year, _month, _day;Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}~Date() { std::cout << "~Date()" << std::endl; }
};int main() {auto sp = std::make_shared<Date>(2024, 11, 16);std::cout << "Year: " << sp->_year << std::endl;return 0;
}

优势

  • 避免两次内存分配。
  • 构造时更安全。

智能指针的注意事项

  1. 避免重复释放资源
    • 智能指针不能管理非 new 分配的资源。
  2. 避免循环引用
    • 使用 std::weak_ptr 打破循环引用。
  3. 显式构造
    • 智能指针构造函数是 explicit 的,防止隐式类型转换。
  4. 移动语义的使用
    • 移动智能指针后,原指针会悬空,使用时需谨慎。

总结:

类型RAII拷贝支持移动支持适用场景
std::auto_ptr已废弃,不建议使用
std::unique_ptr独占资源管理
std::shared_ptr共享资源管理
std::weak_ptr配合 shared_ptr 防止循环引用

智能指针原理

智能指针是 C++ 提供的一种封装原生指针的类,其核心原理是通过 RAII(资源获取即初始化)设计模式,将资源的管理与智能指针对象的生命周期绑定,从而实现资源的自动管理和释放,避免资源泄漏、悬空指针等问题。


RAII 思想

RAII 是智能指针的核心设计思想,资源的获取和释放分别绑定到智能指针对象的构造函数和析构函数中:

  • 构造函数:获取资源(如内存、文件句柄等)。
  • 析构函数:释放资源。

RAII 确保了在异常抛出或正常退出作用域时,智能指针的析构函数能够被自动调用,从而释放资源,避免资源泄漏。


封装原生指针

智能指针通过内部成员变量封装原生指针(如 T* _ptr),并通过重载运算符(*->)实现类似原生指针的操作行为。例如:

  • operator*:访问资源本体。
  • operator->:访问资源的成员。
template <class T>
class SmartPtr {
private:T* _ptr; // 封装原生指针
public:SmartPtr(T* ptr) : _ptr(ptr) {}  // 资源获取~SmartPtr() { delete _ptr; }     // 资源释放T& operator*() { return *_ptr; } // 解引用T* operator->() { return _ptr; } // 指针访问
};

资源管理方式

不同类型的智能指针采用不同的资源管理方式:

独占式资源管理:unique_ptr

  • 每个资源只能由一个 unique_ptr 对象管理。
  • 禁止拷贝,但允许通过移动语义转移管理权。
  • 原理:禁止拷贝构造和拷贝赋值,仅实现移动构造和移动赋值。

共享式资源管理:shared_ptr

  • 多个 shared_ptr 对象可以共享同一资源。
  • 使用引用计数来管理资源的生命周期:
    • 构造:引用计数初始化为 1。
    • 拷贝:引用计数增加。
    • 销毁:引用计数减少,减为 0 时释放资源。
class SharedPtr {
private:T* _ptr;         // 封装原生指针int* _ref_count; // 引用计数
public:SharedPtr(T* ptr) : _ptr(ptr), _ref_count(new int(1)) {}SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {++(*_ref_count); // 引用计数增加}~SharedPtr() {if (--(*_ref_count) == 0) {  // 引用计数为 0 时释放资源delete _ptr;delete _ref_count;}}
};

弱引用:weak_ptr

  • weak_ptr 不直接管理资源,而是观察由 shared_ptr 管理的资源。
  • 不增加引用计数,避免循环引用问题。

析构器的自动调用

智能指针的一个核心特点是:在智能指针对象的生命周期结束时,其析构函数会被自动调用,确保资源的正确释放。这种自动调用依赖于以下机制:

  • 栈上对象:离开作用域时,栈上对象会自动析构。
  • 异常处理:在异常抛出时,堆栈会展开,栈上的对象会按逆序自动析构。

引用计数的实现(shared_ptr

动态分配引用计数

  • shared_ptr 的引用计数需要动态分配:
    • 每次构造一个新的 shared_ptr,分配一份资源和一个引用计数。
    • 多个 shared_ptr 共享一个引用计数。

引用计数的增减

  • shared_ptr 被拷贝时,引用计数增加。
  • shared_ptr 被销毁时,引用计数减少。
  • 当引用计数减为 0 时,释放资源。

循环引用问题

当两个或多个 shared_ptr 互相引用时,会导致引用计数永不为 0,从而资源无法释放。这时需要 weak_ptr 来打破循环。


自定义删除器

智能指针允许通过删除器(deleter)定制资源释放方式。例如:

  • 默认:deletedelete[]
  • 自定义:文件关闭(fclose)、内存池释放等。
std::shared_ptr<FILE> file(fopen("test.txt", "r"), [](FILE* f) {fclose(f);
});

智能指针的关键实现点

  1. RAII 原则:资源绑定到对象生命周期,析构时自动释放。
  2. 引用计数shared_ptr):动态分配引用计数,管理资源生命周期。
  3. 操作符重载:提供与原生指针类似的访问接口。
  4. 自定义删除器:支持用户定制资源释放方式。
  5. 循环引用处理:通过 weak_ptr 解决 shared_ptr 的循环引用问题。

智能指针本质是通过封装原生指针和引用计数,实现安全、高效的资源管理,同时提升了代码的可维护性和异常安全性。

C++11中三种智能指针的模拟实现及原理理解

C++98 auto_ptr

template<class T>
class auto_ptr
{
public:auto_ptr(T* ptr):_ptr(ptr){}auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为⾃⼰给⾃⼰赋值if (_ptr !=ap._ptr){// 释放当前对象中资源if (_ptr){delete _ptr;}// 直接将资源转移,原来的智能指针处于悬浮状态,在使用时可能会出错// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

C++11unique_ptr

template<class T>
class unique_ptr
{
public:explicit unique_ptr(T* ptr) // explicit : 不能通过隐式转换调用:_ptr(ptr){}~unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤T& operator*(){return *_ptr;}T* operator->(){return _ptr;}unique_ptr(const unique_ptr<T>&sp) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;// 如果一定要移动资源,那么通过move()即可强制转移unique_ptr(unique_ptr<T> && sp):_ptr(sp._ptr){sp._ptr = nullptr;}unique_ptr<T>& operator=(unique_ptr<T> && sp){delete _ptr;_ptr = sp._ptr;sp._ptr = nullptr;}private:T* _ptr;
};

C++11shared_ptr

template<class T>
class shared_ptr
{
public:explicit shared_ptr(T* ptr = nullptr) // 不能隐式类型转换: _ptr(ptr), _pcount(new int(1)) // 构造函数,初始化引用计数为1{}template<class D>shared_ptr(T* ptr, D del) // 如果提供_del,则该构造函数: _ptr(ptr), _pcount(new int(1)), _del(del){}// 将当前指向资源所有权传递给新的智能指针,当前智能指针保留原所有权 增加引用计数shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){++(*_pcount);}//void release()//{//	if (--(*_pcount) == 0)//	{//		// 最后⼀个管理的对象,释放资源//		_del(_ptr);//		delete _pcount;//		_ptr = nullptr;//		_pcount = nullptr;//	}//}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){// 如果该智能指针之前拥有的资源已经是该资源最后一个引用计数了,那么就释放资源// 如果不是,就对引用计数 -1 ,然后改变当前智能指针指向的资源,指向新资源if (--(*_pcount) == 0){// 最后⼀个管理的对象,释放资源_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);_del = sp._del;}return *this;}~shared_ptr(){if (--(*_pcount) == 0){// 最后⼀个管理的对象,释放资源_del(_ptr);_ptr = nullptr;delete _pcount;_pcount = nullptr;}}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;int* _pcount;//atomic<int>* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };
};

C++11weak_ptr

template<class T>
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;
};

shared_ptr 循环引用引出 weak_ptr 的作用

在使用 shared_ptr 管理资源时,特殊情况下,如果多个 shared_ptr 通过成员变量相互引用,就会形成循环引用,导致引用计数永不为 0,资源无法释放,进而引发内存泄漏问题。weak_ptr 通过不增加引用计数的方式,帮助打破这种循环引用。


循环引用的形成

以下是循环引用问题的一个典型场景:

示例代码:
#include <iostream>
#include <memory>
using namespace std;struct ListNode {int _data;std::shared_ptr<ListNode> _next; // 下一个节点std::shared_ptr<ListNode> _prev; // 上一个节点// 这里改成weak_ptr,当n1->_next = n2;绑定shared_ptr时// 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引用了/*std::weak_ptr<ListNode> _next;std::weak_ptr<ListNode> _prev;*/~ListNode() {cout << "~ListNode()" << endl;}
};int main() {// 创建两个共享指针std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);cout << "n1 use_count: " << n1.use_count() << endl; // 输出 1cout << "n2 use_count: " << n2.use_count() << endl; // 输出 1// 相互引用n1->_next = n2;n2->_prev = n1;cout << "n1 use_count after linking: " << n1.use_count() << endl; // 输出 2cout << "n2 use_count after linking: " << n2.use_count() << endl; // 输出 2// 离开作用域,n1 和 n2 的析构函数不会被调用,导致内存泄漏return 0;
}
问题分析:
  1. n1_next 成员指向 n2,因此 n2 的引用计数增加。
  2. n2_prev 成员指向 n1,因此 n1 的引用计数增加。
  3. 形成循环引用时,n1n2 的引用计数都为 2。
  4. 即使 n1n2 离开作用域,它们的引用计数永远不会为 0,因此资源无法释放。

再次理解循环引用:

  1. 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。
  2. _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。
  3. 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释
    放了。
  4. _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。 至此逻辑上成功形成回旋镖似的循环引用,谁都不会释放就形成了循环引用,导致内存泄漏把ListNode结构体中的_next和_prev改成weak_ptr,weak_ptr绑定到shared_ptr时不会增加它的引用计数,_next和_prev不参与资源释放管理逻辑,就成功打破了循环引用,解决了这里的问题。

循环引用的理解

ListNode中的 std::shared_ptr<ListNode> _next; std::shared_ptr<ListNode> _prev;互相引用时:

// 相互引用
n1->_next = n2;
n2->_prev = n1;

调用成员函数:

shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del)
{++(*_pcount);
}

由此导致离开作用域时,在n1 和 n2 的析构函数中因为引用是2而只是将_pcount进行了-1,没有进行内存释放,造成内存泄漏。

这并不是一个“循环”本身的问题,而是两个对象互相持有对方的 shared_ptr 导致引用计数形成了一个“闭环”,通过引用计数的次数阻止了资源的释放。这种“互相指向”实际上形成了一个引用计数上的死锁,使得两个对象的引用计数都无法降到零,最终导致内存泄漏。

循环引用的根本原因:shared_ptr 相互引用时,引用计数永不为 0,资源无法释放。


使用 weak_ptr 解决循环引用

template<class T>
class weak_ptr
{
public:weak_ptr(){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}private:T* _ptr = nullptr;
};
}

weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定时不增加shared_ptr的引用计数,如此便可以解决循环引用的问题。

改进后的代码:
#include <iostream>
#include <memory>
using namespace std;struct ListNode {int _data;std::weak_ptr<ListNode> _next;   // 下一个节点std::weak_ptr<ListNode> _prev;    // 使用 weak_ptr 打破循环引用~ListNode() {cout << "~ListNode()" << endl;}
};int main() {// 创建两个共享指针std::shared_ptr<ListNode> n1(new ListNode);std::shared_ptr<ListNode> n2(new ListNode);cout << "n1 use_count: " << n1.use_count() << endl; // 输出 1cout << "n2 use_count: " << n2.use_count() << endl; // 输出 1// 建立关系n1->_next = n2;n2->_prev = n1; // _prev 使用 weak_ptr,不增加引用计数cout << "n1 use_count after linking: " << n1.use_count() << endl; // 输出 1cout << "n2 use_count after linking: " << n2.use_count() << endl; // 输出 2// 离开作用域,资源正常释放return 0;
}
改进点:
  1. _prev 改为 weak_ptr,不增加 n1 的引用计数。
  2. 离开作用域时,n2_prev 并不阻止 n1 的释放,进而正常释放资源。

weak_ptr 的特性与用法

打破循环引用

weak_ptr 最常见的应用场景是配合 shared_ptr 解决循环引用问题。

  • 循环引用问题:当两个或多个 shared_ptr 互相引用时,引用计数永不为 0,导致资源无法释放。
  • weak_ptr** 的作用**:
    • 使用 weak_ptr 代替某些 shared_ptr,让引用关系不增加引用计数,从而打破循环引用。
    • 它依赖于 shared_ptr 的资源管理,弱引用不参与资源的生命周期控制。

提供非强引用(非拥有型引用)

weak_ptr 的另一个重要作用是提供一种非强引用,在某些场景下,允许观察资源而不拥有资源。

场景示例:
  1. 观察者模式
    • 一个对象(被观察者)由多个观察者引用,观察者引用被观察者但不负责管理它的生命周期。
    • 通过 weak_ptr,可以在观察者中引用被观察者,同时不影响被观察者的生命周期。
  2. 缓存机制
    • 使用 weak_ptr 引用缓存中的资源,资源被 shared_ptr 管理。
    • 如果缓存中的资源已经被释放,weak_ptr 会变为过期状态,避免无效访问。

检查资源是否过期

weak_ptr 提供了一些独特的功能,可以用来检查资源的状态

  • expired()
    • 检查 shared_ptr 是否已经释放资源。
  • use_count()
    • 获取 shared_ptr 的引用计数。
  • lock()
    • 尝试获取一个有效的 shared_ptr 来访问资源。
    • 如果资源已释放,返回的 shared_ptr 是空对象,确保访问安全。
示例代码:
#include <iostream>
#include <memory>
using namespace std;int main() {std::shared_ptr<string> sp1(new string("Hello"));std::weak_ptr<string> wp = sp1;cout << "sp1 use_count: " << sp1.use_count() << endl; // 输出 1cout << "wp expired: " << wp.expired() << endl;       // 输出 0(未过期)// 改变 sp1 的管理对象sp1 = std::make_shared<string>("World");cout << "sp1 use_count: " << sp1.use_count() << endl; // 输出 1cout << "wp expired: " << wp.expired() << endl;       // 输出 1(已过期)// 尝试通过 weak_ptr 访问资源if (auto sp2 = wp.lock()) {cout << "Accessed resource: " << *sp2 << endl;} else {cout << "Resource expired, cannot access." << endl;}return 0;
}
输出结果:
sp1 use_count: 1
wp expired: 0
sp1 use_count: 1
wp expired: 1
Resource expired, cannot access.

安全访问资源

**weak_ptr**** 不支持 RAII,**仅观察资源,不参与资源管理。

相比直接使用裸指针(如 raw pointer)观察资源,weak_ptr 提供了更安全的方式:

  • 在资源被释放时,weak_ptr 会自动检测到过期状态,避免了悬空指针问题。
  • 通过 lock() 获取 shared_ptr,在访问前确保资源有效。

总结

weak_ptr 的作用不仅仅是解决引用计数的问题,它的主要功能包括:

  1. 打破循环引用:解决 shared_ptr 循环引用导致的内存泄漏。
  2. 非强引用:提供观察资源的能力,而不影响资源的生命周期。
  3. 安全访问:在访问资源前,检查其是否仍然有效。

weak_ptr 是一种辅助工具,用于在某些需要非强引用的场景中增强程序的健壮性和资源管理的灵活性。

shared_ptr 的线程安全问题与解决方案

shared_ptr 的引用计数是共享资源管理的重要机制,当多个 shared_ptr 实例同时管理同一资源时,引用计数的增减需要是线程安全的,否则可能导致资源重复释放或资源未释放等问题。


引用计数的线程安全

问题描述:
  • shared_ptr 的引用计数(use_count)在堆上存储。
  • 多个线程同时对 shared_ptr 的引用计数进行增减操作时,如果操作不是线程安全的,可能导致以下问题:
    1. 计数错误:引用计数被破坏,导致资源未释放或重复释放。
    2. 程序崩溃:多个线程竞争访问引用计数,可能导致未定义行为。
默认的 shared_ptr
  • 标准库实现的 shared_ptr 使用了原子操作(如 std::atomic)来保证引用计数的线程安全,允许多个线程同时拷贝或销毁 shared_ptr
  • 然而,shared_ptr 仅保证其自身的引用计数是线程安全的,并不保证管理的资源是线程安全的。

被管理资源的线程安全

  • shared_ptr 管理的对象(即资源本体)并不是线程安全的。
  • 如果多个线程同时访问和修改同一资源,需要外部通过互斥锁(std::mutex)等机制实现资源的同步。

自定义 shared_ptr 引用计数的线程安全

以下代码模拟了自定义 shared_ptr 的实现,并展示如何通过原子操作(std::atomic)或互斥锁(std::mutex)解决线程安全问题。

原始问题示例(线程不安全):
#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;struct AA {int _a1 = 0;int _a2 = 0;~AA() {cout << "~AA()" << endl;}
};template <typename T>
class SharedPtr {
public:SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new int(1)) {}SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {++(*_ref_count); // 非线程安全}~SharedPtr() {if (--(*_ref_count) == 0) { // 非线程安全delete _ptr;delete _ref_count;}}T* operator->() { return _ptr; }T& operator*() { return *_ptr; }int use_count() const { return *_ref_count; }private:T* _ptr;int* _ref_count;
};

此代码中的引用计数 (_ref_count) 不是线程安全的,当多个线程同时对 SharedPtr 进行拷贝或销毁时,可能导致未定义行为。


使用 std::atomic 确保引用计数线程安全

int* _ref_count 替换为 std::atomic<int>* _ref_count

template <typename T>
class SharedPtr {
public:SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new std::atomic<int>(1)) {}SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {_ref_count->fetch_add(1); // 线程安全的 ++}~SharedPtr() {if (_ref_count->fetch_sub(1) == 1) { // 线程安全的 --delete _ptr;delete _ref_count;}}T* operator->() { return _ptr; }T& operator*() { return *_ptr; }int use_count() const { return _ref_count->load(); }private:T* _ptr;std::atomic<int>* _ref_count;
};
  • fetch_add(1)fetch_sub(1) 是原子操作,确保多线程环境下引用计数的正确性。
  • 即使多个线程同时对 SharedPtr 进行拷贝或销毁,也不会破坏引用计数。

使用互斥锁确保线程安全

在一些特殊场景下,可以使用 std::mutex 来保护引用计数:

template <typename T>
class SharedPtr {
public:SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new int(1)), _mtx(new std::mutex) {}SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count), _mtx(other._mtx) {std::lock_guard<std::mutex> lock(*_mtx);++(*_ref_count);}~SharedPtr() {std::lock_guard<std::mutex> lock(*_mtx);if (--(*_ref_count) == 0) {delete _ptr;delete _ref_count;delete _mtx;}}T* operator->() { return _ptr; }T& operator*() { return *_ptr; }int use_count() const {std::lock_guard<std::mutex> lock(*_mtx);return *_ref_count;}private:T* _ptr;int* _ref_count;std::mutex* _mtx; // 保护引用计数的互斥锁
};
  • 使用 std::mutex 确保引用计数的增减操作是线程安全的。
  • 但性能可能不如 std::atomic,因为互斥锁的开销更大。

资源访问的线程安全

即使 shared_ptr 的引用计数是线程安全的,资源本体的线程安全性需要外部保证。例如:

示例代码:
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;struct AA {int _a1 = 0;int _a2 = 0;~AA() {cout << "~AA()" << endl;}
};int main() {shared_ptr<AA> p(new AA);const size_t n = 100000;mutex mtx;auto func = [&]() {for (size_t i = 0; i < n; ++i) {// 拷贝 shared_ptr,线程安全shared_ptr<AA> copy(p);// 使用锁保护资源访问{unique_lock<mutex> lk(mtx);copy->_a1++;copy->_a2++;}}};thread t1(func);thread t2(func);t1.join();t2.join();cout << p->_a1 << endl;cout << p->_a2 << endl;cout << p.use_count() << endl;return 0;
}
输出结果:
200000
200000
1
分析:
  • 使用 std::mutex 确保资源的访问是线程安全的。
  • shared_ptr 的引用计数(拷贝和销毁)由标准库自动保证线程安全。

总结

  1. 引用计数的线程安全
    • 标准库的 shared_ptr 使用 std::atomic 保证引用计数的线程安全。
    • 自定义实现时可以使用 std::atomicstd::mutex
  2. 资源的线程安全
    • shared_ptr 不保证管理的资源本体是线程安全的,外部需要通过同步机制(如 std::mutex)保护资源的访问。
  3. 推荐方案
    • 在大多数情况下,直接使用标准库的 std::shared_ptr 即可,它已经处理了引用计数的线程安全问题。
    • 如果需要访问共享资源,外部需要提供额外的同步机制来确保资源的线程安全。

内存泄漏

内存泄漏指的是程序在运行过程中申请了内存资源,但由于程序设计疏忽或异常导致这些资源未被释放,从而使这部分内存资源无法再被使用或回收。

  • 本质
    • 内存泄漏并不意味着物理内存消失,而是程序分配了一块内存,却因为失去了指针或引用的控制权,无法再访问和释放这块内存。
  • 常见原因
    1. 忘记释放:动态分配的内存没有在适当时机调用 deletefree 释放。
    2. 异常中断:程序发生异常时,未能正确执行释放逻辑。
    3. 循环引用:使用智能指针(如 shared_ptr)时,两个或多个对象间形成循环引用,导致资源无法释放。
    4. 悬空指针:内存被释放后,仍然保留指针指向这块内存,之后又尝试使用它。

内存泄漏的危害

  • 短期程序
    • 对于短时间运行的程序(如命令行工具等),内存泄漏的危害较小。
    • 程序结束时,进程的页表会被操作系统清理,内存也随之释放。
    • 示例
int main() {char* ptr = new char[1024 * 1024 * 1024]; // 申请 1GB 内存cout << (void*)ptr << endl;return 0; // 程序结束后操作系统回收内存
}
  • 长期运行的程序
    • 对于长期运行的程序(如后台服务、操作系统、客户端软件等),内存泄漏会严重影响程序的稳定性和性能。
    • 危害
      1. 内存耗尽:内存不断泄漏会导致可用内存减少,系统或程序最终因无法分配内存而崩溃。
      2. 性能下降:内存泄漏会导致内存碎片化,降低程序和系统的内存分配效率,响应速度变慢。
      3. 资源浪费:泄漏的内存无法回收,可能影响其他进程正常运行。

如何检测内存泄漏

Linux 下内存泄漏检测工具

  • Valgrind
    • 功能强大,能检测内存泄漏、非法内存访问等。
    • 使用示例:
valgrind --leak-check=full ./your_program
  • AddressSanitizer (ASan)
    • GCC 和 Clang 提供的内存检测工具。
    • 编译时添加标志:
g++ -fsanitize=address -g your_program.cpp -o your_program
./your_program

Windows 下内存泄漏检测工具

  • Visual Leak Detector (VLD)
    • 一个简单易用的内存泄漏检测工具。
    • 在 Visual Studio 中集成后,编译程序时会自动检测内存泄漏。

跨平台工具

  • Valgrind:支持 Linux 和部分 Unix 系统。
  • Dr. Memory:支持 Windows 和 Linux。

如何避免内存泄漏

良好的编码规范

  • 在代码中动态分配内存时,明确对应的释放逻辑。
    • 如:newdelete 配对,mallocfree 配对。
  • 避免出现多重分配、重复释放或忘记释放的问题。

使用智能指针

  • 使用智能指针(如 std::unique_ptrstd::shared_ptr)自动管理内存资源。
  • 智能指针利用 RAII 原则,绑定资源的生命周期到智能指针的作用域,程序离开作用域时自动释放资源。
  • 避免使用 raw pointer(裸指针)。

采用 RAII 思想

  • RAII(资源获取即初始化)
    • 将资源的获取和释放绑定到对象生命周期中。
    • 例如,将动态分配的资源封装到一个类中,在类的析构函数中释放资源。
    • 示例
class ResourceGuard 
{char* _data;
public:ResourceGuard(size_t size) : _data(new char[size]) {}~ResourceGuard() { delete[] _data; } // 确保资源释放
};
定期检查内存泄漏
  • 开发过程中,使用内存检测工具进行测试。
  • 在上线前通过专业的工具进行内存检查和性能测试。
防止循环引用
  • 在使用 std::shared_ptr 时,避免循环引用。
  • 将循环引用中的一个 shared_ptr 替换为 std::weak_ptr,打破循环。

示例:常见内存泄漏场景及解决

场景 1:忘记释放内存
void leak() {char* data = new char[1024];// 未调用 delete,导致内存泄漏
}

解决方案

  • 使用智能指针自动管理内存:
void no_leak() {std::unique_ptr<char[]> data(new char[1024]);// 离开作用域时,智能指针会自动释放内存
}

场景 2:循环引用导致内存泄漏
#include <memory>
struct Node {std::shared_ptr<Node> next;~Node() { std::cout << "Node destroyed" << std::endl; }
};void cyclic_leak() {auto n1 = std::make_shared<Node>();auto n2 = std::make_shared<Node>();n1->next = n2;n2->next = n1; // 循环引用
}

解决方案

  • 使用 std::weak_ptr 打破循环:
struct Node {std::shared_ptr<Node> next;std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用~Node() { std::cout << "Node destroyed" << std::endl; }
};void no_cyclic_leak() {auto n1 = std::make_shared<Node>();auto n2 = std::make_shared<Node>();n1->next = n2;n2->prev = n1; // 打破循环引用
}

总结

  1. 什么是内存泄漏
    • 程序分配内存后未能释放,导致内存资源无法回收。
  2. 内存泄漏的危害
    • 对短期程序影响较小,但对长期运行的程序可能导致系统性能下降甚至崩溃。
  3. 如何检测内存泄漏
    • 使用工具如 Valgrind、AddressSanitizer、VLD 等。
  4. 如何避免内存泄漏
    • 良好的编码习惯。
    • 使用智能指针和 RAII 管理资源。
    • 使用工具进行定期检测。
    • 通过 weak_ptr 避免循环引用。

通过规范编码和正确使用工具,可以有效预防和解决内存泄漏问题,保障程序的稳定性和高效运行。

C++11及之前的语法与特性到此已经大概讲解完成,内容可能会跳跃或者不全面,如果有问题可以私信笔者或者在评论区留言。
总之,在撰写代码的时候一定要注意语法的规范以及优美的代码风格,代码风格不仅利于自己调试和理解,也会使他人阅读更加便捷。如果有需要可以阅读《高质量C/C++编程》一书。
感谢阅读我的博客!!!

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Android数据存储

前言 在前面&#xff0c;我们已经学了控件和布局&#xff0c;那么我们在存储数据的时候&#xff0c;并不能持久化的存储&#xff0c;所以我们需要来学习一些如何持久化存储数据的方式. 数据存储方式 文件存储&#xff1a;在android中提供了openFileInput()方法和openFileOut…

Java基础——多线程

1. 线程 是一个程序内部的一条执行流程程序中如果只有一条执行流程&#xff0c;那这个程序就是单线程的程序 2. 多线程 指从软硬件上实现的多条执行流程的技术&#xff08;多条线程由CPU负责调度执行&#xff09; 2.1. 如何创建多条线程 Java通过java.lang.Thread类的对象…

【网络】网络层——IP协议

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解在网络层下的IP协议。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安! > 专栏选自&#xff1a;网络…

获取当前程序运行时的栈大小[C语言]

废话前言 一晃已经毕业了4年&#xff0c;也在某个时间点&#xff0c;从面试者转变成了面试官。 进行第一次面试的时候&#xff0c;我好像比候选人还慌张&#xff0c;压根不知道问什么&#xff0c;好在是同行业&#xff0c;看着简历问了一些协议内容以及模块设计思路&#xff0…

人工智能之数学基础:数学在人工智能领域中的地位

人工智能&#xff08;AI&#xff09;是一种新兴的技术&#xff0c;它的目标是构建能够像人类一样思考、学习、推理和解决问题的智能机器。AI已经成为了许多行业的重要组成部分&#xff0c;包括医疗、金融、交通、教育等。而数学则是AI领域中不可或缺的基础学科。本文将阐述数学…

UE5 第一人称射击项目学习(一)

因为工作需要&#xff0c;需要掌握ue5的操作。 选择了视频资料 UE5游戏制作教程Unreal Engine 5 C作为学习。 第一个目标是跟着视频制作出一款第一人称射击项目。 同时作为入门&#xff0c;这个项目不会涉及到C&#xff0c;而是一个纯蓝图的项目。 项目目标 这个项目将实…

图像分类之花卉识别实验验证

本实验基于37种主流的图像分类算法模型&#xff0c;对64种花卉进行识别。使用包括vgg、resnet、densenet、efficientnet、inception、mobilenet等37种图像分类模型进行实验&#xff0c;评估各种模型对花卉的识别准确度、计算量、参数量&#xff0c;对比不同模型的性能和优缺点。…

Linux基础开发工具使用

目录 1. 软件包管理器yum 1.1 概念介绍 1.2 更换镜像源&#xff08;可选&#xff09; 1.3 工具的搜索/查看/安装/卸载 1.4 优势 2. vim编辑器 2.1 vi和vim 2.2 三种常用模式和操作 2.3 配置vim 3. Linux编译器-gcc/g 4. Linux调试器-gdb 5. make和Makefile 6.…

电脑怎么自动切换IP地址

在现代网络环境中&#xff0c;电脑自动切换IP地址的需求日益增多。无论是出于网络安全、隐私保护&#xff0c;还是为了绕过地域限制&#xff0c;自动切换IP地址都成为了许多用户关注的焦点。本文将详细介绍几种实现电脑自动切换IP地址的方法&#xff0c;以满足不同用户的需求。…

PMBOK® 第六版 控制进度

目录 读后感—PMBOK第六版 目录 制定了明确的计划后&#xff0c;对计划的控制尤为重要。例如&#xff0c;经常提到的“累积效应”&#xff0c;如果某个阶段的评分仅为0.9分&#xff0c;那么五个得分为0.9分的阶段&#xff0c;最终结果可能只是一个0.5分。 特别是在当今这个时…

linux001.在Oracle VM VirtualBox中ubuntu虚拟系统扩容

1.打开终端切换到virtualBox安装目录 2.输入命令扩容 如上终端中的代码解释&#xff1a; D:\Program Files\Oracle\VirtualBox>.\VBoxManage modifyhd D:\ubuntu18.04\Ubuntu18.04\Ubuntu18.04.vdi --resize 40960如上代码说明&#xff1a;D:\Program Files\Oracle\Virtual…

Web导出Excel表格

背景&#xff1a; 1. 后端主导实现 流程&#xff1a;前端调用到导出excel接口 -> 后端返回excel文件流 -> 浏览器会识别并自动下载 场景&#xff1a;大部分场景都有后端来做 2. 前端主导实现 流程&#xff1a;前端获取要导出的数据 -> 常规数据用插件处理成一个e…

函数栈帧的创建与销毁

我是目录 环境理解栈帧函数栈帧图预备知识寄存器MOV 指令SUB 指令PUSH 指令POP 指令LEA 指令CALL 指令REP STOS 指令 一个简单的C程序栈帧创建栈帧销毁 如何传参数值参数变量参数 如何返回值数值返回变量返回 环境 集成环境&#xff1a;VS2022 x86 编辑语言&#xff1a;C 汇…

服务端高并发分布式结构进阶之路

序言 在技术求知的旅途中&#xff0c;鉴于多数读者缺乏在中大型系统实践中的亲身体验&#xff0c;难以从宏观角度把握某些概念&#xff0c;因此&#xff0c;本文特选取“电子商务应用”作为实例&#xff0c;详细阐述从百级至千万级并发场景下服务端架构的逐步演变历程。同时&am…

Linux:版本控制器git和调试工具cgdb

✨✨所属专栏&#xff1a;Linux✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 版本控制器 为了能够更⽅便我们管理这些不同版本的⽂件&#xff0c;便有了版本控制器。所谓的版本控制器&#xff0c;就是能让你了解到⼀个⽂件的历史&#xff0c;以及它的发展过程的系统。通俗的讲就是⼀…

【0x001C】HCI_Write_Page_Scan_Activity详解

目录 一、命令概述 二、命令格式和参数说明 2.1. HCI_Write_Page_Scan_Activity命令格式 2.2. Page_Scan_Interval 2.3. Page_Scan_Window 三、响应事件及参数说明 3.1. HCI_Command_Complete事件 3.2. Status 3.3. 示例 四、命令执行流程 4.1. 命令发起阶段(主机端…

云原生之运维监控实践-使用Prometheus与Grafana实现对Nginx和Nacos服务的监测

背景 如果你要为应用程序构建规范或用户故事&#xff0c;那么务必先把应用程序每个组件的监控指标考虑进来&#xff0c;千万不要等到项目结束或部署之前再做这件事情。——《Prometheus监控实战》 去年写了一篇在Docker环境下部署若依微服务ruoyi-cloud项目的文章&#xff0c;当…

突破工业管理新高度:AI多模态引擎赋能设备维护管理

结合AI技术&#xff0c;可以帮助企业提升设备维护效率和管理复杂信息的能力。以下是一个详细流程和思路&#xff1a; 1. 项目背景概述 在高端制造业领域&#xff0c;如飞机、轮船、光刻机等设备的操作手册及零件图纸涉及大量的零配件信息和操作维护流程。传统方式难以高效管理…

C++重写和重定义和重载

重写 概念&#xff1a; 重写发生在类的继承体系中&#xff0c;是指在派生类中重新定义基类中已声明为虚函数&#xff08;使用 virtual 关键字修饰&#xff09;的函数。其目的是让派生类根据自身的需求对基类的虚函数提供不同的具体实现&#xff0c;从而实现运行时多态。 规则及…

centos7在使用yum源安装依赖时报错

1.在centos7中使用yum命令时候报错如下类似信息&#xff1a; Loading mirror speeds from cached hostfile Could not retrieve mirrorlist http://mirrorlist.centos.org/?release7&archx86_64&repoos&infrastock error was 14: curl#6 - "Could not resol…