C++-内存管理(整理笔记)

 

C++ 内存管理 ( 整理笔记 )
C++ 内存管理详解
l          内存分配方式
C++ , 内存分成 5 个区 , 分别是 自由存储区全局 / 静态区 常量存储区 .
: 存放函数参数以及局部变量 , 在出作用域时 , 将自动被释放 . 栈内存分配运算内置于处理器的指令集中 , 效率 , 但分配的内存容量有限 .
:new 分配的内存块 ( 包括数组 , 类实例等 ), delete 手动释放 . 如果未释放 , 在整个程序结束后 ,OS 会帮你回收掉 .
自由存储区 : malloc 分配的内存块 , free 手动释放 . 它和堆有些相似 .
全局 / 静态区 : 全局变量 (global) 和静态变量 (static) 存于此处 .( 在以前的 C 语言中 , 全局变量又分为初始化的和未初始化的 ,C++ 不分 )
常量存储区 : 常量 (const) 存于此处 , 此存储区不可修改 .
l          堆与栈的区别
void f()
{
       int *p = new int[5];
}
上面一段代码就包含了堆与栈 . 指针 P 被分配在了栈中 , new 出来的东西则被分配在了堆中 , 此句可以解释为 在栈中存放了一个指向堆内存的指针 p ”.( 可否理解为 : 指针 p 的值是堆内存块的首地址 ?????? )
       主要区别 :
管理方式不同 : 栈是编译器自动管理的 , 堆需手动释放
空间大小不同 : 32 OS , 堆内存可达到 4GB 的的空间 , 而栈就小得可怜 .(VC6 , 栈默认大小是 1M , 当然 , 你可以修改它 )
能否产生碎片不同 : 对于栈来说 , 进栈 / 出栈都有着严格的顺序 ( 先进后出 ), 不会产生碎片 ; 而堆频繁的 new/delete, 会造成内存空间的不连续 , 容易产生碎片 .
生长方向不同 : 向下生长 , 降序分配内存地址 ; 向上生长 , 升序分配内在地址 .
分配方式不同 : 堆动态分配 , 静态分配 ; 栈分为静态分配和动态分配 , 比如局部变量的分配 , 就是动态分配 (alloca 函数 ), 函数参数的分配就是动态分配 ( 我想的 ).
分配效率不同 : 栈是系统提供的数据结构 , 计算机会在底层对栈提供支持 , 进栈 / 出栈都有专门的指令 , 这就决定了栈的效率比较高 . 堆则不然 , 它由 C/C++ 函数库提供 , 机制复杂 , 堆的效率要比栈低得多 .
可以看出 , 栈的效率要比堆高很多 , 所以 , 推荐大家尽量用栈 . 不过 , 虽然栈有如此多的好处 , 但远没有堆使用灵活 .
l          控制 C++ 的内存分配
其实 C++ 的内存管理容易而且安全 , 因为当一个对象消除时 , 它的析构函数能够安全释放所有分配的内存 . 在嵌入式系统中 , 内存的分配是一个常见问题 , 保守的使用内存分配是嵌入式环境中的第一原则 .
当你需使用 new/delete , 一个防止堆破碎的通用方法是从不同固定大小的内存池中分配不同类型的对象 (??????). 对每个类重载 new delete 就提供了这样的控制 .
class TestClass
{
       void *operator new(size_t size);
       void operator delete(void *p);
};
void *TestClass::operator new(size_t size)
{
       void *p = malloc(size);
       return p;
}
void TestClass::operator delete(void *p)
{
       free(p);
}
对象数组的分配又不同于单个对象的分配 , 所以你仍需再重载 new[] delete[] 操作符 . 但值得注意的是 , 对于 C++ 而言 , 分配对象数组的大小等于数组参数的大小再加上额外的对象数目的一些字节 , 所以要尽量避免使用对象数组 .
class TestClass
{
       void *operator new[](size_t size);
       void operator delete[](void *p);
};
void *TestClass::operator new[](size_t size)
{
       void *p = malloc(size);
       return p;
}
void TestClass::operator delete[](void *p)
{
       free(p);
}
void main()
{
       TestClass *p = new TestClass[10];
       delete[] p;
}
l          常见的内存错误及对策
²         内存分配未成功 , 却使用了它
解决办法 : 在使用之前检查指针是否为 NULL , 如果指针 p 是函数参数 , 那么在函数入口处 assert(p!=NULL). 如果是用 malloc new 申请的话 , 应该用 if(p==NULL) 进行防错处理 .
²         内存分配成功 , 但未初始化就使用它
    解决办法 : 不要嫌麻烦 , 记得初始化就行了 .
