前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >彻底摘明白 C++ 的动态内存分配原理

彻底摘明白 C++ 的动态内存分配原理

原创
作者头像
威哥爱编程
发布2025-02-19 15:51:29
发布2025-02-19 15:51:29
19500
代码可运行
举报
文章被收录于专栏:V哥原创技术栈V哥原创技术栈
运行总次数:0
代码可运行

大家好,我是 V 哥。在C++中,动态内存分配允许程序在运行时请求和释放内存,这对于处理不确定大小的数据或者在程序执行过程中动态调整数据结构非常有用。C++主要通过newdelete运算符(用于对象)以及malloccallocreallocfree函数(继承自C语言)来实现动态内存分配,下面详细介绍它们的原理。先赞再看后评论,腰缠万贯财进门

1. newdelete运算符

原理概述

new运算符用于在堆上分配内存并调用对象的构造函数进行初始化,delete运算符用于释放new分配的内存并调用对象的析构函数。

详细步骤
new运算符
  1. **内存分配**:new运算符首先调用operator new函数来分配所需大小的内存块。operator new是一个全局函数,它通常会调用操作系统提供的内存分配函数(如malloc)来获取内存。
  2. **构造函数调用**:如果分配内存成功,new运算符会调用对象的构造函数对分配的内存进行初始化。

示例代码:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>



class MyClass {

public:

    MyClass() {

        std::cout << "Constructor called" << std::endl;

    }

    ~MyClass() {

        std::cout << "Destructor called" << std::endl;

    }

};



int main() {

    // 使用new分配内存并调用构造函数

    MyClass\* obj = new MyClass();

    // 使用delete释放内存并调用析构函数

    delete obj;

    return 0;

}
delete运算符
  1. **析构函数调用**:delete运算符首先调用对象的析构函数,用于清理对象占用的资源(如关闭文件、释放动态分配的子对象等)。
  2. **内存释放**:调用operator delete函数释放之前分配的内存。operator delete通常会调用操作系统提供的内存释放函数(如free)。
数组的动态分配

使用new[]delete[]可以动态分配和释放数组。new[]会为数组中的每个元素调用构造函数,delete[]会为数组中的每个元素调用析构函数。

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>



class MyClass {

public:

    MyClass() {

        std::cout << "Constructor called" << std::endl;

    }

    ~MyClass() {

        std::cout << "Destructor called" << std::endl;

    }

};



int main() {

    // 使用new[]分配数组内存并调用构造函数

    MyClass\* arr = new MyClass[3];

    // 使用delete[]释放数组内存并调用析构函数

    delete[] arr;

    return 0;

}

2. malloccallocreallocfree函数

原理概述

这些函数是C语言标准库提供的动态内存分配函数,C++为了兼容C语言也支持这些函数。它们不涉及对象的构造和析构,只是简单地分配和释放内存。

详细介绍
malloc函数

malloc函数用于分配指定大小的内存块,返回一个指向该内存块的指针。如果分配失败,返回NULL

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <cstdlib>



int main() {

    // 分配10个int类型的内存空间

    int\* ptr = (int\*)malloc(10 \* sizeof(int));

    if (ptr != NULL) {

        // 使用内存

        for (int i = 0; i < 10; ++i) {

            ptr[i] = i;

        }

        // 释放内存

        free(ptr);

    }

    return 0;

}
calloc函数

calloc函数用于分配指定数量和大小的内存块,并将所有字节初始化为0。

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <cstdlib>



int main() {

    // 分配10个int类型的内存空间并初始化为0

    int\* ptr = (int\*)calloc(10, sizeof(int));

    if (ptr != NULL) {

        // 释放内存

        free(ptr);

    }

    return 0;

}
realloc函数

realloc函数用于重新分配已经分配的内存块的大小。如果新的大小比原来的大,可能会在堆上移动内存块;如果新的大小比原来的小,会截断内存块。

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <cstdlib>



int main() {

    // 分配10个int类型的内存空间

    int\* ptr = (int\*)malloc(10 \* sizeof(int));

    if (ptr != NULL) {

        // 重新分配为20个int类型的内存空间

        int\* newPtr = (int\*)realloc(ptr, 20 \* sizeof(int));

        if (newPtr != NULL) {

            ptr = newPtr;

            // 释放内存

            free(ptr);

        }

    }

    return 0;

}
free函数

free函数用于释放malloccallocrealloc分配的内存。

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <cstdlib>



int main() {

    int\* ptr = (int\*)malloc(10 \* sizeof(int));

    if (ptr != NULL) {

        // 释放内存

        free(ptr);

    }

    return 0;

}

内存管理的注意事项

  • **内存泄漏**:如果使用newmalloc分配的内存没有使用deletefree释放,会导致内存泄漏,程序运行时会逐渐耗尽可用内存。
  • **悬空指针**:释放内存后,指向该内存的指针成为悬空指针,继续使用悬空指针会导致未定义行为。
  • **不匹配使用**:new必须与delete配对使用,new[]必须与delete[]配对使用,malloccallocrealloc必须与free配对使用。

如何避免动态内存分配导致的内存泄漏?

