Guru of the Week 条款10:内存管理(下篇)

 

GotW #10 Memory Management - Part II

著者:Herb Sutter     

翻译:kingofark

[声明]:本文内容取自www.gotw.ca网站上的Guru of the Week栏目,其著作权归原著者本人所有。译者kingofark在未经原著者本人同意的情况下翻译本文。本翻译内容仅供自学和参考用,请所有阅读过本文的人不要擅自转载、传播本翻译内容;下载本翻译内容的人请在阅读浏览后,立即删除其备份。译者kingofark对违反上述两条原则的人不负任何责任。特此声明。

Revision 1.0

Guru of the Week 条款10:内存管理(下篇)

 

难度:6 / 10

 

(你在考虑对某个类实现你自己特定的内存管理方案吗?甚或是想干脆替换掉C++的全局操作符newdelete?先试试下面这个问题再说吧!)

 

 

[问题]

 

下面的代码摘自某个程序,这个程序有一些类使用它们自己的内存管理方案。请尽可能的找出与内存有关的错误,并回答其注释中的附加题。

 

    //  为什么Bdelete还有第二个参数? 
  
  
//  为什么Ddelete却没有第二个参数?
  
  
//
  
  
    class B {
  
  
    public:
  
  
        virtual ~B();
  
  
        void operator delete  ( void*, size_t ) throw();
  
  
        void operator delete[]( void*, size_t ) throw();
  
  
        void f( void*, size_t ) throw();
  
  
    };
  
  
 
  
  
    class D : public B {
  
  
    public:
  
  
        void operator delete  ( void* ) throw();
  
  
        void operator delete[]( void* ) throw();
  
  
    };
  
  
 
  
  
    void f()
  
  
    {
  
  
        //  下面各个语句中,到底哪一个delete被调用了?为什么?
  
  
        //  调用时的参数是什么?
  
  
        //
  
  
        D* pd1 = new D;
  
  
        delete pd1;
  
  
 
  
  
        B* pb1 = new D;
  
  
        delete pb1;
  
  
 
  
  
        D* pd2 = new D[10];
  
  
        delete[] pd2;
  
  
 
  
  
        B* pb2 = new D[10];
  
  
        delete[] pb2;
  
  
 
  
  
        //  下面两个赋值语句合法吗?
  
  
        //
  
  
        B b;
  
  
        typedef void (B::*PMF)(void*, size_t);
  
  
        PMF p1 = &B::f;
  
  
        PMF p2 = &B::operator delete;
  
  
    }
  
  
 
  
  
 
  
  
    class X {
  
  
    public:
  
  
        void* operator new( size_t s, int )
  
  
                         throw( bad_alloc ) {
  
  
            return ::operator new( s );
  
  
        }
  
  
    };
  
  
 
  
  
 
  
  
    class SharedMemory {
  
  
    public:
  
  
        static void* Allocate( size_t s ) {
  
  
            return OsSpecificSharedMemAllocation( s );
  
  
        }
  
  
        static void  Deallocate( void* p, int i ) {
  
  
            OsSpecificSharedMemDeallocation( p, i );
  
  
        }
  
  
    };
  
  
 
  
  
    class Y {
  
  
    public:
  
  
        void* operator new( size_t s,
  
  
                            SharedMemory& m ) throw( bad_alloc ) {
  
  
            return m.Allocate( s );
  
  
        }
  
  
 
  
  
        void  operator delete( void* p,
  
  
                               SharedMemory& m,
  
  
                               int i ) throw() {
  
  
            m.Deallocate( p, i );
  
  
        }
  
  
    };
  
  
 
  
  
 
  
  
    void operator delete( void* p ) throw() {
  
  
        SharedMemory::Deallocate( p );
  
  
    }
  
  
 
  
  
    void operator delete( void* p,
  
  
                          std::nothrow_t& ) throw() {
  
  
        SharedMemory::Deallocate( p );
  
  
    }
  
  

 

 

[解答]

 

    //  为什么Bdelete还有第二个参数? 
  
  
//  为什么Ddelete却没有第二个参数?
  
  
//
  
  
    class B {
  
  
    public:
  
  
        virtual ~B();
  
  
        void operator delete  ( void*, size_t ) throw();
  
  
        void operator delete[]( void*, size_t ) throw();
  
  
        void f( void*, size_t ) throw();
  
  
    };
  
  
 
  
  
    class D : public B {
  
  
    public:
  
  
        void operator delete  ( void* ) throw();
  
  
        void operator delete[]( void* ) throw();
  
  
    };
  
  
 
  
  

附加题答案:这是出于个人喜好(preference)。两种都是正常的回收(译注:作者在这里用了“deallocation(去配)”一词,鄙人一律翻译为“回收”)函数,而不是placement deletes(译注:关于placement deleteplacement new,可以阅读Stanley Lippman的《Inside The C++ Object Model(深度探索C++对象模型)》一书中的6.2节:Placement Operator New的语意)。

 

