C/C++内存申请和释放(二)

终于、终于、终于又有时间写博客了,这一篇接着上一篇介绍一下 C++ 中的 new 和 delete 的使用,下一篇介绍一下C++中的引用,把前面的坑填上

如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢
如果看过上一篇中介绍的C/C++能存申请和释放(一),看这篇文章那就轻松多了

new

基本用法
int* p = new int;     // 申请单个空间
int* q = new int[10]; // 申请连续空间
  • new 在申请基本类型空间的时,主要会经历两个过程:
  1. 调用 operator new(size_t) 申请空间
  2. 进行强制类型转换(代码如下)
type* p = new type;
// 执行上面这条语句实际的过程是下面的语句
void* tmp = operator new(sizeof(type)); // 调用 operator new(size_t) 申请空间
type* p = static_cast<type*>(tmp); // 进行强制类型转换
  • new 在申请 object 空间时,主要会经历三个过程:
  1. 调用 operator new(size_t) 申请空间
  2. 进行强制类型转换
  3. 调用类的构造函数(代码如下)
classname* p = new classname;
// 执行上面的语句实际的过程是下面的条语句
void* tmp = operator new(sizeof(classname)); // 调用 operator new(size_t) 申请空间
classname* p = static_cast<classname*>(tmp);  // 进行强制类型转换
p->classname::classname(); // 调用类的构造函数,用户不允许这样调用构造函数,如果用户想调用可以通过 定位(placement)new 运算符 的方式调用
  • 定位(placement) new 运算符
    定位(placement) new 运算符允许我们将 object 或基本类型的数据创建在已申请的内存中,并且 定位new 运算符没有对应的 定位(placement) delete ,因为 定位 new 运算符 根本没有申请内存空间,如果需要调用析构函数,需要程序员显式调用析构函数
char* buf1 = new char[40];
char* buf2 = new char[sizeof(classname)];
int* q1 = new(buf1)int[5];
int* q2 = new(buf1)int[5]; // 如果这样定义 buf1、q1、q2 起始地址是一样的,也是就是 q2 会覆盖掉 q1;  
int* q2 = new(buf1 + sizeof(int) * 5)int[5]; // 正确创建 q2 的方式
classname* p = new(buf2)classname; // 构造函数不需要传参
classname* p = new(buf2)classname(type...); // 构造函数需要传参
new的探究

operator new(size_t); 这个特殊的函数是允许重载的

  1. operator new(size_t); 在类内重载,如在类内重载一定是 static 静态函数,因为类内的 operator new(size_t); 函数调用可能是在创建对象时,但是 C++ 中对于这个特殊函数的重载可以不加 static
  2. operator new(size_t); 在类外重载,也就是全局的重载,这样做是有一定危险的,全局函数也就可以任意调用,影响比较大,不建议进行类外重载
  3. 重载时需要注意,函数的返回值为 void* ,第一个参数一定是 size_t
  4. 后面会出一篇文章专门讲一下 C++ 函数和运算符的重载,这里不再赘述

new 的底层是由 malloc 实现的

// 这是 VC\Tools\MSVC\14.16.27023\crt\src\vcruntime\new_scalar.cpp 中的代码,可以看到 new 的底层还是 malloc

_CRT_SECURITYCRITICAL_ATTRIBUTE
void* __CRTDECL operator new(size_t const size)
{
    for (;;)
    {
        if (void* const block = malloc(size))
        {
            return block;
        }

        if (_callnewh(size) == 0)
        {
            if (size == SIZE_MAX)
            {
                __scrt_throw_std_bad_array_new_length();
            }
            else
            {
                __scrt_throw_std_bad_alloc();
            }
        }

        // The new handler was successful; try to allocate again...
    }
}

delete

基本用法
delete p;
delete[] q;
  • delete 的过程与 new 很相似,会调用 operator delete(); 释放内存
delete p;
// 执行上面的代码实际过程是下面的语句
operator delete(p);
  • delete 释放 object 空间
  1. 调用类的析构函数
  2. 调用 operator delete(); 释放内存
delete obj;
// 执行上面的语句实际过程是下面的语句
obj->~classname(); // 首先调用类的析构函数
operator delete(obj); // 释放 object 内存空间
delete 的探究

delete 底层是由 free 实现的

// 这是 VC\Tools\MSVC\14.16.27023\crt\src\vcruntime\delete_scalar.cpp 中的代码,可以看到 delete 的底层还是 free

_CRT_SECURITYCRITICAL_ATTRIBUTE
void __CRTDECL operator delete(void* const block) noexcept
{
    #ifdef _DEBUG
    _free_dbg(block, _UNKNOWN_BLOCK);
    #else
    free(block);
    #endif
}
  • 注意:释放 object 数组
  1. 在 new 一个数组时,与 malloc 相似OS会维护一张记录数组头指针和数组长度的表
  2. 释放基本数据类型的指针时,数组的头指针最终会被 free(q); 释放,所以不论是 delete q; 或者 delete[] q; 最终的结果都是调用 free(q); 释放内存
  3. 释放 object 数组空间时,如果有空间需要在析构函数中释放,直接调用 delete obj; 只会调用一次析构函数,然后就执行 free(obj); 没有调用其他数组元素的析构函数很容易导致内存泄漏,所以在释放 object 数组时,一定要用 delete[] obj; 释放内存,总而言之,数组最好是用 delete[] 的方式释放,这里只是解释一下,为什么这么用
  4. 定位new 如果创建了 object ,因为没有对应的 定位delete ,所以需要程序员显式的调用类的析构函数
char* buf = new char[512];
// 定位new 在指定位置创建一个 object
classname *obj = new(buf)classname;
// 程序员需要显式调用类的析构函数
obj->~classname();
delete[] buf;
发布了20 篇原创文章 · 获赞 3 · 访问量 489

猜你喜欢

转载自blog.csdn.net/xiao_ma_nong_last/article/details/103330767