²         内存分配成功且已初始化 , 但操作越过了边界
    解决办法 : 此问题通常出现于循环之中 , 注意不要多 1 或少 1 就行 .
²         忘记释放内存
   
解决办法 : 含有这个错误的函数每调用一次就丢失一块内存 , 造成内存耗尽 . 记得 free delete 就行 .
²         释放了内存却继续使用它
有三种情况 :
程序中对象的关系过于复杂 , 难以搞清哪个对象是否已经释放了内存 .
函数中 return 写错 , 返回了指向栈中的指针或引用 .
free delete , 没有将指针设为 NULL, 产生 野指针 ”.
l          指针与数组
C++ , 指针和数组有着不少相似的地方 , 容易让我产生错觉 , 以为它们是等价的 , 其实不然 . 数组
       在静态存储区或是栈上被创建 , 数组名对应着 ( 而不是指向 ) 一块内存 , 其地址与容量在生命周期内保持
       不变 . 指针可以随时指向任意类型的内存块 , 远比数组灵活 , 但也危险 .
       char a[] = "hello";
a[0] = 'x';
char *p = "world";
p[0] = 'y'; // 试图修改常量字符串 , 编译器不能发现 , 执行会报错
杜绝 野指针
野指针 不是 NULL 指针 , 是指向 垃圾内存 的指针 . 它的缺省值是随机的 , 所以它会乱指一气 .
产生 野指针 的原因有 3 :
1 、指针变量没有被初始化 ;
2 、指针被 free/delete 后被没有设置为 NULL;
3 、指针操作超越了变量的作用域范围 . 如下例 ,p->fun() ,a 已经消失 .
class A
{
public:
       void fun()
       {}
};
void Test()
{
       A *p;
       {
              A a;
              p = &a; //a 的生命周期会在出作用域时结束
       }
       p->fun(); //p 此时是 " 野指针 "
}
l          malloc/free new/delete
有了 malloc/free 为何还需要 new/delete ? malloc/free 是标准库函数 , new/delete 是运算符 , 它们都可用于申请 / 释放动态内存 . 但对于非基本数据类型 ( 比如类对象 ) 而言 , malloc/free 无法 自动执行对象的构造 / 析构 函数 . new/delete 可以 .
       malloc
函数 malloc 的原型 :
void *malloc(size_t size);
       函数 malloc 的使用 :
              int *p = (int*)malloc(sizeof(int)*length);//length 前是乘号
       可见 , 在使用 malloc 时需要进行类型转换 . 而使用 sizeof 运算符也是良好的代码风格 .
       new
new 内置 sizeof, 所以用起来写法更简洁 . 注意 , 使用 new 创建对象数组 , 只能 使用对象的无参数构造函数 . Obj *o = new Obj[100];
l          内存耗尽怎么办 ?
解决办法 :
1 、判断指针是否为 NULL, 如果是立即返回
void fun()
{
    A *a = new A();
    if(a==NULL)
           return;
}
2 、判断指针是否为 NULL, 如果是立即终止
void fun()
{
    A *a = new A();
    if(a==NULL)
           exit(1);
}
提示 : 不要不忍心使用 exit(1), 否则会害死 OS. 所以推荐使用方法 2 . 不过搞笑的是 , 32 OS , 永远也不会内存耗尽 .

你可能感兴趣的:(C++,目录)