外传篇3 动态内存申请的结果

1. 必须知道的事实

(1)常见的动态内存分配代码

//C代码
int* p = (int*)malloc(10 * sizeof(int));

if(p != NULL){
    //...
}

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

(2)必须知道的事实

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

  ②new关键字申请失败时,则会根据编译器的不同,有的返回NULL值,而有的抛出std::bad_alloc异常。

2. new operator和operator new的区别

(1)new operator是C++内建的,无法改变其行为。主要做两件事第一,分配足够的内存,相当于调用operator new第二,调用对象构造函数

(2)重载operator new操作符

  ①只分配所要求的空间,不调用相关对象的构造函数。当无法满足所要求分配的空间时,如果有new_handler,则调用new_handler;否则如果没要求不抛出异常(以nothrow参数表达),则执行bad_alloc异常;否则返回0。(注意这个调用顺序!)

  ②重载时,返回类型必须声明为void*

  ③重载时,第一个参数类型必须为表达要求分配空间的大小(字节,类型size_t)

  ④重载时,可以带其它参数。

(3)全局operator new伪代码

extern void* operator new( size_t size )
{
  if( size == 0 )
  size = 1; // 这里保证像 new T[0] 这样得语句也是可行的
  
  void *last_alloc;
  while( !(last_alloc = malloc( size )) )
  {
     if( _new_handler )
         ( *_new_handler )(); //调用handler函数,失败时会抛std::bad_alloc
     else
         return 0;  //如果没有设置new_handler,则失败时返回NULL
  }
  return last_alloc; //成功时,返回对象地址。
}

3. new_handler()的定义和使用

//new_handler()的定义
void my_new_handler()
{
    cout << "Not enough memory" << endl;

    exit(1);
}

//设置new_handler
int main()
{
    set_new_handler(my_new_handler);

    //...

    return 0;
}

4. 跨编译器统一new的行为,提高代码移植性——解决方案

(1)全局范围不推荐

  ①重新定义new/delete的实现,不抛出任何异常(这样失败时,返回NULL)

  ②自定义new_handler()函数,不抛出任何异常(这样失败时,返回NULL)

(2)类层次范围重载new/delete不抛出任何异常(这样失败时,返回NULL)

(3)单次动态内存分配:使用nothrow参数,指明new不抛出异常(这样失败时,返回NULL)

【编程实验】动态内存申请

#include <iostream>
#include <cstdlib>
#include <new>  //for new_handler
#include <exception> //for bad_alloc

using namespace std;

class Test
{
    int m_value;
public:
    Test()
    {
        cout << "Test()" << endl;
        m_value = 0;
    }

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

    //重载new操作符
    void* operator new(unsigned int size) //throw()
    {
        cout << "operator new: " << size << endl;

        //return malloc(size);

        return NULL;
    }