另外,这两个类都提供了deletedelete[],却没有提供相对应的newnew[]。这是非常危险的。你可以试想,当更下层的派生类提供了它自己的newnew[]时会发生什么!

 

 

    void f()
  
  
    {
  
  
        //  下面各个语句中,到底哪一个delete被调用了?为什么?
  
  
        //  调用时的参数是什么?
  
  
        //
  
  
        D* pd1 = new D;
  
  
        delete pd1;
  
  
 
  
  

这里调用了D::operator delete(void*)

 

 

        B* pb1 = new D;
  
  
        delete pb1;
  
  
 
  
  

这里调用D::operator delete(void*)。因为B的析构函数(destructor)被声明成virtual,所以D的析构函数(destructor)理所当然会被正常调用,但同时这也意味着,即使B::operator delete()不声明成virtualD::operator delete()也必将被调用。

 

 

        D* pd2 = new D[10];
  
  
        delete[] pd2;
  
  
 
  
  

这里D::operator delete[](void*)被调用。

 

 

        B* pb2 = new D[10];
  
  
        delete[] pb2;
  
  
 
  
  

这里的行为是不可预料的。C++语言要求,传递给delete的指针之静态类型必须与其之动态类型一样。关于这个问题的进一步谈论,可以参看Scott Meyers在《Effective C++》或者《More Effective C++》中有关“Never Treat Arrays Polymorphically”的部分(译注:这里指的应该是《More Effective C++》中的条款3:绝对不要以多态方式处理数组)。

 

 

        //  下面两个赋值语句合法吗?
  
  
        //
  
  
        B b;
  
  
        typedef void (B::*PMF)(void*, size_t);
  
  
        PMF p1 = &B::f;
  
  
        PMF p2 = &B::operator delete;
  
  
    }
  
  
 
  
  

第一个赋值语句没问题,但是第二个是不合法的,因为                            void operator delete(void*,size_t)throw()”并不是B的成员函数,即使它被写在上面使其看上去很像是。这里有一个小伎俩需要弄清楚,即newdelete总是静态的,即使它们不被显式的声明为static。总是把它们声明为static是个很好的习惯,这可以让所有阅读你代码的程序员们明白无误的认识到这一点。

 

 

    class X {
  
  
    public:
  
  
        void* operator new( size_t s, int )
  
  
                         throw( bad_alloc ) {
  
  
            return ::operator new( s );
  
  
        }
  
  
    };
  
  
 
  
  

这会产生内存泄漏,因为没有相应的placement delete存在。下面的代码也是一样:

 

 

    class SharedMemory {
  
  
    public:
  
  
        static void* Allocate( size_t s ) {
  
  
            return OsSpecificSharedMemAllocation( s );
  
  
        }
  
  
        static void  Deallocate( void* p, int i ) {
  
  
            OsSpecificSharedMemDeallocation( p, i );
  
  
        }
  
  
    };
  
  
 
  
  
    class Y {
  
  
    public:
  
  
        void* operator new( size_t s,
  
  
                            SharedMemory& m ) throw( bad_alloc ) {
  
  
            return m.Allocate( s );
  
  
        }
  
  
 
  
  

这里也产生内存泄漏,因为没有对应的delete。如果在用这个函数分配的内存里放置对象的构造过程中抛出了异常,内存就不会被正常释放。例如:

 

        SharedMemory shared;
  
  
        ...
  
  
        new (shared) T; // if T::T() throws, memory is leaked
  
  

 

在这里,内存还无法被安全的删除,因为类中没有提供正常的operator delete。这意味着,基类或者派生类的operator delete(或者是那个全局的delete)将会试图处理这个回收操作(这几乎肯定会失败,除非你替换掉周围环境中所有类似的delete——这实在是一件繁琐和可怕的事情)。

 

 

        void  operator delete( void* p,
  
  
                               SharedMemory& m,
  
  
                               int i ) throw() {
  
  
            m.Deallocate( p, i );
  
  
        }
  
  
    };
  
  
 
  
  

这里的这个delete毫无用处,因为它从不会被调用。

 

 

    void operator delete( void* p ) throw() {
  
  
        SharedMemory::Deallocate( p );
  
  
    }
  
  
 
  
  

这是一个严重的错误,因为它将要删除那些被缺省的::operator new分配出来的内存,而不是被SharedMemory::Allocate()分配的内存。你顶多只能盼来一个迅速的core dump。真是见鬼!

 

 

    void operator delete( void* p,
  
  
                          std::nothrow_t& ) throw() {
  
  
        SharedMemory::Deallocate( p );
  
  
    }
  
  

 

这里也是一样,只是更为微妙。这里的delete只会在“new(nothrow)T”失败的时候才会被调用,因为T的构造函数(constructor)会带着一个异常来终止,并企图回收那些不是被SharedMemory::Allocate()分配的内存。这真是又邪恶又阴险!

 

 

好,如果你回答出了以上所有的问题,那你就肯定是走在成为内存管理机制专家的光明大道上了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).这个错误的原因是中断看门狗在CPU1上超时。中断看门狗是一种用于监视程序运行情况的机制,当程序在规定的时间内没有完成任务时,中断看门狗会触发重启系统。在这种情况下,可能是由于中断函数中的阻塞操作导致了中断看门狗的超时。根据错误信息显示,在中断函数`onTimer()`中使用了阻塞性函数`Serial.println()`,推测是由于中断阻塞导致没有及时喂狗而触发了重启。此外,错误信息中还提到了调试异常的原因是堆栈金丝雀(watchpoint)触发了,这可能是由于任务(task_name)的堆栈溢出导致的。为了解决这个问题,我们可以考虑以下解决方案: 1. 在中断函数中尽量避免使用阻塞性函数,特别是在低优先级的中断服务程序中 2. 确保中断服务程序尽快完成任务,以避免中断看门狗的超时 3. 检查任务的堆栈使用情况,确保没有堆栈溢出的问题 4. 调整中断看门狗的超时时间,使其能够适应程序的运行情况 5. 可以考虑使用RTOS的任务管理机制,以提高系统的稳定性和可靠性 通过以上的解决方案,我们可以尝试修复Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1)这个错误。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [[esp32] Guru Meditation 错误解析及解决方案](https://blog.csdn.net/weixin_42942530/article/details/103975237)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【踩坑日记】ESP32触发定时器中断后无限重启](https://blog.csdn.net/Beihai_Van/article/details/125793806)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kingofark

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值