• C++中动态内存申请的结果


    1,问题:

        1,动态内存申请一定成功吗?

           1,不一定成功;

       

    2,常见的动态内存分配代码:

        1,C 代码:

    1 int* p = (int*)malloc(10 * sizeof(int));
    2        
    3 if( p !=  NULL )
    4 {
    5     // ... ...
    6 }

           1,这种写法合理;

        2,C++ 代码:

    1 int* p = new int[10];
    2        
    3 if( p != NULL )
    4 {
    5     // ... ...
    6 }

           1,古代编译器这种写法合理;

           2,现代编译器这种写法就不合理,申请成功时,此语句没有任何意义,申请失败后,就会抛出一个标准库中的异常对象,程序就不会向下执行到  if() 语句;

           3,如果用的是一款现代的 C++ 编译器,new 的结果无论成功或者失败,根本用不着使用 if() 判断语句;

          

    3,申请内存失败时:

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

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

           1,返回 NULL 值;

               1,古代编译器兼容 C 中方式,返回 NULL 值;

           2,抛出 std::bad_alloc 异常;

               1,近代编译器不会返回 NULL 值,而是抛出一个标准库中的 std::bad_alloc 异常;

          

    4,问题:

        1,new 语句中的异常是怎么抛出来的?

       

    5,new 关键字在 C++ 规范中的标准行为:

        1,在堆空间申请足够大的内存;

           1,成功:

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

               2,返回对象的地址;

           2,失败:

               1,C++ 规范定义要抛出 std::bad_alloc 异常;

        2,new 在分配空间时:

           1,如果空间不足,会调用全局的 new_handler() 函数(见 9 中例一);

               1,调用 new_handler() 函数的意义在于我们可以有机会做一些处理,使得有更多的空间可以空出来;

               2,整理空间、空出足够的内存 C++ 编译器是不知道的,这件事情具体的平台具体讨论,于是 C++ 标准规范就给了默认的 new_handler() 实现,即抛出异常;

           2,new_handler() 函数中抛出 std::bad_alloc 异常;

        3,可以自定义 new_handler() 函数:

           1,处理默认的 new 内存分配失败的情况;

           2,C++ 平台不能整理空间、空出足够的内存,那么就交给我们自己来定义;

          

    6,new_handler() 的定义和使用:

        1,代码示例:

    复制代码
    1 void my_new_handler()
    2 {
    3     cout << "No enough memory";
    4     cout << endl;
    5            
    6     exit(1);  // 内存不足了,就把当前的程序结束了;
    7 }
    复制代码

           1,实际产品开发中,一般会尝试在这个函数中进行内存的整理,整理后期望有更多的堆空间空出来,然后满足当前程序对内存的需求;

           2,也可以内存不足自己结束程序;

           3,友好的方式是抛一个异常,然后进行异常处理,这是 C++ 默认的实现方式;

      2,使用:

    复制代码
    1 int main(int argc, char* argv[])
    2 {
    3     set_new_handler(my_new_handler);  // 告诉 C++ 编译器;可以设置自定义的处理函数,处理堆空间不足的情况;
    4            
    5      // ... ...
    6            
    7      return 0;
    8 }
    复制代码

          

    7,问题:

        1,如果跨编译器统一 new 的行为,提高代码移植性?

           1,无论在任何情况下,申请失败都返回空或者抛出异常;

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

       

    8,解决方案:

        1,全局范围(不推荐):

           1,重新定义 nwe/delete 的实现,不抛出任何异常;

           2,自定义 new_handler() 函数,不抛出任何异常;

               1,空函数摆在那里;

               2,见本文 10.2.2.1 分析,这个方案对 VS 2010 编译器是有用的;

           3,不推荐,全局范围重定义 new,风险是非常大的;

        2,类层次范围(推荐)(见本文 9 中例二):

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

           2,失败了返回空指针;

        3,单次动态内存分配(见 本文9 中例三):

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

           2,失败了返回空指针;

          

    9,动态内存申请编程实验:

    复制代码
      1 #include <iostream>
      2 #include <new>
      3 #include <cstdlib>
      4 #include <exception>
      5 
      6 using namespace std;
      7 
      8 class Test
      9 {
     10     int m_value;
     11 public:
     12     Test()
     13     {
     14         cout << "Test()" << endl;
     15         
     16         m_value = 0;
     17     }
     18     
     19     ~Test()
     20     {
     21         cout << "~Test()" << endl;  
     22     }
     23     
     24     void* operator new (unsigned int size) throw()
     25     {
     26         cout << "operator new: " << size << endl;
     27         
     28         // return malloc(size);
     29         
     30         return NULL;  // 这里当没有加上 throw() 时,编译器显示: warning: 'operator new' must not return NULL unless it is declared 'throw()' (or -fcheck-new is in effect);
     31     }
     32     
     33     void operator delete (void* p)
     34     {
     35         cout << "operator delete: " << p << endl;
     36         
     37         free(p);
     38     }
     39     
     40     void* operator new[] (unsigned int size) throw()
     41     {
     42         cout << "operator new[]: " << size << endl;
     43         
     44         // return malloc(size);
     45         
     46         return NULL;
     47     }
     48     
     49     void operator delete[] (void* p)
     50     {
     51         cout << "operator delete[]: " << p << endl;
     52         
     53         free(p);
     54     }
     55 };
     56 
     57 void my_new_handler()
     58 {
     59     cout << "void my_new_handler()" << endl;
     60 }
     61 
     62 /* 证明 new_handler() 函数存在 */
     63 void ex_func_1()
     64 {
     65     /* 定义 func 变量,其类型为 new_handler 类型,C++ 中 new_handler 是一个预定义的函数指针,指向的函数类型是 void(*)(),调用 set_new_handler() 是将自定义的 my_new_handler() 处理函数设置进去,设置了自定义处理函数后,原来的处理函数就会作为返回值返回到 func,这点和上一节的不同,上一节是返回自定义的处理函数,这一节是返回原来的预定义处理函数; */
     66     new_handler func = set_new_handler(my_new_handler);  
     67     
     68     try
     69     {
     70         cout << "func = " << func << endl;
     71         
     72         if( func )  // 加上 if() 处理语句是因为默认的情况下面可能是没有处理函数的,此时 func 为空;
     73         {
     74             func();
     75         }
     76     }
     77     catch(const bad_alloc&)  // 想证明默认的 new_handler() 处理函数确实是要抛出 bad_alloc 异常;
     78     {
     79         cout << "catch(const bad_alloc&)" << endl;
     80     }
     81 }
     82 
     83 void ex_func_2()
     84 {
     85     Test* pt = new Test();
     86     
     87     cout << "pt = " << pt << endl;
     88     
     89     delete pt;
     90     
     91     pt = new Test[5];
     92     
     93     cout << "pt = " << pt << endl;
     94     
     95     delete[] pt; 
     96 }
     97 
     98 /* 如何在单次申请的时候,告诉编译器,不管结果是什么,都不要抛出异常,如果说申请失败了,直接返回空指针 */
     99 void ex_func_3()
    100 {
    101     /* 这个语句是 C++ 标准语法,只是之前没见过而已 */
    102     int* p = new(nothrow) int[10];  // 现在进行动态内存申请,但是不管结果有没有成功,都不要抛出异常,结果失败,直接返回空;
    103     
    104     // ... ...
    105     
    106     delete[] p; 
    107     
    108     /* 上面 new 的写法也可以写成下面的形式 */
    109     
    110     int bb[2] = {0}; 
    111     
    112     struct ST
    113     {
    114         int x;
    115         int y;
    116     };
    117     
    118     ST* pt = new(bb) ST();  // 把 ST 对象创建到 bb[2] 的栈空间中去,即在指定的位置创建一个对象,括号的作用是向编译器指明要在指定的地址上面创建一个对象出来;
    119     
    120     /* 对创建的对象赋值 */
    121     pt->x = 1;
    122     pt->y = 2;
    123     
    124     /* 这里的打印想证明上面创建的对象确实存在 bb[2] 空间当中的 */
    125     cout << bb[0] << endl;  // 打印 1
    126     cout << bb[1] << endl;  // 打印 2
    127     
    128     pt->~ST();  // 显示的调用析构函数,因为我们指定了穿件对象的空间,这时必须显示手动调用析构函数;
    129 }
    130 
    131 int main(int argc, char *argv[])
    132 {
    133     // ex_func_1();  
    134     // ex_func_2();
    135     // ex_func_3();
    136     
    137     return 0;
    138 }
    复制代码

        1,ex_func_1() 打印结果:

           1,g++ 编译器:

              func = 0;说明默认的情况下,g++ 编译器并没有一个全局的 new_handler() 处理函数;

           2,VS 2010 编译器:

              func = 00000000;说明默认的情况下,VS 2010 编译器并没有一个全局的 new_handler() 处理函数;

           3,BCC 编译器:

              func = 00401474

              catch(const bad_alloc&);说明 BCC 的实现当中,确实是有一个全局 new_handler() 函数,它在调用后确实抛出了一个 bad_alloc 异常;

        2,第一个打印结果说明不同的 C++ 编译器 new 的行为是有一点不一样的,具     体在 new 失败时;

        3,ex_func_2() 打印 new 的结果:

           1,g++ 编译器:

              operator new: 4

              Test()

              段错误;因为 new 的重载函数返回空,于是 C++ 编译器在空地址上面调用构造函数创建对象,并且在构造函数中做了一个赋值操作,这等价于对 0 地址处进行赋值,所以段错误;

           2,VS 2010 编译器:

              operator new: 4

              pt = 00000000;这款编译器中,如果 new 的结果返回为空,是不会调用构造函数的;

           3,BCC 编译器:

              operator new: 4

              pt = 00000000;这款编译器中,如果 new 的结果返回为空,是不会调用构造函数的;

        4,不管哪款编译器,申请动态内存失败,直接返回空指针,不要干其它多余操作,则对 new 和 new[] 的重载进行异常规格说明,说明无论如何都不会扔出异常;

           1,g++ 编译器:

              operator new: 4

              pt = 0;和 VS 2010 以及 BCC 编译器行为统一了;

        5,ex_func_2() 打印 new[] 的结果:

           1,g++ 编译器:

              operator new[]: 24

              pt = 0

           2,VS 2010 编译器:

              operator new[]: 24

              pt = 00000000

           3,BCC 编译器:

              operator new[]: 24

              pt = 00000000

              operator delete[]: 00000000

        6,ex_func_3() 打印结果:

           1,g++ 编译器:

              1

              2

           2,VS 2010 编译器:

              1

              2

           3,BCC 编译器:

              1

              2

        7,new 关键字可以在指定的空间上面创建对象,如果显示的指定穿件对象的内存,就要显示的手动调用析构函数;

        8,三款编译器的行为都一样了;

       

    10,实验结论:

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

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

           1,虽然现代的编译器在 new 失败的时候会抛出一个 bad_alloc 异常,但是这个异常不一定就是在默认的 new_handler() 函数里面抛出来的,只有(上述三款编译器) BCC 编译器设置了默认的全局的  new_handler() 函数;

           2,分析 VS 2010 new 如何实现,以观察 new_handler() 如何实现:

     

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

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

       

    11,小结:

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

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

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

           1,可能返回 NULL 值;

           2,可能抛出 bad_alloc 值;

    此文为作者学习唐佐林老师的学习笔记,仅为交流共享之用,由此带来的后果,与作者无关;转载请注明转载出处;难免有错,欢迎指正,联系方式qunchao24@sina.com。
  • 相关阅读:
    mysql学习笔记(七)
    Mysql学习笔记(八)
    vcpkg安装库
    nvm node版本管理工具
    Node.js版本管理工具nvm
    mysql安装后启动及navicat绿色版
    蚁景Web安全12期笔记
    python协程asyncio的个人理解
    EF或原生sql语句使用全文索引
    在拥挤和变化的世界中茁壮成长:C++ 2006–2020
  • 原文地址:https://www.cnblogs.com/sharecenter/p/14710106.html
Copyright © 2020-2023  润新知