内存分配不一定成功

常见内存分配的方法

code about c

int* p = (int*)malloc(10 * sizeof(int));
 if( p !=  NULL )
 {
     // ... 
     ...
 }

这种写法是合法的


code about c++

int* p = new int[10];
        
if( p != NULL )
 {
     // ... 
     ...
 }

对于古代编译器而言这种写法是合法的。对于现代编译就不合法了

  • 申请成功i的时候这句话没有意义
  • 申请失败的时候编译器会抛出异常

对于现代编译器而言:无论成功与失败都没有必要用 if()语句判断


申请内存失败时:   
1,malloc 函数申请失败时返回 NULL 值;   
2,new 关键字申请失败时(根据编译器的不同):      

  • 返回 NULL 值;古代编译器兼容 C 中方式,返回 NULL 值;     
  • 抛出 std::bad_alloc 异常;近代编译器不会返回 NULL 值,而是抛出一个标准库中的 std::bad_alloc 异常;      

    problem: new 语句中的异常是怎么抛出来的?   

    new 关键字在 C++ 规范中的标准行为:在堆空间申请足够大的内存;      

成功:

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

失败:          

C++ 规范定义要抛出 std::bad_alloc 异常; 
new 在分配空间时:     

  • 如果空间不足,会调用全局的 new_handler() 函数 。调用 new_handler() 函数的意义在于我们可以有机会做一些处理,使得有更多的空间可以空出来;整理空间、空出足够的内存 C++ 编译器是不知道的,这件事情具体的平台具体讨论,于是 C++ 标准规范就给了默认的 new_handler() 实现,即抛出异常;      
  • new_handler() 函数中抛出 std::bad_alloc 异常;   
  • 可以自定义 new_handler() 函数:处理默认的 new 内存分配失败的情况;C++ 平台不能整理空间、空出足够的内存,那么就交给我们自己来定义;

 void my_new_handler()
{
    cout << "No enough memory";
    cout << endl;
           
    exit(1);  // 内存不足了,就把当前的程序结束了;
}

int main(int argc, char* argv[])
{
    set_new_handler(my_new_handler);  // 告诉 C++ 编译器;可以设置自定义的处理函数,处理堆空间不足的情况;
           
     // ... ...
           
     return 0;
}

针对不同的编译器

如果跨编译器统一 new 的行为,提高代码移植性?      
无论在任何情况下,申请失败都返回空或者抛出异常;

为了兼顾古代编译器,一般做法是自定义 new,使得 new 在申请堆空间失败的时候,直接返回空指针,而不抛出异常;
### 解决方案

全局范围(不推荐):      
1,重新定义 nwe/delete 的实现,不抛出任何异常;      
2,自定义 new_handler() 函数,不抛出任何异常;          

  • 空函数摆在那里;                
  • 不推荐,全局范围重定义 new,风险是非常大的;   

类层次范围(推荐):
1,重载 new/delete,不抛出任何异常;      
2,失败了返回空指针;   


单次动态内存分配:  
1,使用 nothrow 参数,指明 new 不抛出异常;      
2,失败了返回空指针;


Test Code


#include <iostream>
#include <new>
#include <cstdlib>
#include <exception>

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;  // 这里当没有加上 throw() 时,编译器显示: warning: 'operator new' must not return NULL unless it is declared 'throw()' (or -fcheck-new is in effect);
    }
    
    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;
}

/* 证明 new_handler() 函数存在 */
void ex_func_1()
{
    /* 定义 func 变量,其类型为 new_handler 类型,C++ 中 new_handler 是一个预定义的函数指针,指向的函数类型是 void(*)(),调用 set_new_handler() 是将自定义的 my_new_handler() 处理函数设置进去,设置了自定义处理函数后,原来的处理函数就会作为返回值返回到 func,这点和上一节的不同,上一节是返回自定义的处理函数,这一节是返回原来的预定义处理函数; */
    new_handler func = set_new_handler(my_new_handler);  
    
    try
    {
        cout << "func = " << func << endl;
        
        if( func )  // 加上 if() 处理语句是因为默认的情况下面可能是没有处理函数的,此时 func 为空;
        {
            func();
        }
    }
    catch(const bad_alloc&)  // 想证明默认的 new_handler() 处理函数确实是要抛出 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()
{
    /* 这个语句是 C++ 标准语法,只是之前没见过而已 */
    int* p = new(nothrow) int[10];  // 现在进行动态内存申请,但是不管结果有没有成功,都不要抛出异常,结果失败,直接返回空;
    
    // ... ...
    
    delete[] p; 
    
    /* 上面 new 的写法也可以写成下面的形式 */
    
    int bb[2] = {0}; 
    
    struct ST
    {
        int x;
        int y;
    };
    
    ST* pt = new(bb) ST();  // 把 ST 对象创建到 bb[2] 的栈空间中去,即在指定的位置创建一个对象,括号的作用是向编译器指明要在指定的地址上面创建一个对象出来;
    
    /* 对创建的对象赋值 */
    pt->x = 1;
    pt->y = 2;
    
    /* 这里的打印想证明上面创建的对象确实存在 bb[2] 空间当中的 */
    cout << bb[0] << endl;  // 打印 1
    cout << bb[1] << endl;  // 打印 2
    
    pt->~ST();  // 显示的调用析构函数,因为我们指定了穿件对象的空间,这时必须显示手动调用析构函数;
}

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

summary

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

本文内容参考狄泰软件学院教程


huu红山竹
4 声望2 粉丝

study


« 上一篇
学习方法
下一篇 »
邻接链表c++