C/C++内存管理

1. C/C++内存分布

《深入理解计算机系统》p580

2. C语言中动态内存管理方式

3. C++中动态内存管理

  1. new/delete操纵内置类型

// 操作内置类型的测试程序
int main()
{
    //开辟一个int类型大小的空间
    int* p1 = new int;
    delete p1;

    //开辟一个int类型大小的空间,并初始化为0
    int* p2 = new int(0);
    delete p2;
    
    //开辟10个int类型大小的空间
    int* p3 = new int[10];
    delete []p3;

    //开辟10个int类型大小的空间,并初始化前6个元素
    int* p4 = new int[10]{0,2,3,5,6,1};
    delete []p3;
}

注:(1)、调试看看:对于内置类型new操作会不会对变量进行初始化操作?

(2)、调试看看:对于内置类型如果new与delete不匹配会发生什么?

  1. new/delete操纵自定义类型

// 测试new/delete操纵自定义类型的实现机制
class A
{
public:
    A(int a = 0)
    :_a(a)
    {
        cout << A() << this <<endl;
    }
    ~A()
    {
        cout << "~A()" << this << endl;
    }
private:
    int _a;
};
int main()
{
    A* p1 = new A;
    delete p1;

    A* p2 = (A*)malloc(sizeof(A));
    free(p2);

    A* p3 = new A[10];
    delete []p3;

    A* p4 = new A[5];
    delete p4;
}

注:(1)、调试看看:对于自定义类型new操作会不会对对象进行初始化操作?

答:new对自定义类型会进行初始化操作,并且通过调试发现:实际上new一个自定义类型会执行以下三部操作:(1).第一步new表达式会调用operator new/operator new[]的标准库函数,该函数会分配一块足够大、原始的、未命名的内存空间来存储自定义类型的对象或者对象数组。(2).第二步编译器会运行自定义类型的构造函数完成对象或者对象数组的初始化。(3).第三步返回一个指向该对象的指针

(2)、调试看看:对于自定义类型如果new与delete不匹配会发生什么?

答:比如这种情况:new了一块空间,但delete却只释放了一个。对于内置类型,delete/delete[]等同。对于自定义类型这将导致程序崩溃。

(3)、浅谈delete的实现机制(以vs编译器为例)?

答:(1).第一步调用N次对象或者对象数组的析构函数,如果是对象数组(数组内有N个对象),则需要调用N次析构函数,来进行数据清除。(2).第二步调用operator delete/operator delete[ ]来释放内存空间

所以对于delete p4/delete [ ]p4,前者是释放的是p4指向的空间,后者释放的是p4-4指向的空间。

  1. new/delete与malloc/free的区别

(1).new在申请空间失败时,会抛出异常(异常机制见《C++primer》p172、p684),不需要检查返回值;而malloc在申请空间失败后,会返回空指针(空指针:地址为0的空间),所以malloc需要检查返回值。测试代码如下:

#include<exception>
int main()
{
    try
    {
        while(1)
        {
            int* p1 = new int[1024*1024*1024];
            char* p2 = new char[1024*1024*1024];
            // 这里如果是char*的话,由于cout自动识别数据类型,这里空间里面的类型会识别为字符串,
            // 字符串的打印结束标志符为\0,但是该空间里面全是随机值,直接打印就会乱码。
            if(p1)
            {
                cout << p1 << endl;
                // 应该强转类型为void*
                cout << (void*)p2 << endl;
            }
            else
            {
                cout << "申请空间失败" << endl;
                break;
            }
        }
    }
    // exception是一个异常类,调用类中的成员函数what()来获取识别异常的字符串,catch括号里面的是
    // 异常申明,后面块中为异常处理代码
    catch(exception& e)
    {
        cout << e.what() << endl;
    }
    return 0;
}

(2).malloc/realloc/free是一个函数、new/delete是操作符。

(3).malloc/realloc分配好空间后不会对空间进行初始化,new会对空间进行初始化。

(4).申请自定义类型对象时,malloc/free只会开辟空间,不会调用他的构造函数/析构函数进行初始化/清除数据;new/delete会调用构造函数/析构函数。

