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

目录
第一部分 语法篇

  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:内存池

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,820评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,648评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,324评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,714评论 1 297
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,724评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,328评论 1 310
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,897评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,804评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,345评论 1 318
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,431评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,561评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,238评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,928评论 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,417评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,528评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,983评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,573评论 2 359

推荐阅读更多精彩内容