C++--自定义内存管理

1、关于动态内存分配

2、new / delete的重载

3、重载new[] / delete[]

4、动态内存申请的结果

1、动态内存申请的结果

2、跨编译器统一new的行为

5、小结

1、关于动态内存分配

new关键字与malloc函数的区别

- new关键字是C++的—部分 ,malloc是由C库提供的函数

- new以具体类型为单位进行内存分配 ,malloc以字节为单位进行内存分配

- new在申请内存空间时可进行初始化 ,malloc仅根据需要申请定量的内存空间

- new在所有C++编译器中都被支持 ,malloc在某些系统开发中是不能调用

- new能够触发构造函数的调用 ,malloc仅分配需要的内存空间

- 对象的创建只能使用new ,malloc不适合面向对象开发

delete和free的区别

- delete在所有C++编译器中都被支持 ,free在某些系统开发中是不能调用

- delete能够触发析构函数的调用 ,free仅归还之前分配的内存空间

- 对象的销毁只能使用delete ,free不适合面向对象开发

面试题:new关键字创建出来的对象位于什么地方?

2、new / delete的重载

new / delete的本质是C++预定义的操作符

C++对这两个操作符做了严格的行为定义

☛ new:

① 获取足够大的内存空间(默认为堆空间)

② 在获取的空间中调用构造函数创建对象

☛ delete:

① 调用析构函数销毁对象

② 归还对象所占用的空间(默认为堆空间)

在C++中能够重载new / delete操作符

- 全局重载(不推荐)

- 局部重载(针对具体类进行重载)

重载new / delete的意义在于改变动态对象创建时的内存分配方式

new / delete的重载方式

// static member function, 默认为静态成员函数
void* operator new (unsigned int size)
{
void* ret = NULL;

/* ret point to allocated memory */

return ret;

}
// static member function
void operator delete (void* p)
{
/* free the memory which is pointed by p */
}
编程实验

静态存储区中创建动态对象  test.cpp

#include     
  
using namespace std;  
  
class Test  
{  
    static const unsigned int COUNT = 4;  
    static char c_buffer[];  
    static char c_map[]; 
      
    int m_value;  
public:  
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        for(int i=0; i<COUNT; i++)  // 在c_buffer中找到空闲位置
        {  
            if( !c_map[i] )  
            {  
                c_map[i] = 1;  
                  
                ret = c_buffer + i * sizeof(Test);  // 可用空间的首地址
                  
                cout << "succeed to allocate memory: " << ret << endl;  
                  
                break;  
            }  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            char* mem = reinterpret_cast<char*>§;

int index = (mem - c_buffer) / sizeof(Test);  
            int flag = (mem - c_buffer) % sizeof(Test);  
             
            if( (flag == 0) && (0 <= index) && (index < COUNT) )  
            {  
                c_map[index] = 0;  
                  
                cout << “succeed to free memory: " << p << endl;  
            }  
        }  
    }  
};  
  
char Test::c_buffer[sizeof(Test) * Test::COUNT] = {0};  
char Test::c_map[Test::COUNT] = {0};  
  
