【C++入门到精通】C++入门 —— 内存管理(new函数的讲解)下

简介: 【C++入门到精通】C++入门 —— 内存管理(new函数的讲解)下

三、C++中动态内存管理


       C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过 new 和 delete 操作符进行动态内存管理。new 操作符在C++中用于在堆上动态分配内存,可以用于分配单个对象、对象数组以及动态创建对象。它调用对象的构造函数进行初始化,并需要使用 delete 操作符手动释放分配的内存。


new 操作符的一般语法为:


ptr = new T;
delete ptr; // 释放已分配的内存

其中,T 是要分配的对象的类型,ptr 是一个指针变量,用于存储分配对象的地址。


int* ptr = new int;  // 分配一个int类型的内存
*ptr = 10;  // 向分配的内存存储数据
// 动态申请一个int类型的空间并初始化为10
int* ptr1 = new int(10);
delete ptr; // 释放已分配的内存
delete ptr1; // 释放已分配的内存

在上述示例中,new int 用于分配一个 int 类型的内存,并返回一个指向该内存的指针,然后将值 10 存储到该内存中。


如果需要分配一个数组,可以使用new[]操作符:


int size = 5;
int* arr = new int[size]; // 分配一个包含5个int元素的数组内存
for (int i = 0; i < size; i++) {
    arr[i] = i;
}
delete[] arr; // 释放已分配的数组内存

       在上面的代码中,new int[size] 用于分配包含5个 int 元素的数组,并返回指向该数组首元素的指针。然后,通过循环将每个元素赋值为其索引。


需要注意以下几点:


  • 使用new分配的内存位于堆上,直到使用delete手动释放或对象超出作用域时才会被释放。
  • 当使用new操作符分配内存时,会调用对象的构造函数来初始化该对象。
  • 分配的内存应使用相应的delete或delete[]操作符进行释放,以避免内存泄漏。
  • new操作符分配内存失败时,会抛出std::bad_alloc异常,因此需要进行错误处理。

除了上面提到的使用new来分配内存,还可以使用new操作符来动态创建对象并初始化,例如:


class MyClass {
public:
    MyClass(int value) : m_value(value) {
        std::cout << "Constructor called." << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called." << std::endl;
    }
    void PrintValue() {
        std::cout << "Value: " << m_value << std::endl;
    }
private:
    int m_value;
};
int main() {
    MyClass* obj = new MyClass(42);  // 动态创建MyClass对象,并通过构造函数进行初始化
    obj->PrintValue();
    delete obj;  // 手动释放内存,调用析构函数
    return 0;
}


       在上面的代码中,通过 new 动态创建一个 MyClass 对象,并调用构造函数进行初始化。通过指针访问对象的成员函数,并最后使用 delete 释放所分配的内存。在申请自定义类型的空间时,new 会调用构造函数,delete 会调用析构函数,而 malloc 与 free 不会


       在使用 new / delete 或 new[] / delete[] 进行动态内存管理时,应该准确匹配每个 new 操作符和相应的 delete 操作符,或者每个 new[] 操作符和相应的 delete[] 操作符。不正确的匹配可能会导致内存泄漏或未定义行为。


四、operator new与operator delete函数


       new 和 delete 是用户进行动态内存申请和释放的操作符,operator new 和 operator delete 是系统提供的全局函数,new 在底层调用 operator new 全局函数来申请空间,delete 在底层通过 operator delete 全局函数来释放空间。


⭕operator new


       该函数实际通过 malloc 来申请空间,当 malloc 申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。


void* operator new(size_t size) {
    // 调用底层的内存分配函数,例如 malloc
    void* ptr = malloc(size);
    if (ptr == nullptr) {
        // 内存分配失败,抛出异常
        throw std::bad_alloc();
    }
    return ptr;
}
void operator delete(void* ptr) noexcept {
    // 调用底层的内存释放函数,例如 free
    free(ptr);
}
int main() {
    int* ptr = new int;
    *ptr = 10;
    delete ptr;
    return 0;
}


       在上面的代码中,operator new 函数调用了底层的  malloc 函数来分配内存,并在分配失败时抛出 std::bad_alloc 异常。 operator delete 函数调用了底层的   free 函数来释放内存。上面的这些只是概念性的代码,实际的底层实现可能更加复杂,并可能涉及到处理内存对齐、内存池、内存追踪等操作。具体的底层代码会因编译器、操作系统和编译选项的不同而有所差异。


       总的来说,operator new 实际也是通过 malloc 来申请空间,如果 malloc 申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。


⭕operator delete


该函数最终是通过free来释放空间的,operator delete 函数的底层逻辑:


  • operator delete函数负责释放通过operator new分配的内存。它会调用底层的内存释放函数,如free。free函数是操作系统提供的用于释放内存的函数。
  • operator delete函数将传入的内存指针作为参数,并将其传递给底层的free函数来释放内存。
  • operator delete函数不会关心所释放的内存大小。因此,释放内存时必须确保传入的内存指针是通过对应的operator new分配的,并且内存大小与分配时的大小匹配。
  • C++标准库还提供了operator delete[]函数,用于释放通过operator new[]分配的数组内存。
void operator delete(void *pUserData)
{
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK); /* block other threads */
    __TRY
        /* get a pointer to memory block header */
        pHead = pHdr(pUserData);
        /* verify block type */
        _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
        _free_dbg( pUserData, pHead->nBlockUse );
    __FINALLY
        _munlock(_HEAP_LOCK); /* release other threads */
    __END_TRY_FINALLY
    return;
}
/*
free的实现
*/
#define     free(p)     _free_dbg(p, _NORMAL_BLOCK)


