当前位置: 首页 > article >正文

【c++篇】掌握动态内存的奥妙

【C++篇】动态内存

  • 一、Static 关键字
    • 1.1函数内部的静态变量
    • 1.2 全局静态变量
    • 1.3静态成员变量
    • 1.4静态成员函数
  • 二、内存管理
    • 2.1栈区(Stack)
    • 2.2堆区(Heap)
  • 三、动态内存分配机制
    • 3.1、动态内存分配的两种方法
      • c语言
      • c++
    • 3.2new 和delete的用法
    • 3.3语法和类型安全性
    • 3.4new和delete实现原理
      • 3.4.1内置类型
      • 3.4.2自定义类型
  • 四、operator new与operator delete函数
    • 4.1 operator new
    • 4.2 operator delete
    • 4.3默认实现与工作原理
    • 4.4operator new 和 operator delete 的重载
    • 4.5 malloc/free和new/delete的区别

链接: 基于上篇我们学习了构造函数、重载等,我们继续探究类与对象以及内存管理 https://blog.csdn.net/2401_83157962/article/details/143778695
欢迎进入我的博客

一、Static 关键字

定义:

  • 静态数据成员:属于类本身,而不是类的某个对象。它在整个程序中只有一份拷贝,所有对象共享。
  • 静态成员函数:不依赖具体对象调用,不能访问非静态成员。

特点:

  • 静态数据成员在程序启动时分配内存,直到程序结束时释放。
  • 静态成员函数不能使用 this 指针,也不能直接访问非静态成员变量或成员函数。

1.1函数内部的静态变量

当在函数内部声明一个变量为static时,这个变量的生命周期会延续到程序结束,但它的作用域仅限于该函数。换句话说,虽然它只能在定义它的函数内访问,但它不会在函数调用结束后被销毁。

示例如下:

#include <iostream>  

void counter() {  
    static int count = 0;  // 静态变量只会被初始化一次  
    count++;  
    std::cout << "Count: " << count << std::endl;  
}  

int main() {  
    counter(); // 输出 Count: 1  
    counter(); // 输出 Count: 2  
    counter(); // 输出 Count: 3  
    return 0;  
}

1.2 全局静态变量

如果在文件级别(全局作用域)定义一个静态变量,它只能在当前文件内访问。这对于实现模块封装非常有用,可以防止命名冲突。
示例如下:

下面展示一些 内联代码片

#include <iostream>  

static int globalVariable = 42; // 只能在此文件中使用  

void display() {  
    std::cout << "Global variable: " << globalVariable << std::endl;  
}  

int main() {  
    display();  
    return 0;  
}

1.3静态成员变量

在类中,使用static可以声明静态成员变量,它属于类而不是某个特定的对象。所有对象共享这个变量,静态成员变量在类的所有对象之间是相同的。


示例如下:

#include <iostream>  
class MyClass {  
public:  
    static int staticValue; // 声明静态成员变量  

    MyClass() {  
        staticValue++;  
    }  
};  

int MyClass::staticValue = 0; // 定义并初始化静态成员变量  

int main() {  
    MyClass obj1;  
    MyClass obj2;  
    std::cout << "Static Value: " << MyClass::staticValue << std::endl; // 输出 2  
    return 0;  
}

1.4静态成员函数

静态成员函数是属于类的,而不是类的实例。它们只能访问静态成员变量或静态成员函数,不能访问非静态的成员变量和成员函数
示例如下:

#include <iostream>  

class MyClass {  
public:  
    static int staticValue;  

    static void display() { // 静态成员函数  
        std::cout << "Static Value: " << staticValue << std::endl;  
    }  
};  

int MyClass::staticValue = 10;  

int main() {  
    MyClass::display(); // 通过类名访问静态成员函数  
    return 0;  
}

二、内存管理

2.1栈区(Stack)

栈内存是由编译器自动管理的。函数调用时,局部变量会在栈上分配内存,当函数返回后,分配的内存会自动被释放。由于栈内存的这种自动管理特性,使用栈内存的分配和释放非常高效,但栈内存的大小是有限的,通常适合存储小型对象或简单的数据结构。


