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

深入理解C++的new和delete

文章目录

    • 一、new 操作符介绍
    • 二、new的多种操作方式
    • 三、new和malloc的区别
    • 四、深入理解new[]的delete[]的原理

一、new 操作符介绍

在 C++ 中,new 和 delete 是用于动态内存管理的操作符。它们允许程序在运行时分配和释放内存,这对需要灵活控制内存生命周期的场景非常重要。new 用于在堆(heap)上动态分配内存,并调用构造函数(如果是对象)。
语法:

类型* 指针 = new 类型;
类型* 指针 = new 类型(初始化值);
类型* 数组指针 = new 类型[数组大小];

功能:

1.动态分配内存:new 会在堆上分配内存。
2.调用构造函数(如果适用):为对象类型调用对应的构造函数。
3.返回指针:返回分配的内存块的指针。

示例:

// 分配单个整数
int* pInt = new int(10); // 分配并初始化为10
std::cout << *pInt << std::endl;

// 分配数组
int* pArray = new int[5]; // 分配5个整数的数组
for (int i = 0; i < 5; ++i) {
    pArray[i] = i + 1;
    std::cout << pArray[i] << " ";
}
std::cout << std::endl;

二、new的多种操作方式

new 操作符有多种变体和用法,主要包括以下几种:
1. 普通 new
用于分配单个对象的内存并调用构造函数。

int* p = new int;         // 分配未初始化的整数内存
int* pInit = new int(10); // 分配并初始化为10

2.定位 new(Placement New)
允许在特定的内存地址上构造对象。

#include <iostream>
#include <new> // 包含 placement new

int main() {
    char buffer[sizeof(int)];    // 提供内存空间
    int* p = new (buffer) int(42); // 在 buffer 中构造整数
    std::cout << *p << std::endl;
    p->~int(); // 手动调用析构函数
    return 0;
}

注意:使用定位 new 时,delete 不能释放这块内存;需要手动调用析构函数并释放内存(如果需要)。

3.异常安全 new
默认情况下,new 分配失败会抛出 std::bad_alloc。使用 nothrow,可以避免抛出异常,而是返回 nullptr。

#include <iostream>
#include <new>

int main() {
    int* p = new (std::nothrow) int; // 分配失败时返回 nullptr
    if (p) {
        *p = 10;
        std::cout << *p << std::endl;
        delete p;
    } else {
        std::cout << "Allocation failed" << std::endl;
    }
    return 0;
}

4.常量new
常量 new 的用法指的是通过 new 分配常量内存,也就是动态分配的内存指向一个 const 类型的值或对象。这可以确保分配的值不会被修改,从而提高程序的安全性和可读性。

#include <iostream>

int main() {
    const int* pConst = new const int(42); // 分配一个常量整数
    std::cout << "Value: " << *pConst << std::endl;

    // *pConst = 10; // 错误!不能修改常量值

    delete pConst; // 内存仍需释放
    return 0;
}

三、new和malloc的区别

1.new被称作运算符,返回指定类型的指针(无需类型转换),且自动调用构造函数(对象);内存释放的时候使用 delete 或 delete[]。使用new开辟内存失败后抛出 std::bad_alloc 异常。而且它支持重载。
2.malloc是C的库函数,返回 void*,需要显式类型转换,不会自动初始化,需手动赋值。内存释放的时候使用另一个库函数free。如果malloc开辟内存失败,返回nullptr。它不支持重载。

new 会在分配内存的同时完成初始化:

int* p = new int(10); // 分配内存并初始化为 10

malloc 不会进行初始化,分配的内存是未定义的:

int* p = (int*)malloc(sizeof(int)); // 内存中的值是随机的

new 根据分配的类型返回特定类型的指针。例如:

int* p = new int(10); // 不需要类型转换

malloc 返回 void*,需要显式类型转换:

int* p = (int*)malloc(sizeof(int)); // 必须转换类型

new 使用 delete 释放单个对象,使用 delete[] 释放数组,并自动调用析构函数(如果是对象类型):

