C/C++中基本的内存管理工具

C/C++中基本的内存管理工具

new操作符以及相应的操作符函数

new/delete,new[]/delete[]

  对于new/delete,new[]/delete[],他们均为C++的运算符。

他们的使用:

string *sp = new string;//单个对象
string *arr = new string[10];//10个对象

delete sp;
delete[] arr;

当我们使用一个new表达式时,他实际上执行了三个步骤:
  1. new 表达式调用一个operator new(operator new[])的标准库函数,请求分配一块内存空间。
  2. 将分配的空间(void * )转为对应的对象指针。
  3. 编译器运行相应的构造函数以构造这些对象,并为其传入初始值。

  而当我们使用一条delete 表达式删除一个对象时,它背后实际上执行了两步操作:
  1. 调用对象对应的析构函数
  2. 调用名为operator delete(或者 operator delete[])的标准库函数释放内存空间。

  测试代码:

\\测试代码
#include <iostream>
using namespace std;
class Complex
{
private:
    int a;
    int b;
public:
    //默认构造函数
    Complex():a(0),b(0){
        cout<<"default Complex constructor"<<endl;
    };
    //有参数的构造函数
    Complex(const int &i,const int &j = 0):a(i),b(j){
        cout<<"Complex constructor"<<endl;
    }

    //析构函数
    ~Complex(){
        cout<<"Complex destructor"<<endl;
    };
};
void* operator new(size_t size){
    cout<<"operator new"<<endl;
    return malloc(size);
}
void* operator new[](size_t size){
    cout<<"operator new[]"<<endl;
    return malloc(size);
}
void operator delete(void *p){
    cout<<"operator delete"<<endl;
    return free(p);
}
void operator delete[](void *p){
    cout<<"operator delete[]"<<endl;
    return free(p);
}

int main(){

    Complex* p0 = new Complex;
    Complex* p1 = new Complex[10];
    delete(p0);
    delete[](p1);
    system("pause");

    return 0;
}

在这里插入图片描述

placement new

  调用形式如下:

new (place_address) type
new (place_address) type (initializers)
new (place_address) type [size]
new (place_address) type [size]{ braced initializer list}

其中place_address必须是一个指针,同时在initializers中提供一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。(这个new不分配空间)

  当我们使用一个**placement new(定位new)*时,定位new使用operator new(size_t,void)"分配"它的内存。这是一个我们无法自定义的operator new版本。该函数不分配任何内存,它只是简单的返回指针实参;然后由new表达式负责在指定的地址初始化对象以完成整个工作。定位new允许我们在一个特定的,预先分配的内存地址上构造对象。

  使用实例:

void* ptr = malloc(sizeof(Complex));
complex* ptr1 = new(ptr)Complex(2,3);

void* ptr2 = malloc(sizeof(Complex)*5);
void* ptr3 = new(ptr2)Complex[5]{{1,2},3};

free(ptr);
free(ptr2);

  测试代码:

#include <iostream>
using namespace std;
class Complex
{
public:
    int a;
    int b;
public:
    //默认构造函数
    Complex():a(0),b(0){
        cout<<"default Complex constructor"<<endl;
    };
    //有参数的构造函数
    Complex(const int &i,const int &j=0):a(i),b(j){
        cout<<"Complex constructor"<<endl;
    }

    //析构函数
    ~Complex(){
        cout<<"Complex destructor"<<endl;
    };
};
void* operator new(size_t size){
    cout<<"operator new"<<endl;
    return malloc(size);
}
void* operator new[](size_t size){
    cout<<"operator new[]"<<endl;
    return malloc(size);
}
void operator delete(void *p){
    cout<<"operator delete"<<endl;
    return free(p);
}
void operator delete[](void *p){
    cout<<"operator delete[]"<<endl;
    return free(p);
}

int main(){

    //单个对象的定位new
    void* p0 = malloc(sizeof(Complex));
    cout<<"分配的内存地址:"<<p0<<endl;
    Complex* p1 = new(p0)Complex(2,3);
    cout<<"经过定位new操作后的内存地址:"<<p1<<endl;
    cout<<endl<<endl;
    
    //多个对象的定位new:
    void* p2 = malloc(sizeof(Complex)*5);
    cout<<"分配的内存地址:"<<p2<<endl;
    Complex* p3 = new(p2)Complex[5]{{1,2},3};
    cout<<"经过定位new操作后的内存地址:"<<p3<<endl;
    
    free(p0);
    free(p2);
    system("pause");
    return 0;
}

在这里插入图片描述

operator new/operator new[],operator delete/operator delete[]

operator new/operator new[]

  operator new函数原型:

throwing(1)void* operator new (std::size_t size);
nothrow(2)void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement(3)void* operator new (std::size_t size, void* ptr) noexcept;

(1)分配大小为size的字节,并适当对齐以表示该大小的对象,并返回指向该块第一个字节的非空指针。分配失败时(内存耗尽或者没有足够大的连续内存空间),它会抛出一个bad_alloc异常。

(2)与上面(1)的功能相同,除了失败时返回空指针而不是抛出异常。(默认定义调用第一个版本)