在C++中,动态内存分配如果管理不当很容易导致内存泄漏,即程序中已分配的内存不再被使用,但却没有被释放,随着程序的运行,可用内存会逐渐减少。以下是一些避免动态内存分配导致内存泄漏的方法:

1. 遵循配对原则

在使用动态内存分配时,要确保newdeletenew[]delete[]malloc/calloc/reallocfree正确配对使用。

**示例代码**:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <cstdlib>



int main() {

    // 使用new分配单个对象

    int\* singlePtr = new int(10);

    // 使用delete释放单个对象

    delete singlePtr;



    // 使用new[]分配数组

    int\* arrayPtr = new int[5];

    // 使用delete[]释放数组

    delete[] arrayPtr;



    // 使用malloc分配内存

    char\* charPtr = (char\*)malloc(10 \* sizeof(char));

    // 使用free释放内存

    free(charPtr);



    return 0;

}
2. 使用智能指针

智能指针是C++标准库提供的一种类模板,它可以自动管理动态分配的内存,当智能指针的生命周期结束时,会自动释放所指向的内存。

2.1 std::unique\_ptr

std::unique\_ptr是一种独占式智能指针,同一时间只能有一个std::unique\_ptr指向同一个对象,当它离开作用域时,会自动释放所指向的内存。

**示例代码**:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <memory>



class MyClass {

public:

    MyClass() { std::cout << "Constructor called" << std::endl; }

    ~MyClass() { std::cout << "Destructor called" << std::endl; }

};



int main() {

    // 创建std::unique\_ptr

    std::unique\_ptr<MyClass> ptr = std::make\_unique<MyClass>();

    // 不需要手动释放内存,ptr离开作用域时会自动释放

    return 0;

}
2.2 std::shared\_ptr

std::shared\_ptr是一种共享式智能指针,多个std::shared\_ptr可以指向同一个对象,使用引用计数来管理对象的生命周期,当引用计数为0时,会自动释放所指向的内存。

**示例代码**:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <memory>



class MyClass {

public:

    MyClass() { std::cout << "Constructor called" << std::endl; }

    ~MyClass() { std::cout << "Destructor called" << std::endl; }

};



int main() {

    // 创建std::shared\_ptr

    std::shared\_ptr<MyClass> ptr1 = std::make\_shared<MyClass>();

    std::shared\_ptr<MyClass> ptr2 = ptr1;  // 引用计数加1

    // 当ptr1和ptr2都离开作用域时,引用计数变为0,自动释放内存

    return 0;

}
3. 异常安全

在使用动态内存分配时,要确保在发生异常的情况下也能正确释放内存。可以使用try-catch块来捕获异常,并在catch块中释放内存。但使用智能指针可以更方便地实现异常安全。

**示例代码(使用智能指针实现异常安全)**:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>

#include <memory>

#include <stdexcept>



class MyClass {

public:

    MyClass() { std::cout << "Constructor called" << std::endl; }

    ~MyClass() { std::cout << "Destructor called" << std::endl; }

};



void func() {

    std::unique\_ptr<MyClass> ptr = std::make\_unique<MyClass>();

    // 可能会抛出异常的代码

    if (true) {

        throw std::runtime\_error("An error occurred");

    }

    // 不需要手动释放内存,ptr离开作用域时会自动释放

}



int main() {

    try {

        func();

    } catch (const std::exception& e) {

        std::cout << "Exception caught: " << e.what() << std::endl;

    }

    return 0;

}
4. 封装动态内存管理

将动态内存管理封装在类中,通过类的构造函数分配内存,析构函数释放内存,遵循RAII(资源获取即初始化)原则。

**示例代码**:

代码语言:cpp
代码运行次数:0
运行
复制
#include <iostream>



class MemoryWrapper {

private:

    int\* data;

public:

    MemoryWrapper() {

        data = new int[10];

        std::cout << "Memory allocated" << std::endl;

    }

    ~MemoryWrapper() {

        delete[] data;

        std::cout << "Memory freed" << std::endl;

    }

};



int main() {

    MemoryWrapper wrapper;

    // 当wrapper离开作用域时,析构函数会自动释放内存

    return 0;

}
5. 代码审查和静态分析工具

定期进行代码审查,检查动态内存分配和释放的代码是否正确。同时,可以使用静态分析工具(如Clang Static Analyzer、Cppcheck等)来帮助检测潜在的内存泄漏问题。

最后

理解 C++的动态内存分配原理,以及掌握如何避免动态内存分配导致的内存泄漏?是在开发中非常关键的知识,这篇文章希望可以帮助到你,关注威哥爱编程,全栈开发就你行。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. new和delete运算符
    • 原理概述
    • 详细步骤
      • new运算符
      • delete运算符
    • 数组的动态分配
  • 2. malloc、calloc、realloc和free函数
    • 原理概述
    • 详细介绍
      • malloc函数
      • calloc函数
      • realloc函数
      • free函数
  • 内存管理的注意事项
  • 如何避免动态内存分配导致的内存泄漏?
    • 1. 遵循配对原则
    • 2. 使用智能指针
      • 2.1 std::unique\_ptr
      • 2.2 std::shared\_ptr
    • 3. 异常安全
    • 4. 封装动态内存管理
    • 5. 代码审查和静态分析工具
  • 最后
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档