美文网首页
【笔记】C++的150个建议,第三章

【笔记】C++的150个建议,第三章

作者: NeilXu | 来源:发表于2019-12-25 22:51 被阅读0次

    目录
    第一部分 语法篇

    1. 第一章 从C继承而来
    2. 第二章 从C到C++
    3. 第三章 内存管理
    4. 第四章 类

    第三章 内存管理

    在VC中,栈空间未初始化的字符默认是-52,补码是0xCC。0xCCCC在GBK编码中就是"烫"。
    堆空间未初始化的字符默认是-51,两个-51在GBK编码中就是"屯"。

    建议27:区分内存分配的方式

    程序由4部分组成:

    1. 代码区,存放程序的执行代码。无法控制。
    2. 数据区,存放全局数据、常量、静态变量等。自由存储区、全局/静态存储区、常量存储区。
    3. 堆区,存放动态内存
    4. 栈区,存放程序中用到的局部数据。
      内存的5个区:
    5. 栈区,存储函数内部变量,函数执行完自动释放。栈内存分配运算内置于处理器的指令集,效率很高,缺点是分配的内存容量有限。
    6. 堆区,由new运算符分配的内存块,由delete释放。如果没释放,程序结束后操作系统自动回收。
    7. 自由存储区,由malloc等分配的内存块,用free释放。
    8. 全局/静态存储区,C的全局常量分为初始化和未初始化,C++里没区分。
    9. 常量存储区,存放常量,不允许修改。

    堆与栈的区别:

    1. 管理方式:栈由编译器自动管理;对由开发人员自己管理。
    2. 空间大小:堆内存可以占据整个内存空间;栈内存一般只有一定大小的空间。
    3. 碎片问题:频繁地new/delete会造成内存空间不连续;栈的数据是连续的。
    4. 生长方向:堆是向上增长;栈是向下增长。
    5. 分配方式:堆都是动态分配的。栈可能是静态或者动态分配的。栈的静态分配由编译器完成,动态分配由alloca函数完成,由编译器释放。
    6. 分配效率:堆内存的分配效率很低,还可能引发用户态和和心态的切换。栈内存的分配效率很高。
      因此要在合适的地方采用合适的内存分配方式。

    建议28:new/delete与new[]/delete[]必须配对使用

    内置类型没有构造、析构函数,所以使用deletedelete[]一样。

    建议29:区分new的三种形态

    1. new operator,最常见的new运算符。
      语言内建的,不能重载,也不能改变其行为。做如下三件事:
      (1)分配内存(2)调用构造函数(3)返回对象的指针
    2. operator new,申请原始内存。也就是new operator的第一步。与C库中的malloc函数很像。
    3. placement new,选择调用哪个构造函数。也就是new operator的第二步。

    三种形态的用法:

    1. 如果只是想在堆上建立对象,使用new operator。
    2. 如果只是想分配内存,使用operator new。
    3. 如果想在已经获得的内存里建立一个对象,使用placement new。
      operator newplacement new的示例:
    class A{
    public:
        A();
        A(int a);
        ~A();
        void* operator new(size_t size);
        void* operator new[](size_t size);
        void operator delete(void*ptr, size_t sz);
        void operator delete[](void*ptr, size_t sz);
    
    public:
        int a;
    };
    
    A::A():a(0)
    {
        cout << "construct A" << endl;
    }
    
    A::A(int a):a(a)
    {
        cout << "construct A" << endl;
    }
    A::~A() {
        cout << "destruct A" << endl;
    }
    
    void * A::operator new(size_t sz)
    {
        cout << "custom operator new for size " << sz << endl;
        return ::operator new(sz);
    }
    void * A::operator new[](size_t sz)
    {
        cout << "custom operator new for size " << sz << endl;
        return ::operator new(sz);
    }
    
    void A::operator delete(void* ptr, size_t sz)
    {
        std::cout << "custom operator delete for size " << sz << endl;
        ::operator delete(ptr);
    }
    void A::operator delete[](void* ptr, size_t sz)
    {
        std::cout << "custom operator delete for size " << sz << endl;
        ::operator delete(ptr);
    }
    
    
    int main(int argv, char* args[]) {
        cout << "1. operate new" << endl;
        A *ptrA = new A(123);     // operator new for size 4
        cout << "(*ptrA).a: " << (*ptrA).a << endl;
        cout << "sizeof ptrA: " << sizeof(ptrA) << endl;    // sizeof ptrA: 8
        delete ptrA;            // custom operator delete for size 4
        cout << endl;
    
        size_t len = 3;
        A *ptrB = new A[len];   // custom operator new for size 20
        cout << "sizeof ptrB: " << sizeof(ptrB) << endl;    // sizeof ptrB: 8
        cout << "sizeof A: " << sizeof(A) << endl;          // sizeof A: 4
        for (size_t ik = 0; ik < len; ++ik) {
            cout << "(*(ptrB+" << ik << ")).a: " << (*(ptrB + ik)).a << endl;
        }
        delete[] ptrB;          // custom operator delete for size 20, (20=8+4*3)
    
        cout << "\n2. placement new" << endl;
        void* s = operator new (sizeof(A));     // 分配内存
        A* ptrC = (A*) s;
        ::new(ptrC) A(2019);  // 调用一个参数的构造函数,ptrC->A::A(2019);
        cout << "(*ptrC).a: " << (*ptrC).a << endl;
        ::new(ptrC) A();         // 调用没有参数的构造函数,ptrC->A::A();
        cout << "(*ptrC).a: " << (*ptrC).a << endl;
        delete ptrC;
        return 0;
    }
    
    /* Output:
    1. operate new
    custom operator new for size 4
    construct A
    (*ptrA).a: 123
    sizeof ptrA: 8
    destruct A
    custom operator delete for size 4
    
    custom operator new for size 20
    construct A
    construct A
    construct A
    sizeof ptrB: 8
    sizeof A: 4
    (*(ptrB+0)).a: 0
    (*(ptrB+1)).a: 0
    (*(ptrB+2)).a: 0
    destruct A
    destruct A
    destruct A
    custom operator delete for size 20
    
    2. placement new
    construct A
    (*ptrC).a: 2019
    construct A
    (*ptrC).a: 0
    destruct A
    custom operator delete for size 4
    */
    

    建议30:new内存失败后的正确处理方式

    malloc函数在申请内存失败后会返回NULL。
    new在申请内存失败后会抛一个异常,不会执行if(ptr == NULL){}

    // new失败抛异常
    char *pStr = new string[SIZE];
    if(pStr == NULL){
        // 如果new失败,不会执行
        return false;
    }
    // new失败不抛异常
    char *pStr = new(std::nothrow) string[SIZE];
    if(pStr == NULL){
        // 如果new失败,不会执行
        return false;
    }
    

    如果想检查new是否成功,则应该捕捉异常:

    try{
        char *pStr = new string[SIZE];
    }catch(const bad_alloc& e){
        return -1;
    }
    

    一般来说,new失败可能是内存不足引起的,捕获这个异常没有意义,可以直接core dump。

    建议31:new内存失败后会调用new_handler函数

    建议32:检测内存泄露工具

    原理:截获对内存分配和内存释放的函数的调用。每当分配一块内存时,将其加入一个全局内存链中,每当释放一块内存时,将其删除。程序结束后,内存链中剩余的指针就是未被释放的。
    (1)Windows平台:MS C-Runtime Libraay、BoundsChecker、Insure++
    (2)Linux平台:Rational Purify、Valgrind

    建议33:operator new/operator delete的重载

    • 重载的operator new必须是类成员函数或全局函数,不可以是某一个命名空间之内的函数或全局静态函数。
    • 因为operator new是在类的具体对象被构建出来之前调用的,在调用operator new的时候this指针尚未诞生,因此重载的operator new必须是static的。同理,operator delete也是static的。
    • operator newoperator delete成对重载
    • delete会调用free函数

    建议34:智能指针

    RAII(Resource Acquisition In Initialization)
    智能指针实际是一个指针类,将指针通过构造函数传递给指针类的对象,当这个对象析构时,将指针delete
    不能使用临时的share_ptr对象。当将share_ptr对象作为函数参数时,可能会由于函数参数求值顺序,引起内存泄露。

    建议35:内存池

    当程序中需要频繁地申请大小相同的内存,用内存池技术能提高效率。
    内存池技术通过批量申请内存,降低内存申请次数,节省时间,且能减少内存碎片的产生。

    相关文章

      网友评论

          本文标题:【笔记】C++的150个建议,第三章

          本文链接:https://www.haomeiwen.com/subject/xxbhoctx.html