以下是一个使用重载 operator delete的示例:


void operator delete(void* ptr) noexcept {
    // 自定义内存释放逻辑,例如使用其他的内存释放函数
    myMemoryFreeFunction(ptr);
}
int* ptr = new int;
delete ptr;  // 使用自定义的operator delete进行内存释放

       通过重载operator delete,可以使用自定义的内存释放逻辑来代替默认的内存释放策略。这对于使用自定义的内存分配方案或特定的内存管理需求非常有用。


需要注意的问题有以下几点:


  • operator new和 operator delete函数是全局函数,可以在全局范围内进行重载。
  • 可以根据需要重载不同的版本,在重载时可以提供额外的参数来进行定制化的内存分配或释放操作。
  • 在重载过程中,要遵循相应的内存对齐规则和语义,以确保内存分配和释放的正确性。
  • 使用自定义的operator new和operator delete函数时要注意内存的一致性和正确释放,以避免内存泄漏或未定义行为。


五、new和delete的实现原理


1.内置类型


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


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来释放空间


六、定位new表达式(placement-new)


       定位new表达式(Placement new)是C++中的一种特殊形式的new表达式,用于在已经分配的内存区域上构造对象。它可以在指定的内存地址上调用对象的构造函数,而不是分配新的内存来创建对象。


定位new表达式的语法如下:


new (ptr) Type(arguments);

       其中,ptr 是一个指向已分配内存的指针, Type 是要构造的对象类型, arguments 是传递给对象构造函数的参数。


       使用定位new表达式时,会在给定的内存地址上调用对象的构造函数,将对象在该地址上构造出来。这意味着对象的内存由用户预先分配,而不是由new操作符动态分配。由此,定位new表达式允许在指定的内存位置创建对象,适用于特定的内存分配需求。以下是一个使用定位new表达式的示例:


#include <iostream>
class MyClass {
public:
    MyClass(int value) : m_value(value) {
        std::cout << "Constructor called" << std::endl;
    }
    ~MyClass() {
        std::cout << "Destructor called" << std::endl;
    }
    void printValue() {
        std::cout << "Value: " << m_value << std::endl;
    }
private:
    int m_value;
};
int main() {
    // 分配内存
    void* memory = operator new(sizeof(MyClass));
    // 在已分配的内存上构造对象
    MyClass* obj = new (memory) MyClass(42);
    // 调用对象的成员函数
    obj->printValue();
    // 调用对象的析构函数
    obj->~MyClass();
    // 释放内存
    operator delete(memory);
    return 0;
}


       在上述示例中,我们首先使用 operator new 手动分配了一块内存,然后通过定位new表达式在这个内存地址上构造了一个MyClass对象。然后,我们可以通过对象指针调用成员函数和析构函数。最后,使用 operator delete 释放了这块内存。


       注意:定位new表达式要求用户自行管理内存的分配和释放,确保在构造和析构期间正确处理生命周期。同时,使用定位new表达式时必须保证分配的内存空间足够容纳对象,并且类型对齐正确。


       定位new表达式是C++中的一种特殊形式的new表达式,用于在已经分配的内存区域上构造对象。它使用分配的内存地址来调用对象的构造函数,允许在指定的内存位置创建对象,适用于特定的内存分配需求。使用定位new表达式时,要确保正确管理内存的分配和释放,并确保类型对齐正确。


总结


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在释放空间前会调用析构函数完成空间中资源的清理。


什么是内存泄漏,内存泄漏的危害


       什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

       内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等出现内存泄漏会导致响应越来越慢,最终卡死。


温馨提示


       感谢您对博主文章的关注与支持!在阅读本篇文章的同时,我们想提醒您留下您宝贵的意见和反馈。如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于C++以及编程技术问题的深入解析、应用案例和趣味玩法等。请继续关注博主的更新,不要错过任何精彩内容!


       再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!

f3da82c02a7b7a3dc9154983d517186b_78ec7b4c989b4d4d9cf4ccd04b3fc0e0.jpeg


目录
相关文章
|
7月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
254 26
|
7月前
|
安全 C语言
C语言中的字符、字符串及内存操作函数详细讲解
通过这些函数的正确使用,可以有效管理字符串和内存操作,它们是C语言编程中不可或缺的工具。
375 15
|
存储 程序员 编译器
玩转C++内存管理:从新手到高手的必备指南
C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
|
8月前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
157 1
|
8月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
220 0
|
11月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
11月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
666 6
|
12月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
517 0
|
4月前
|
编解码 Java 开发者
Java String类的关键方法总结
以上总结了Java `String` 类最常见和重要功能性方法。每种操作都对应着日常编程任务,并且理解每种操作如何影响及处理 `Strings` 对于任何使用 Java 的开发者来说都至关重要。
355 5
|
8月前
|
存储 编译器 C语言
关于string的‘\0‘与string,vector构造特点,反迭代器与迭代器类等的讨论
你真的了解string的'\0'么?你知道创建一个string a("abcddddddddddddddddddddddddd", 16);这样的string对象要创建多少个对象么?你知道string与vector进行扩容时进行了怎么的操作么?你知道怎么求Vector 最大 最小值 索引 位置么?
210 0