特点:

  • 为局部变量和函数调用分配内存,生命周期由作用域决定。
  • 自动分配和释放,速度快,但大小有限。

示例如下:

void stackExample() {
    int a = 10; // 栈分配
} // 离开作用域后,a 自动销毁


2.2堆区(Heap)

定义:

堆内存是由程序员手动管理的。通过newdelete运算符进行动态内存分配和释放。这种方法允许在运行时进行内存分配,因此可以根据需要分配更大的内存空间,但同时也增加了内存泄漏和碎片化的风险。


特性:

  • 由程序员手动分配和释放,灵活性高,但容易出现内存泄漏。
  • 使用 new 和 delete 管理。

示例如下:

int* p = new int(10); // 堆分配
delete p;             // 手动释放

三、动态内存分配机制

动态内存分配是通过new和delete操作符完成的。new运算符用于在堆上分配内存,并返回指向分配的内存的指针;delete运算符则用于释放之前使用new分配的内存。


3.1、动态内存分配的两种方法

c语言

  • 在C语言中,动态内存分配主要通过标准库函数(malloc、calloc、realloc、free)来完成。C语言不提供内置的类型安全机制,因此程序员需要手动管理内存的分配和释放。
int* arr = (int*)malloc(10 * sizeof(int)); // 分配内存
free(arr);                                // 释放内存

c++

在C++中,动态内存分配主要通过new和delete运算符来完成。与C语言不同,C++的内存管理提供了更丰富的语法,使得代码更加类型安全,并且支持构造和析构函数的调用。


示例如下:

int* arr = new int[10]; // 分配内存
delete[] arr;           // 释放内存

3.2new 和delete的用法

  • 分配单个对象:
int* p = new int(42); // 分配并初始化
delete p;             // 释放内存
  • 分配数组
int* arr = new int[10]; // 分配数组
delete[] arr;           // 释放数组

3.3语法和类型安全性

  • C语言:使用malloc和free进行内存分配和释放。由于返回的是void*类型的指针,常常需要进行强制类型转换,缺乏类型安全保障。

  • C++:使用new和delete,内存分配后已经是目标类型,无需进行转换。此外,使用new时会调用对象的构造函数,delete会调用析构函数,这对于管理对象资源非常有用。


3.4new和delete实现原理

3.4.1内置类型

如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常, malloc会返回NULL。


3.4.2自定义类型

  • new 原理
    1. 调用operator new函数申请空间
    2. 在申请的空间上执行构造函数,完成对象的构造
  • delete原理
    1. 在空间上执行析构函数,完成对象中资源的清理工作
    2. 调用operator delete函数释放对象的空间
  • new T[N] 原理
    1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请
    2. 在申请的空间上执行N次构造函数
  • delete[ ] 原理
    1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理
    2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间

四、operator new与operator delete函数

4.1 operator new

定义:

operator new 是 C++ 中用于分配内存的运算符,用于申请指定数量的内存块。与 C 语言的 malloc 相比,operator new 提供了一种类型安全的方式来分配内存,因为它返回的是指定类型的指针,而不是 void*。

特性·:

  • 是 C++ 的内存分配函数,类似于 C 中的 malloc。
  • 被 new 关键字调用,用于在堆上分配足够的内存以容纳一个或多个对象。
  • 默认实现使用标准库的 malloc 来分配内存。
  • 如果分配失败,会抛出 std::bad_alloc 异常,而不是返回 nullptr。

语法:

void* operator new(std::size_t size);
  • size:要分配的字节数。
  • 返回值:返回一个指向已分配内存的指针,如果分配失败,将会抛出std::bad_alloc异常。

4.2 operator delete

operator delete 是与 operator new 配对使用的函数,用于释放之前分配的内存。它的基本形式如下:

void operator delete(void* ptr);
  • ptr:指向要释放的内存块的指针

特性:

  • 是 C++ 的内存释放函数,类似于 C 中的 free。
  • 被 delete 关键字调用,用于释放 operator new 分配的内存。
  • 默认实现使用标准库的 free 来释放内存。

4.3默认实现与工作原理

operator new 的作用是分配一块大小为 size 的内存(以字节为单位),并返回指向分配内存的指针。它的默认实现可以表示如下:

