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

2019-12-25  本文已影响0人  NeilXu

目录
第一部分 语法篇

  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的重载

建议34:智能指针

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

建议35:内存池

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

上一篇下一篇

猜你喜欢

热点阅读