IE盒子

搜索
查看: 79|回复: 1

C++ placement new/delete 运算符

[复制链接]

4

主题

9

帖子

17

积分

新手上路

Rank: 1

积分
17
发表于 2022-12-21 13:50:38 | 显示全部楼层 |阅读模式
“在C++中,内存区分为5个区,分别是堆、栈、自由存储区、全局/静态存储区、常量存储区”。
C/C++的动态内存分配是指由程序员手动进行内存分配。动态分配的内存在堆上分配,非静态和局部变量在堆栈上分配内存。
动态分配内存的一种用途是分配可变大小的内存,这对于编译器分配的内存是不可能的,可变长度数组除外。
最重要的用途是为程序员提供的灵活性。 我们可以在需要和不再需要时自由分配和释放内存。 在许多情况下,这种灵活性会有所帮助。 这种情况的例子有链表、树等。
如果你对内存分布还不了解可以阅读:
new/delete 运算符

用于动态内存的 new 和 delete 运算符
它与分配给普遍变量的内存有何不同


  • 对于“int a”、“char str[10]”等普通变量,内存会自动分配和释放。
  • 对于动态分配的内存,如“int *p = new int[10]”,程序员有责任在不再需要时释放内存。
  • 如果程序员不释放内存,就会导致内存泄漏(直到程序终止才会释放内存)。
operator new 运算符

new 运算符表示在 自由存储区(Free Store )上分配内存的请求。 如果有足够的可用内存,则 new 运算符会初始化内存并将新分配和初始化的内存的地址返回给指针变量。
pointer-variable = new data-type
这里,指针变量是数据类型的指针。 数据类型可以是任何内置数据类型,包括数组或任何用户定义的数据类型,包括结构和类。
初始化内存

我们还可以使用 new 运算符为内置数据类型初始化内存。 对于自定义数据类型,需要一个构造函数(将数据类型作为输入)来初始化值。
pointer-variable = new data-type(value);
分配一块内存

new 运算符还用于分配数据类型的内存块(数组)。
pointer-variable = new data-type[size];
其中 size(a variable) 指定数组中元素的数量。
示例:
int *p = new int[10]
普通数组声明与使用 new

声明普通数组和使用 new 分配内存块是有区别的。 最重要的区别是,普通数组由编译器释放(如果数组是本地的,则在函数返回或完成时释放)。 然而,动态分配的数组总是保留在那里,直到它们被程序员释放或程序终止。
如果在运行时没有足够的内存可用怎么办?

如果堆中没有足够的内存可供分配,则新请求通过抛出 std::bad_alloc 类型的异常来指示失败,除非“nothrow”与 new 运算符一起使用,在这种情况下它返回 NULL 指针(滚动到 本文“new 操作符的异常处理”一节)。 因此,在使用程序之前检查 new 生成的指针变量可能是个好主意。
operator delete 运算符

由于释放动态分配的内存是程序员的责任,因此在 C++ 语言中为程序员提供了 delete 运算符。
delete pointer-variable;
要释放指针变量指向的动态分配的数组,请使用以下形式的删除:
delete[] pointer-variable;
malloc() 和 new


  • 调用构造函数:new 调用构造函数,而malloc() 不调用。
  • operator vs function:new是一个operator,而 malloc() 是一个function。
  • 返回类型:new 返回准确的数据类型,而 malloc() 返回 void*。
  • 失败条件:失败时,malloc() 返回 NULL,而 new 抛出 bad_alloc 异常。
  • 内存:在 new 的情况下,内存是从空闲存储区分配的,而在 malloc() 中,内存分配是从堆中完成的。
  • 大小:所需的内存大小由编译器为 new 计算,而我们必须手动计 malloc() 的大小。
  • 缓冲区大小:malloc() 允许使用 realloc() 更改2缓冲区的大小,而 new 则不允许。
delete 和 free()

在C++中,delete 运算符只能用于指向使用 new 运算符分配的内存的指针或NULL指针,而free() 只能用于指向使用 malloc() 分配的内存的指针或对于NULL指针。
区别