int main(int argc, char argv[])  
{  
    cout << "===== Test Single Object ===" << endl;  
       
    Test
 pt = new Test;  
      
    delete pt;  
      
    cout << "=== Test Object Array =====” << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << “pa[” << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}

面试题 :如何在指定的地址上创建C++对象?

解决方案

-在类中重载new / delete操作符

-在new的操作符重载函数中返回指定的地址

-在delete操作符重载中标记对应的地址可用

编程实验

自定义动态对象的存储空间   test.cpp

#include   
#include   
  
using namespace std;  
  
class Test  
{  
    static unsigned int c_count;  
    static char* c_buffer;  
    static char* c_map;  
      
    int m_value;  
public:  
    static bool SetMemorySource(char* memory, unsigned int size)  
    {  
        bool ret = false;  
          
        c_count = size / sizeof(Test);  
          
        ret = (c_count && (c_map = reinterpret_cast<char*>(calloc(c_count, sizeof(char)))));  
          
        if( ret )  
        {  
            c_buffer = memory;  
        }  
        else  
        {  
            free(c_map);  
              
            c_map = NULL;  
            c_buffer = NULL;  
            c_count = 0;  
        }  
          
        return ret;  
    }  
      
    void* operator new (unsigned int size)  
    {  
        void* ret = NULL;  
          
        if( c_count > 0 )  
        {  
            for(int i=0; i<c_count; i++)  
            {  
                if( !c_map[i] )  
                {  
                    c_map[i] = 1;  
                      
                    ret = c_buffer + i * sizeof(Test);  
                      
                    cout << "succeed to allocate memory: " << ret << endl;  
                      
                    break;  
                }  
            }  
        }  
        else  
        {  
            ret = malloc(size);  
        }  
          
        return ret;  
    }  
      
    void operator delete (void* p)  
    {  
        if( p != NULL )  
        {  
            if( c_count > 0 )  
            {  
                char* mem = reinterpret_cast<char*>§;  
                int index = (mem - c_buffer) / sizeof(Test);  
                int flag = (mem - c_buffer) % sizeof(Test);  
                  
                if( (flag == 0) && (0 <= index) && (index < c_count) )  
                {  
                    c_map[index] = 0;  
                      
                    cout << “succeed to free memory: " << p << endl;  
                }  
            }  
            else  
            {  
                free§;  
            }  
        }  
    }  
};  
  
unsigned int Test::c_count = 0;  
char* Test::c_buffer = NULL;  
char* Test::c_map = NULL;  
  
int main(int argc, char argv[])  
{  
    char buffer[12] = {0};  
      
    Test::SetMemorySource(buffer, sizeof(buffer));  
      
    cout << "===== Test Single Object ===" << endl;  
       
    Test
 pt = new Test;  
      
    delete pt;  
      
    cout << "=== Test Object Array =====” << endl;  
      
    Test* pa[5] = {0};  
      
    for(int i=0; i<5; i++)  
    {  
        pa[i] = new Test;  
          
        cout << “pa[” << i << "] = " << pa[i] << endl;  
    }  
      
    for(int i=0; i<5; i++)  
    {  
        cout << "delete " << pa[i] << endl;  
          
        delete pa[i];  
    }  
      
    return 0;  
}

3、重载new[] / delete[]

new[] / delete[]与new / delete完全不同

-动态对象数组创建通过new[]完成

-动态对象数组的销毁通过delete[]完成

-new[] / delete[]能够被重载,进而改变内存管理方式

new[] / delete[]的重载方式

// static member function
void* operator new[] (unsigned int size)
{
return malloc(size);
}
// static member function
void operator delete[] (void* p)
{
free§;
}
注意事项

-new[]实际需要返回的内存空间可能比期望的要多

-对象数组占用的内存中需要保存数组信息

-数组信息用于确定构造函数和析构函数的调用次数

编程实验

动态数组的内存管理    test.cpp

#include    
#include   
  
using namespace std;  
  
class Test  
{  
    int m_value;  
public:  
    Test()  
    {  
        m_value = 0;  
    }  
      
    ~Test()  
    {  
    }  
      
    void* operator new (unsigned int size)  
    {  
        cout << "operator new: " << size << endl;  
          
        return malloc(size);  
    }  
      
    void operator delete (void* p)  
    {  
        cout << "operator delete: " << p << endl;  
          
        free§;  
    }  
      
    void* operator new[] (unsigned int size)  
    {  
        cout << "operator new[]: " << size << endl;  
          
        return malloc(size);  
    }  
      
    void operator delete[] (void* p)  
    {  
        cout << "operator delete[]: " << p << endl;  
          
        free§;  
    }  
};  
  
int main(int argc, char argv[])  
{  
    Test
 pt = NULL;  
      
    pt = new Test;  
      
    delete pt;  
      
    pt = new Test[5];  
      
    delete[] pt;  
      
    return 0;  
}

多四个字节,用于保存数组的大小信息

4、动态内存申请的结果

1、动态内存申请的结果

malloc函数申请失败时返回NULL值

new关键字申请失败时(根据编译器的不同)

• 返回NULL值 (古老C++编译器兼容malloc)

• 抛出std::bad_alloc异常 (现代C++编译器)

编译器的不同导致new内存申请失败时的行为不同。。。

那么new语句中的异常是如何抛出的?

new 关键字在C++规范中的标准行为(但编译器不一定遵循)

- 在堆空间申请足够大的内存

• 成功: 在获取的空间中调用构造函数创建对象 ,返回对象的地址

• 失败: 抛出std::bad_alloc异常

- new在分配内存不足时

• 如果空间不足,会调用全局的new_handler()函数

• new_handler()函数中抛出std::bad_alloc异常

- 可以自定义new _handler()函数

• 处理默认的new内存分配失败的情况

typedef void (*new_handler)();

new_handler set_new_handler (new_handler new_p) throw(); // C++98
new_handler set_new_handler (new_handler new_p) noexcept; // C++11
new_handler()的定义和使用

void my_new_handler()
{
cout << “No enough memory” << endl;

exit(1);

}

int main()
{
set_new_handler(my_new_handler);

// ...

return 0;

}
2、跨编译器统一new的行为

问题 :如何跨编译器统一new的行为, 提高代码移植性?

解决方案 (3种)

-全局范围(不推荐)

☛ 重新定义new / delete的实现,不抛出任何异常

☛ 自定义new_handler()函数,不抛出任何异常 (后面分析VS中new实现发现此方法对VS有效)

-类层次范围

☛ 重载new / delete , 不抛出任何异常

-单次动态内存分配

☛ 使用nothrow参数,指明new不抛出异常

编程实验

动态内存申请    E3-1.cpp

#include
#include
#include
#include

using namespace std;

class Test
{
int m_value;
public:
Test()
{
cout << “Test()” << endl;

    m_value = 0;
}

~Test()
{
    cout << "~Test()" << endl;  
}

void* operator new (unsigned int size) throw()
{
    cout << "operator new: " << size << endl;
    
    // return malloc(size);
    
    return NULL;  // 内存申请失败
}

void operator delete (void* p)
{
    cout << "operator delete: " << p << endl;
    
    free(p);
}

void* operator new[] (unsigned int size) throw()
{
    cout << "operator new[]: " << size << endl;
    
    // return malloc(size);
    
    return NULL;
}

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

};

void my_new_handler()
{
cout << “void my_new_handler()” << endl;
}

void ex_func_1()
{
new_handler func = set_new_handler(my_new_handler); // 返回原来的new_handler处理函数

try
{
    cout << "func = " << func << endl;   // 若默认没有处理函数func为NULL
    
    if( func )
    {
        func(); // 若默认情况有处理函数,会抛出bad_alloc异常
    }
}
catch(const bad_alloc&)
{
    cout << "catch(const bad_alloc&)" << endl;
}

}

void ex_func_2()
{
Test* pt = new Test();

cout << "pt = " << pt << endl;

delete pt;

pt = new Test[5];

cout << "pt = " << pt << endl;

delete[] pt; 

}

void ex_func_3()
{
int* p = new(nothrow) int[10]; // 不管成功与否都不抛出异常,失败返回NULL

// ... ...

delete[] p; 

int bb[2] = {0};

struct ST
{
    int x;
    int y;
};

ST* pt = new(bb) ST(); // 将ST的对象创建到bb栈空间里

pt->x = 1;
pt->y = 2;

cout << bb[0] << endl;
cout << bb[1] << endl;

pt->~ST();  // 需要手工调用析构函数

}

int main(int argc, char *argv[])
{
// ex_func_1();
// ex_func_2();
// ex_func_3();

return 0;

}

① 调用ex_fun_1()函数

VS2015

VS2015和g++打印结果都为0,说明默认没有全局的new_handler处理函数

Borland C++ 5.5.1(标准C++)

遵循标准C++的BCC编译器有默认new_handler处理函数,在这个函数中会抛出bad_alloc异常

② 调用ex_fun_2()函数

在重载new时若不加上throw()

- 在g++下,new的重载返回NULL时,在NULL地址处调用了构造函数创建对象,操作0地址,段错误

- 在VS2015和BCC下,new的重载返回NULL时,没有调用构造函数,pt打印0

三款编译器动态内存申请失败时的行为不一致

加上throw统一了编译器行为,在new失败时不抛任何异常,pt都打印0

③ 调用ex_fun_3()函数

实验结论

-不是所有的编译器都遵循C++的标准规范

-编译器可能重定义new的实现,并在实现中抛出bad_alloc异常

(虽然现代编译器申请失败时会抛出bad_alloc异常,但不一定是在new_handler里抛出的

实验可以看到VS和g++默认没有设置全局的new_handler函数,只有BCC设置了)

- 编译器的默认实现中,可能没有设置全局的new_handler()函数

-对于移植性要求较高的代码,需要考虑new的具体细节

查看分析VS2010下new的实现文件

xxx\Microsoft Visual Studio 10.0\VC\crt\src\new.cpp

/***
*new.cxx - defines C++ new routine
*

  •   Copyright (c) Microsoft Corporation.  All rights reserved.
    

*Purpose:

  •   Defines C++ new routine.
    

*******************************************************************************/

#ifdef _SYSCRT /* 通过该宏区分两个版本的new实现 */
#include <cruntime.h>
#include <crtdbg.h>
#include <malloc.h>
#include <new.h>
#include <stdlib.h>
#include <winheap.h>
#include <rtcsup.h>
#include <internal.h>

void * operator new( size_t cb )
{
void *res;

for (;;) {

    //  allocate memory block
    res = _heap_alloc(cb);   

    //  if successful allocation, return pointer to memory

    if (res)
        break;

    //  call installed new handler    申请失败,会调用定义好的new_handler函数 ,而这个过程通过_callnewh函数完成
    if (!_callnewh(cb))
        break;

    //  new handler was successful -- try to allocate again
}

RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));  // 调试代码忽视

return res;

}
#else /* _SYSCRT */

#include
#include

_C_LIB_DECL
int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);
_END_C_LIB_DECL

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{ // report no memory
static const std::bad_alloc nomem; // 这个实现中只要不成功必然抛异常
_RAISE(nomem);
}

    return (p);
    }

/*

  • Copyright © 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED.
  • Consult your license regarding permissions and restrictions.
    V3.13:0009 /
    #endif /
    _SYSCRT */
    _callnewh

如果没有设置全局的处理函数或者设置了全局的处理函数但这个处理函数不能使有更多堆空间,返回值为0

如果没有设置全局处理函数就抛出bad_alloc异常

5、小结

new / delete的本质为操作符

可以通过全局函数重载new / delete (不推荐)

可以针对具体的类重载new / delete

new[]  / delete[]与new / delete完全不同

new[]  / delete[]也是可以被重载的操作符

new[] 返回的内存空间可能比期望的要多

不同的编译器在动态内存分配上的实现细节不同

malloc函数在内存申请失败时返回NULL值

new关键字在内存申请失败时

-可能返回NULL值

-可能抛出bad_alloc异常 
————————————————
版权声明:本文为CSDN博主「洋葱汪」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_39654127/article/details/79870658

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章