1

动态内存的结果

问题: 动态内存申请一定成功吗?

  • 常见的动态内存分配代码

C 代码:

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

C++ 代码:

void code()
{
    int* p = new int[10];
    
    if( p != NULL )
    {
        // ...
    }
    
    delete p;
}
  • 必须知道的事实

    • malloc 函数申请失败时返回 NULL 值
    • new 关键字申请失败时(根据编译器不同)

      • 返回 NULL 值 (古代)
      • 抛出 std::bad_alloc 异常 (现代)

问题: new 语句中的异常是怎么抛出来的呢?

  • new 关键字在 C++ 规范中的标准行为

    • 在堆空间申请足够的内存

      • 成功:

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

        • 抛出 std::bad_alloc 异常

  • new 关键字在 C++ 规范中的标准行为

    • new 在分配内存时

      • 如果空间不足,会调用全局的 new_handler() 函数
      • new_handler() 函数中抛出 std::bad_alloc 异常
    • 可以自定义 new_handler() 函数

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

new_handler() 中,可以手动做一些内存整理的工作,使得更多的堆空间可以被使用。

  • new_handler() 函数的替换

    • 自定义一个无返回值无参数的函数
    • 调用 set_new_handler() 设置自定义的函数

      • 参数类型为 void(*)()
      • 返回值为默认的 new_handler() 函数入口地址

  • new_handler() 的定义和使用
void my_new_handler()
{
    cout << "No enough memory" << endl;
}

int main(int argc, char* argv[])
{
    set_new_handler(my_new_handler);
    
    // ...
    
    return 0;
}

编程实验: new_handler 初探

公用实验代码:

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

using namespace std;

class Test
{
private:
    int m_value;
    
public:
    Test()
    {
        cout << "Test()" << endl;
        
        m_value = 0;
    }
    
    ~Test()
    {
        cout << "~Test()" << endl;
    }
    
    void* operator new (unsigned int size)
    {
        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)
    {
        cout << "operator new[]: " << size << endl;
        
        // return malloc(size);
        
        return NULL;        // 注意这里! 模拟内存申请失败
    }
    
    void operator delete[](void* p) 
    {
        cout << "operator delete[]: " << p << endl;
        
        free(p);
    }
};

实验 1:不同编译器中 new_handler() 行为

void my_new_handler()
{
    cout << "void my_new_handler()" << endl;
}

void ex_func_1()
{
    new_handler func = set_new_handler(my_new_handler);  // 注意这里!
    
    try
    {
        cout << "func = " << func << endl;
        
        if( func )
        {
            func();
        }
    }
    catch(const bad_alloc&)
    {
        cout << "catch(catch bad_alloc&)" << endl;
    }
}

int main()
{
    ex_func_1();

    return 0;
}
输出:[g++]
func = 0

输出:[vc++2010]
func = 00000000

输出:[bcc]
func = 0x00401474
catch(catch bad_alloc&)

结论:
默认情况下,g++,vc++2010 并没有提供全局的 new_handler() 函数;
           gcc 提供了全局的 new_handler() 函数,并抛出了 bad_alloc 异常。             

实验 2:不同编译器 new 失败行为

void ex_func_2()
{
    Test* pt = new Test();
    
     cout << "pt = " << pt << endl;
     
     delete pt;
     
     pt = new Test[5];
     
     cout << "pt = " << pt << endl;
     
     delete[] pt;
}

int main()
{
    ex_func_2();

    return 0;
}
输出:[g++]
Test()
段错误

输出:[vc++2010]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000

输出:[bcc]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000

分析:
g++ 编译生成的可执行文件运行时发生段错误: 
new 重载函数返回 NULL, 于是就在 0 地址处创建对象,调用构造函数。构造函数中,m_value = 0,导致段错误。

vc++2010:
如果 new 的重载返回 NULL,对象未创建,构造函数不会被调用

bcc:
如果 new 的重载返回 NULL,对象未创建,构造函数不会被调用

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

  • 解决方案【动态内存分配失败时,返回空指针】

    • 全局范围(不推荐)

      • 重新定义 new / delete 的实现,不抛出任何异常
      • 自定义 new_handler() 函数,不抛出任何异常
    • 类层次范围

      • 重载 new / delete, 不抛出任何异常
    • 单此动态内存分配

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

编程实验:动态内存申请

实验 1: 类层次范围

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

using namespace std;

class Test
{
private:
    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 ex_func_2()
{
    Test* pt = new Test();
    
     cout << "pt = " << pt << endl;
     
     delete pt;
     
     pt = new Test[5];
     
     cout << "pt = " << pt << endl;
     
     delete[] pt;
}

int main()
{
    ex_func_2();

    return 0;
}
输出:[g++]
operator new: 4
pt = 0
operator new[]: 24
pt = 0

输出:[vc++2010]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000

输出:[bcc]
operator new: 4
pt = 00000000
operator new[]: 24
pt = 00000000

实验 2:单次动态内存分配范围

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

using namespace std;

void ex_func_3()
{
     int* p = new(nothrow) int[10];    // 注意这里!
    
     cout << "p = " << p << endl;
     
     delete p;
}

int main()
{
    ex_func_3();

    return 0;
}
输出:
p = 0x8300008
  • 实验结论

    • 不是所有的编译器都遵循 C++ 的标准规范
    • 编译器可能重定义 new 的实现,并在实现中抛出 bad_alloc 异常
    • 编译器的默认实现中,可能没有设置全局的 new_handler() 函数
    • 对于移植性要求较高的代码,需要考虑 new 的具体细节

小结

  • 不同的编译器在动态内存分配上的实现细节不同
  • malloc 函数在内存申请失败时返回 NULL 值
  • new 关键字在内存申请失败时

    • 可能返回 NULL 值
    • 可能抛出 bad_alloc 异常

关于 new 的小知识点补充:
在指定的内存空间上创建对象(需要手动调用析构函数)

#include <iostream>

using namespace std;

int main()
{
    int bb[2] = {0};

    struct ST
    {
        int x;
        int y;
    };

    ST* pt = new(bb) ST();    // 注意这里!

    pt->x = 1;
    pt->y = 2;
    
    cout << bb[0] << endl;
    cout << bb[1] << endl;

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

    return 0;
}
输出:
1
2

以上内容参考狄泰软件学院系列课程,请大家保护原创!


TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