内存
一丶内存分配方式 C++中内存分为五个区,分别是栈(stack),堆(heap),自由存储区,全局/静态存储区(bss),常量存储区
栈:在执行函数时,函数内局部变量的存储单元,函数参数都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈内存分配运算内置于处理器指令集中,效率高,但分配内存容量有限。 堆:那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete,如果程序员没有释放掉,程序结束后,操作系统会自动回收(内存泄漏不是系统无法回收那片内存,而是你自己的应用程序无法使用那片内存。操作系统本身就有管理内存的职责,在进程结束后,操作系统会自动回收内存的) 自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过他是用free来结束自己的生命的 引申出来的问题: 很多编译器中new/delete都是以melloc和/free为基础实现的,那么就会引申出一个问题,自由存储区和堆相同么? 答案:堆是操作系统维护的一块内存,而自由存储时c++中通过new和delete动态分配和释放对象的抽象概念,堆和自由存储区并不等价 全局/静态存储区 全局变量和静态变量被分配到同一块内存中,在以前的c语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区 常量存储区 这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改
二丶堆和栈的区别
void func() {
int* p = new int[5];
}
在这条语句里,看到了new就想到分配了一块堆内存,而指针p分配了一块栈内存。这句话的意思是,在栈内存中存放了一个指向对内存的指针p
堆和栈的区别主要分为以下六点: 1.管理方式不同: 对于栈来说,是编译器自动管理的,不需要我们手动控制 对于堆来说,释放工作由程序员控制,容易产生内存泄漏 2.空间大小: 一般在32位系统下,堆内存可以达到4g空间 对于栈来说,一般是有一定的空间大小,vc下面栈的空间默认1M(可以通过编译器设置修改) 3.碎片问题: 对于堆来说,频繁的new/delete势必会造成内存空间不连续,从而造成大量碎片,使程序效率降低 对于栈来说,不会存在这个问题,因为栈使先进后出的队列,他们一一对应,永远不可能邮一个内存块从栈中间弹出,在他弹出之前,它上面的后进的栈内容已被弹出 4.生长方向: 对于堆来说,生长方向是向上的,也就是向着内存地址增加的方向 对于栈来说,它的生长方向是向下的,是向着内存地址减小的方向增长 5.分配方式不同: 堆都是动态分配的 栈有两种分配方式动态分配和静态分配,静态分配是编译器完成的,比如局部变量的分配,动态分配是由alloca完成的,栈的动态分配是由编译器进行释放,无需我们手动实现 6.分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。 堆则是C/C++函数库提供的,他的机制很是复杂,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间,就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分配到足够大小的内存,然后进行返回,显然,堆的效率比栈要低的多
虽然栈有众多好处,但是和堆比不是那么灵活,有时候分配大量的内存空间,还是堆好一些
三丶常见的内存错误及其对策 1.内存分配未成功,却使用了它 常见的解决办法是在使用内存之前检查指针是否为NULL。如果指针是函数的参数,那么在函数入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理
2.内存分配虽然成功,但是尚未初始化就引用它 犯这种错误主要是有两个起因:一是没有初始化的概念,二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么没有统一的标准,尽管有时候为零值,我们宁可信其无不可信其有。所以无论如何用哪种方式创建数组,都别忘了赋初值,即使是赋零值也不可省略,不要嫌麻烦。
3.内存分配成功已经初始化,但操作越过了内存的边界 例如在使用数组时经常发生下标”多1“或者”少1“的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组越界
4.忘记了释放内存,造成内存泄露 含有这种错误的函数每被调用一次就会丢失一块内存,刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。动态的申请和释放必须配对,程序中malloc与free的次数一定要相同,否则肯定有错误
5.释放了内存却继续使用它 有三种情况: 1)程序中的对象调用过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理混乱的局面 2)函数的return语句写错了,注意不要返回指向”栈内存“的指针”或者引用“,因为该内存在函数体结束时被自动销毁 3)使用free或delete释放了内存后,没有将指针设置为NULL,导致产生”野指针“
【规则1】在使用malloc或new申请内存之后,应该立即检查指针值是否为NULL,防止使用指针值作为NULL的内存 【规则2】不要忘记为数组或动态内存赋初值。防止将违背初始化的内存作为右值使用 【规则3】避免数组或指针下标越界,特别当心发生”多1“或者”少1“操作 【规则4】动态内存的申请和释放必须配对,防止内存泄露 【规则5】用free或delete释放内存之后,立即将指针设置为NULL,防止产生野指针
四丶野指针 ”野指针“不是NULL指针,是指向”垃圾“内存的指针。人们一般不会用错NULL指针,因为用if语句很容易判断,但是野指针是很危险的,if语句对它不起作用,”野指针“的成因主要有两种 1.指针变量没有初始化,任何指针变量被创建时不会自动成为NULL,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存
char *p = NULL;
char *str = (char *)malloc(100);
2.指针p被free或者delete之后,没有置为NULL,让人误以为p是一个合法的指针 3.指针操作超越了变量的作用域范围,这种情况让人防不胜防
class A {
public:
void Func(void) {
cout << "Func of class A" << endl;
}
};
void Test(void) {
A *p;
{
A a;
p = &a;
}
p->Func();
}
函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了野指针,但奇怪的是这个程序没有报错,可能和编译器有关
五丶内存耗尽怎么办 如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理”内存耗尽“问题 1.判断指针是否为NULL,如果是则马上用return语句终止本函数,例如:
void Func(void) {
A* a = new A;
if (a == NULL) {
return;
}
}
2.判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行,例如:
void Func(void) {
A* a = new A;
if (a == NULL) {
exit(1);
}
}
|