deletefree
它是一个操作符它是一个库函数
它动态地取消分配内存它会在运行时破坏内存
它应该只用于指向使用 new 运算符分配的内存的指针或NULL指针它只能用于指向使用 malloc() 分配的内存的指针或NULL指针
该运算符在销毁分配的内存后调用析构函数此函数仅从堆中释放内存。它不调用析构函数
它更快它比 delete 慢,因为它是一个函数
注意:为什么 free() 不应该用于取消分配使用new分配的内存的最重要原因是,它不会调用该对象的析构函数,而 delete 运算符会调用。
placement new/delete 运算符

placement new 是C++中的一种变体 new 运算符。
new vs placement new


  • 普通的 new 运算符做两件事:(1)分配内存 (2)在分配的内存中构造一个对象
  • placement new 允许我们将以上两件事分开。在placement new 中,我们可以传递一个预分配的内存并在传递的内存中构造一个对象。
  • 普通的 new 在堆中分配内存并在那里构造对象,而使用placement new,对象构造可以在已知地址完成。
  • 对应普通的 new,不知道它指向什么地址或内存位置,而在使用 placement new 时它指向的地址或内存位置是已知的。
  • 当分配由 new 完成但没有 placement delete 时,使用删除操作完成释放,但如果需要,可以在析构函数的帮助下完成编写。
new (address) (type) initializer
示例:



左侧为源代码;右侧为输出结果

使用 placement new 的时机

因为它允许在已经分配的内存上构造一个对象,所以需要进行优化,因为不总是重新分配会更快。可能存在需要多次重新构造对象的情况,因此在这些情况下 placement new 运算符可能更有效。
示例:
const char* charString = "Hello, World";
//分配所需内存
void *mem = ::operator new(sizeof(Buffer) + strlen(charString) + 1);
//在现有内存块构造一个 "Buffer" 对象
Buffer* buf = new(mem) Buffer(strlen(charString));
   
//在不释放内存的情况下销毁 "Buffer" 对象
buf->~Buffer();
//释放内存
::operator delete(mem);
当然,对于此示例,您可以只使用普通的 new 和 delete,但它显示了如何将内存分配与其构造以及销毁与释放分开。
例如,如果您管理内存池并且当对象被销毁时内存可以回收回内存池而不是返回堆,则此技术很有用。
如何删除 placement new 分配的内存?

operator delete只能删除heap中创建的storage,所以placement new时不能使用delete operator删除storage。 在使用 placement new 操作符进行内存分配的情况下,由于它是在堆栈中创建的,因此编译器知道何时删除它并且它将自动处理内存的释放。 如果需要,可以在析构函数的帮助下编写它,如下所示。
// 使用析构函数删除由 placement new 分配的内存
#include<iostream>
#include<cstdlib>
#include<cmath>
using namespace std;

class Complex
{
private:
        double re_, im_;
public:
        // Constructor
        Complex(double re = 0, double im = 0): re_(re), im_(im)
        {
                cout << "Constructor : (" << re_
                        << ", " << im_ << ")" << endl;
        }

        // Destructor
        ~Complex()
        {
                cout << "Destructor : (" << re_ << ", "
                        << im_ << ")" << endl;
        }

        double normal()
        {
                return sqrt(re_*re_ + im_*im_);
        }

        void print()
        {
                cout << "|" << re_ <<" +j" << im_
                        << " | = " << normal() << endl;
        }
};

int main()
{
        // 堆栈上的缓冲区
        unsigned char buf[100];

        Complex* pc = new Complex(4.2, 5.3);
        Complex* pd = new Complex[2];

        // 使用placement new
        Complex *pe = new (buf) Complex(2.6, 3.9);

        // 使用对象
        pc -> print();
        pd[0].print();
        pd[1].print();
        pe->print();

        // 释放对象调用析构函数然后释放内存
        delete pc;

        // 调用对象 pd[0] 的析构函数,然后释放内存,对 pd[1] 也是如此
        delete [] pd;

        // 不删除:显式调用析构函数。
        pe->~Complex();

        return 0;
}
//output: 如图


placement new operator什么时候会显示segmentation fault?

应谨慎使用 placement new 运算符。 传递的地址可以是引用或指向有效内存位置的指针。 当传递的地址是:时,它可能会显示错误:

  • 指针,例如 NULL 指针。
  • 不指向任何位置的指针。
  • 它不能是空指针,除非它指向某个位置。
回复

使用道具 举报

1

主题

8

帖子

13

积分

新手上路

Rank: 1

积分
13
发表于 3 天前 | 显示全部楼层
大人,此事必有蹊跷!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表