(3)返回ptr指针(不分配存储空间

  operator new[]函数原型:

throwing(1)void* operator new[] (std::size_t size) ;
nothrow(2)void* operator new[] (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement(3)void* operator new[] (std::size_t size, void* ptr) noexcept;

与operator new功能相同,区别在与它是数组形式的分配函数。

(1)默认定义调用operator new(size),功能与operator delete相同,只不过是数组形式。
(2)默认定义调用(1)
(3)返回ptr指针(不分配存储空间

operator delete/operator delete[]
ordinary(1)void operator delete (void* ptr) noexcept;
nothrow (2)void operator delete (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement(3)void operator delete (void* ptr, void* voidptr2) noexcept;

(1)释放由ptr(如果非空)指向的内存块,释放先前通过operator new分配给它的存储空间,并使该指针位置无效。

(2)与(1)相同,默认定义调用第一个版本的operator delete

(3)什么也不做。

ordinary(1)void operator delete[] (void* ptr) noexcept;
nothrow (2)void operator delete[] (void* ptr, const std::nothrow_t& nothrow_constant) noexcept;
placement(3)void operator delete[] (void* ptr, void* voidptr2) noexcept;

(1)默认定义调用operator delete(ptr),功能与operator delete相同,只不过是数组形式。
(2)默认定义调用(1)
(3)什么也不做。

测试代码:

#include <iostream>
using namespace std;
class Complex
{
private:
    int a;
    int b;
public:
    //默认构造函数
    Complex():a(0),b(0){
        cout<<"default Complex constructor"<<endl;
    };
    //有参数的构造函数
    Complex(const int &i,const int &j=0):a(i),b(j){
        cout<<"Complex constructor"<<endl;
    }

    //析构函数
    ~Complex(){
        cout<<"Complex destructor"<<endl;
    };
};

int main(){

    //使用array new分配空间
    void* ptr0 = operator new[](sizeof(Complex)*5);
    //使用placement new构造对象,无法直接调用构造函数
    new(ptr0) Complex[5]{1,2,3};
    Complex* ptr1 = static_cast<Complex*>(ptr0);

    //模拟delete[],先析构,后释放空间
    for (size_t i = 0; i < 5; i++)
    {
        ptr1[i].~Complex();
    }
    operator delete[](ptr0);
    

    system("pause");
    return 0;
}

在这里插入图片描述

关于new与delete背后工作的图示

  在候捷老师内存管理的课程中有图如下:展示了new表达式背后的工作,分配内存,类型转换,调用构造函数(只有编译器能直接调用ctor,构造函数),我们可以利用placement new间接调用ctor。以及在vc98中,operator new的实现细节,通过调用(CRT,C runtime libary),malloc来分配空间。

在这里插入图片描述
  下图展示了delete表达式背后的工作,调用析构函数,调用operator delete(实际上通过调用free(),C函数)来释放空间。

在这里插入图片描述

NOTE:对于malloc分配的空间,可以通过placement new来调用构造函数,初始化对象。析构函数是可以直接被调用的

关于内存分配的CRT(C runtime Libary)

calloc/realloc/malloc/free

动态内存管理数函数说明
void* malloc (size_t size);分配一块大小为size字节的内存块,并返回一个指向该块开头的指针。新分配的内存块的内容未初始化,值不确定。如果size为零,则返回值取决于特定的库实现(它可以是NULL指针,也可以不是NULL指针),但是不应取消对返回的指针的引用。
void free (void* ptr);释放由malloc/calloc/realloc分配的内存块,使得这个内存块能再一次的被分配。如果ptr指向的是不是以上函数分配的内存块,那么会导致不确定的行为。ptr为空,则这个函数不做任何事。这个函数不会改变ptr的值,因此ptr仍然指向之前分配的内存。
void* calloc (size_t num,size_t size);为元素大小为size,含有num个元素的数组分配内存空间,并且把这个空间的所有位(bits)置为0。
void* realloc (void* ptr,size_t size);重新分配内存块。更改ptr指向的内存块的大小。该函数可以将内存块移动到新的位置(其地址由该函数返回)。即使将存储块移动到新位置,该内存块的内容也会保留到新旧大小中的较小者。如果新大小较大,则新分配部分的值不确定。如果ptr是空指针,则该函数的行为类似于malloc。

测试代码:

#include <iostream>
#include "stdlib.h"
using namespace std;

int main(){ 
    int num = 10;
    int *ptr = (int*)calloc(num,sizeof(int));
    cout<<"ptr地址:"<<ptr<<endl;
    for (size_t i = 0; i < num; i++)
    {
        cout<<ptr[i]<<",";
    }
    cout<<endl;
    

    ptr = (int*)realloc((void *)ptr,num/2*sizeof(int));
    cout<<"ptr地址:"<<ptr<<endl;
    for (size_t i = 0; i < num/2; i++)
    {
        cout<<ptr[i]<<",";
    }
    cout<<endl;

    ptr = (int*)realloc((void *)ptr,num*2*sizeof(int));
    cout<<"ptr地址:"<<ptr<<endl;
    for (size_t i = 0; i < num*2; i++)
    {
        cout<<ptr[i]<<",";
    }
    cout<<endl;
    free(ptr);
    system("pause");
    return 0;
}

Reference

1.https://www.bilibili.com/video/BV1Kb411B7N8 C++内存管理,候捷,博览网课程

2.http://www.cplusplus.com/reference/cstdlib/ C++ reference

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值