void* operator new(size_t size) {
    if (size == 0) size = 1;  // 确保最小分配单位
    void* ptr = malloc(size); // 调用 C 的 malloc
    if (!ptr) throw std::bad_alloc(); // 分配失败抛异常
    return ptr;
}

分配行为:

  • 如果 size 为 0,operator new 会分配一个最小的非零内存块。
  • 使用 malloc 从堆上分配内存。
  • 如果 malloc 返回 nullptr(分配失败),operator new 会抛出异常。

operator delete 的作用是释放 operator new 分配的内存块。它的默认实现如下:

void operator delete(void* ptr) noexcept {
    free(ptr); // 调用 C 的 free
}

释放行为:

  • 如果传入的 ptr 是 nullptr,operator delete 不会执行任何操作。
  • 否则,它调用 free 将内存归还给系统。

4.4operator new 和 operator delete 的重载

C++ 允许重载这些操作符,以便提供自定义内存管理策略。重载可以帮助跟踪内存分配、统计内存使用、实现自定义的内存池等。
示例如下:

#include <iostream>  
#include <cstdlib>  

void* operator new(std::size_t size) {  
    std::cout << "Allocating " << size << " bytes\n";  
    void* p = std::malloc(size);  
    if (!p) throw std::bad_alloc();  
    return p;  
}  

void operator delete(void* ptr) noexcept {  
    std::cout << "Deallocating memory\n";  
    std::free(ptr);  
}  

int main() {  
    int* p = new int(10); // Calls overloaded operator new  
    std::cout << "Value: " << *p << std::endl;  

    delete p; // Calls overloaded operator delete  
    return 0;  
}

在这个示例中,重载的 operator new 在每次内存分配时打印分配的字节数,而 operator delete 则在释放内存时打印相应的信息。


4.5 malloc/free和new/delete的区别

malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地方是:

  1. malloc和free是函数,new和delete是操作符
  2. malloc申请的空间不会初始化,new可以初始化
  3. malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可, 如果是多个 对象,[]中指定对象个数即可
  4. malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  5. malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
  6. 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间 后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理

最后

  • operator newoperator delete 是 C++ 提供的用于动态内存管理的操作符,提供了一种类型安全的方式来分配和释放内存
  • 始终确保内存分配和释放对称,避免内存泄漏和悬垂指针(指向已释放内存的指针称为悬垂指针,访问悬垂指针会导致未定义行为。)。
  • 推荐使用 智能指针,如 std::unique_ptr 和 std::shared_ptr,自动管理动态

http://www.kler.cn/a/408521.html

相关文章:

  • SATA接口不通分析案例分享
  • python如何解压缩文件或文件夹
  • 局域网与广域网:探索网络的规模与奥秘(3/10)
  • 《操作系统 - 清华大学》5 -2:覆盖技术
  • Linux网络——网络层
  • 【Unity踩坑】在Mac上安装Cocoapods失败
  • 丑数 详解
  • 修改ffmpeg实现https-flv内容加密
  • apache、iis屏蔽限制ip访问(适用虚拟主机)
  • C语言-详细讲解-洛谷P1420 最长连号
  • 字符串-07-判断两个IP是否属于同一子网
  • 微信小程序中使用iconfont的详细教程
  • Python棉花病虫害图谱系统CNN识别+AI问答知识neo4j vue+flask深度学习神经网络可视化
  • grep/egrep正则表达式
  • Linux基本指令的使用
  • 力扣 239. 滑动窗口最大值
  • 数字化工厂 MES试点方案全解析(三)
  • 行为树详解(2)——最简单的行为树
  • LeetCode题练习与总结:棋盘上的战舰--419
  • 【Python爬虫五十个小案例】爬取豆瓣电影Top250
  • ElasticSearch7.x入门教程之索引数据类型和映射(四)
  • 11.21 小清新图论专场训练
  • 华为FusionCube 500-8.2.0SPC100 实施部署文档
  • 项目实战:Vue3开发一个购物车
  • ComfyUI绘画|SD WebUI 与 SD ComfyUI 的区别
  • 【含文档】基于.NET的医院医保管理系统(含源码+数据库+lw)