    //重载delete操作符
    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 func1()
{
    new_handler func = set_new_handler(my_new_handler);

    try
    {
        cout << "func = " << func << endl;

        //如果存在默认的new_handler函数,则执行它。
        //默认下g++和VC++编译器没有设置这样一个函数。但bcc会存在这样的new_handler,并且会抛出bad_alloc异常
        if(func){
            func();
        }
    }catch(const bad_alloc&){
        cout << "catch(const bad_alloc&)" << endl;
    }
}

//统一编译器的new行为
void func2()
{
    //出现段错误的原因及解决方案:
    //(1)当调用new operator时,会找到重载的operator new,紧接着调用Test的构造函数。
    //如果重载的operator new函数未加异常规格说明,表示可以抛出任何类型的异常。g++编译器认为:未加异常说明的
    //operator new,调用失败时是会抛出异常的。如果没有抛出异常就说明分配内存是成功的,所以会继续调用对象
    //的构造函数。但由于我们重载的new返回的是NULL,这恰恰骗了g++编译器一把,所以会出现给0地址对象的成员
    //(m_value)赋值。因此,出现段错误。而bcc和VC++编译器不管抛不抛出异常,在调用构造函数前都会判断对象指
    //针是否为NULL,为NULL则不调用构造函数。
    //(2)为了统一编译器的行为,在operator new函数后加throw()异常规格说明,表示该函数不会抛出异常。这样
    //编译器在调用对象的构造函数前就会主动判断对象指针是否为NULL,如果为NULL则不调用构造函数。

    //创建对象
    Test* pt = new Test();
    cout << "pt = " << pt << endl;
    delete pt;

    //创建数组对象
    pt = new Test[5];
    cout << "pt = " << pt << endl;
    delete[] pt;
}

//使用单次动态内存分配
void func3()
{
    //使用nothrow指明new不抛出异常
    int* p = new(nothrow) int[10]; //nothrow表示不抛出异常,需要自行判断p是否为NULL

    //....

    delete[] p;

    //place new的其它用法
    int bb[2]={0};

    struct ST
    {
        int x;
        int y;
        ~ST()
        {
            cout << "(x, y) :";
            cout << "(" << x << ", " << y << ")" << endl;
        }
    };

    //在bb数组栈空间new一个对象
    ST* pt = new(bb) ST();
    pt->x = 1;
    pt->y = 2;

    cout << bb[0] << endl;  //输出:1
    cout << bb[1] << endl;    //输出:2

    pt->~ST(); //显式调用析构函数
    //delete pt;
}

int main()
{
    //func1();
    //func2();

    func3();
}

5. 小结

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

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

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

(4)不同编译器在动态内存分配上的实现细节不同。对于移植性要求较高的代码,需要考虑new的具体细节。

时间: 2025-01-10 02:02:07

外传篇3 动态内存申请的结果的相关文章

C++解析-外传篇(3):动态内存申请的结果

0.目录 1.动态内存申请一定成功吗? 2.new_handler() 函数 3.小结 1.动态内存申请一定成功吗? 问题: 动态内存申请一定成功吗? 常见的动态内存分配代码: C代码: C++代码: 必须知道的事实! malloc函数申请失败时返回NULL值 new关键字申请失败时(根据编译器的不同) 返回NULL值 抛出 std::bad_alloc 异常 问题: new语句中的异常是怎么抛出来的? new关键字在C++规范中的标准行为: 在堆空间申请足够大的内存 成功: 在获取的空间中调用

外传三 动态内存申请的结果

问题: 动态内存申请一定成功吗? 问题: new语句中的异常是怎么抛出来的? 一般我们会在new_handler函数中进行内存的整理,整理之后再次申请. 问题: 如何跨编译器统一new的行为,提高代码移植性? 全局定义new就是全局new操作符的重载. 最后两种方法是推荐的做法. 使用nothrow时,new失败了会返回空指针. 示例程序: 1 #include <iostream> 2 #include <new> 3 #include <cstdlib> 4 #in

C++函数中,两个自动释放内存的动态内存申请类

最近做一个事情,实现一个流程交互,其中主交互流程函数中,涉及较多的内存申请, 而健康的函数,都是在函数退出前将手动申请不再需要的内存释放掉, 使用很多方法,都避免不了较多的出错分支时,一堆的if free/delete,代码长而且不好管理 因此,利用C++对象离开作用域会自动调用析构函数的特点,在这儿实现了两个自动释放内存的动态内存申请类 第一个类,只管理内存,不并管理对象 #include <vector> class XAutoFreeMem { protected: std::vecto

C语言动态内存的申请和释放

什么是动态内存的申请和释放? 当程序运行到需要一个动态分配的变量时,必须向系统申请取得堆中的一块所需大小的存储空间,用于存储该变量.当不再使用该变量时,也就是它的生命结束时,要显式释放它所占用的存储空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源. 下面将介绍动态内存申请和释放的函数 1.malloc函数 在C语言中,使用malloc函数来申请内存.函数原型如下: #include<stdlib.h> void *malloc(size_t size); 参数size代表需要动

内存申请和释放及堆连续

glibc 内存申请和释放及堆连续检查 C语言有两种内存申请方式: 1.静态申请:当你声明全局或静态变量的时候,会用到静态申请内存.静态申请的内存有固定的空间大小.空间只在程序开始的时候申请一次,并且不再释放(除非程序结束). 2.自动申请:当你声明自动变量的时候会使用自动申请.函数参数.局部变量都属于自动变量.这些变量空间在程序执行致相关语句块申请,离开语句块时释放. 还有一种内存申请方式:动态内存申请.C语言变量并不支持动态内存申请,这一功能由库函数实现.C里面没有动态这个存储类型!! 当你

FreeRTOS 动态内存管理

本章节为大家讲解 FreeRTOS 动态内存管理,动态内存管理是 FreeRTOS 非常重要的一项功能,前面章节讲解的任务创建. 信号量. 消息队列. 事件标志组. 互斥信号量. 软件定时器组等需要的 RAM 空间都是通过动态内存管理从 FreeRTOSConfig.h 文件定义的 heap 空间中申请的. 动态内存管理介绍FreeRTOS 支持 5 种动态内存管理方案,分别通过文件 heap_1,heap_2,heap_3,heap_4 和 heap_5实现,这 5 个文件在 FreeRTOS

C语言学习笔记--动态内存分配

1. 动态内存分配的意义 (1)C 语言中的一切操作都是基于内存的. (2)变量和数组都是内存的别名. ①内存分配由编译器在编译期间决定 ②定义数组的时候必须指定数组长度 ③数组长度是在编译期就必须确定的 (3)但是程序运行的过程中,可能需要使用一些额外的内存空间 2. malloc 和 free 函数 (1)malloc 和 free 用于执行动态内存分配的释放 (2)malloc 所分配的是一块连续的内存 (3)malloc 以字节为单位,并且返回值不带任何的类型信息:void* mallo

keil c51的内部RAM(idata)动态内存管理程序(转)

源:keil c51的内部RAM(idata)动态内存管理程序 程序比较简单,但感觉比较有意思,个人认为有一定应用价值,希望大家有更好的思路和方法,互相促进. 程序的基本思路是:在CPU堆栈指针SP以上的RAM区域,通过把堆栈指针SP上移若干个字节,把空出的RAM区域供用户使用,当用户在使用完后又可以把该RAM区域释放. 头文件dmalloc51.h /* **********************************************************************

第38课 动态内存分配

动态内存分配的意义: malloc和free: 注意: 思考: malloc(0)将返回什么? 运行结果如下: 可以看到,返回了具体的地址. 我们所说的内存包括起始地址和长度.我们平时说内存的时候更多的是关注起始地址,而忽略了长度. 如果动态的综合这两部,我们使用malloc(0)返回了一个地址就不会奇怪了,因为这块内存的长度是0.这块内存我们可能无法正常使用,因为长度是0. 我们如果不停的malloc(0),会使系统的内存耗尽吗? 答案是会的,因为我们malloc的时候,得到的内存往往要比实际