delete p;
delete[] pArray;

malloc 使用 free 释放内存,但不会调用析构函数

free(p);

代码示例:
使用 new 分配对象

#include <iostream>

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

int main() {
    MyClass* obj = new MyClass; // 调用构造函数
    delete obj;                 // 调用析构函数
    return 0;
}

使用 malloc 分配内存

#include <iostream>
#include <cstdlib> // 包含 malloc 和 free

struct MyStruct {
    int x;
};

int main() {
    MyStruct* obj = (MyStruct*)malloc(sizeof(MyStruct));
    obj->x = 42; // 手动初始化
    std::cout << "x: " << obj->x << std::endl;
    free(obj); // 释放内存
    return 0;
}

四、深入理解new[]的delete[]的原理

#include <iostream>

// 先调用operator new开辟内存空间,然后调用对象的构造函数(初始化)
void* operator new(size_t size) {
    void* p = malloc(size);
    if (p == nullptr) {
        throw std::bad_alloc();
    }
    std::cout << "operator new addr:" << p << std::endl;

    return p;
}

void* operator new[](size_t size) {
    void* p = malloc(size);
    if (p == nullptr) {
        throw std::bad_alloc();
    }
    std::cout << "operator new[] addr:" << p << std::endl;

    return p;
}

// delete p:调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete(void* ptr) {
    std::cout << "operator delete addr:" << ptr << std::endl;
    free(ptr);
}

void operator delete[](void* ptr) {
    std::cout << "operator delete addr[]:" << ptr << std::endl;
    free(ptr);
}

class Test {
public:
    Test(int data = 10) {
        std::cout << "Test()" << std::endl;
    }
    ~Test() {
        std::cout << "~Test()" << std::endl;
    }

private:
    int ma;
};

int main() {
    Test* p2 = new Test[5];
    std::cout << "p2:" << p2 << std::endl;
    delete[] p2;

	
	return 0;
}

输出结果:
在这里插入图片描述
从上面的输出可以看出,申请自定义数组对象的时候,会多开辟4个字节的内存,用来存储对象的个数,假设这个地址如上所示为015C0880,而返回给p2的地址则是在起始地址的基础上再移动4个字节,即015C0884,记录对象个数的原因是在释放内存的时候,要根据对象个数进行对象的析构。因为在释放内存的时候,肯定需要从开辟的起始地址释放内存,也就是从015C0880开始释放,如果使用delete p2去释放内存,那么就只会释放掉p2[0]的内存而已,从而造成内存泄漏的问题。


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

相关文章:

  • 复制粘贴小工具——Ditto
  • 如何在Arduino上使用NodeMCU
  • mybatis plus 持久化使用技巧及场景
  • Kafka 可靠性探究—副本刨析
  • 逻辑起源 - 比较DS与豆包对“逻辑”源头的提炼差异
  • Verilog基础(一):基础元素
  • linux 使用docker安装 postgres 教程,踩坑实践
  • Unity3D 切线空间及其应用详解
  • springboot011-G县乡村生活垃圾治理问题中运输地图系统
  • 网络安全配置
  • 从源码到上线:AI在线教育系统开发全流程与网校APP构建指南
  • 使用 TensorRT 和 Python 实现高性能图像推理服务器
  • LeetCode 415
  • 无人机使用数传时的注意事项!
  • 尝试在Excel里调用硅基流动上的免费大语言模型
  • FFmpeg 头文件完美翻译之 libavdevice 模块
  • gc buffer busy acquire导致的重大数据库性能故障
  • Elasticsearch集群模式保姆级教程
  • MQTT:物联网时代的数据桥梁
  • 【Redisson分布式锁】基于redisson的分布式锁
  • 在 Vue Router 中,params和query的区别?
  • 使用EVE-NG实现VLAN
  • 绿联NAS安装cpolar内网穿透工具实现无公网IP远程访问教程
  • Vue.js 中 computed 和 watch 的使用场景
  • LangGraph中的Human-in-the-loop技术(GPT-4o 回答)
  • 园区网设计与实战