(5).malloc在申请空间时需要手动计算空间大小(字节),而new则不用,new只需要后面跟一个空间类型或者加上对象的个数。

(6).malloc的返回值是一个void*,并且需要强转;而new不需要,new后跟空间类型。

(7).相同点:malloc与new都是在堆上申请空间,并且需要人为手动释放。

4. operator new与operator delete函数

首先:operator new和operator delete不是new&delete的重载,注意区分。《C++primer》p728。最主要的原因为:如果是重载,那么形参必须要有一个是自定义类型,但是operator new/operator delete的参数没有自定义类型,他是一个size_t。

new的底层是由malloc来实现,由于new的核心机制是开辟空间失败抛异常,而malloc的机制开辟空间失败是返回空指针。所以new在开辟空间失败的处理方式上对malloc进行了封装。这样才符合C++面向对象处理错误的方式。

void* operator new(size_t)
{
    void* p;
    //为了保证new一定可以申请到空间,在实现过程中,使用了while循环来判断是否成功申请
    while((p = malloc(size)) == 0)
    {
        // malloc开辟失败则抛出bad_alloc类型异常
        if(_callnewh(size) == 0)
        {
            static const std::bad_alloc nomen;
            _RAISE(nomen);
        }
    }
    // 开辟成功返回p
    return (p);
}

另外:operator new也可以单独使用。

/* operator delete: 该函数最终是通过free来释放空间的 */
/* free的实现 */
#define   free(p)   _free_dbg(p, _NORMAL_BLOCK)
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;
}

5. new和delete的实现原理

(1)new的实现原理

  1. 调用operator new函数来开辟一块足够大的原始的未命名的内存空间。(不成功抛异常)。

  1. 再调用构造函数对对象空间进行初始化。

  1. 返回该空间的地址。

(2)delete的实现原理

  1. 调用析构函数完成对象的数据清理。

  1. 再调用operator delete来释放掉空间。

(3).new[ ]的实现原理

  1. 调用operator new[ ]函数来开辟一块足够大的原始的未命名的内存空间。(不成功抛异常)。

  1. 再调用N次构造函数对对象空间进行初始化。

  1. 返回该空间的地址。

(4).delete[ ]的实现原理

  1. 调用N次析构函数完成N个对象的数据清理。

  1. 再调用operator delete[ ]函数来释放掉空间。

6. 定位new表达式(placement-new)

定位new表达式是对已经分配好了的空间调用构造函数进行初始化。

另外:构造函数的显式调用将在这里用到,其余地方应该不能,析构函数却能显式调用。

class A
{    
public:
    A(int a = 0)
    :_a(a)
    {}
    ~A()
    {}
private:
    int _a;
};
int main()
{
    A* p1 = (A*)malloc(sizeof(A));
    if(p1 == nullptr)
    {
        exit(-1);
    }
    // 定位new表达式的写法--对p1指向的空间,显式调用他的构造函数初始化
    new(p1)A(0);

    // 他的释放如下:可以调用delete或者分步调用
    p1->~A();
    free(p1);
}

拓展:池化技术(链接池、线程池):当要进行频繁的向堆申请内存空间(malloc也是池化技术的一种),为提高效率,需要定制一个内存池,malloc和new的共同点就是需要排队向堆申请空间,定位new的出现可以从内存池中来申请内存。例子:stl中list容器中的获取一个节点link_type get_node() {return list_node_allocator:: allocate();}这里的list_node_allocator:: allocate()就是STL_list的内存池。如下stl_list源码中:创建一个新节点,就利用了内存池进行申请空间,再使用定位new进行初始化。construct是初始化函数

他的定义如下:这里的初始化就用了定位new。

void construct(pointer p,const _Ty& value)
{
    new(static_cast<void*>(p))_Ty(value);// static_cast<void*>强转类型
}

7. 常见-面试题

什么是内存泄露?内存泄露有什么危害?

答:在使用new/malloc()/operator new/realloc开辟空间后没有使用delete/free()/operator delete进行内存释放,导致这块空间失去控制一直不释放,造成空间浪费。

危害:如果存在长时间的内存泄漏,将会导致内存空间被消耗完,致使程序崩溃。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值