[C/C++学习]之十五、内存管理(二)

2014-11-24 09:46:56 · 作者: · 浏览: 1
常量区, p3 在栈上。
static int c =0;// 全局(静态)初始化区
p1 = new char[10];
p2 = new char[20]; // 分配得来得和字节的区域就在堆区。
strcpy(p1, /"123456/"); //123456//0 放在常量区,编译器可能会将它与 p3 所指向的 /"123456/"
}


内存分配常见错误:
1、内存泄露
在堆上对内存进行申请的时候,一定要提高警觉程度。 动态内存的申请与释放必须配对,程序中malloc 与free 、new与delete的一定要成对。千万防止光申请不释放的代码出现。
如果发生这种错误,函数每被调用一次就丢失一块内存。
我们来看一下如何防止:
我们将指针放进对象的内部并且让对象管理指针是最简单的防止内存泄露的方法。应把new返回的指针存储在对象的成员变量里,当需要时该对象的析构函数便能够释放掉该指针。
这种方法的优点就是将指针完全交给对象去管理,不需要担心在操作指针时出现内存泄露等问题。 但是指针仅有初步创建和释放的语义,通过把指针放进对象里可以保证该析构函数的执行及正确释放分配的内存。
C/C++中,内存管理器不会自动回收不再使用的内存,如果忘记释放不再使用的内存,这些内存就允许被重用,此时就会造成内存泄露。

2、内存越界
何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。
读越界:读了不属于自己的数据。 如果读的内存地址是无效的就会崩溃。
写越界:也叫做 缓冲区溢出, 在写书的数据对别人来说是随机的,这样也会发生错误。
解决:遇到这种问题,首先你得找到哪里有内存访问越界,而一个比较麻烦得问题在于,出现错误得地方往往不是真正内存越界得地方。对于内存访问越界,往往需要进行仔细得代码走查、单步跟踪并观察变量以及在调试环境得帮助下对变量进行写入跟踪
[cpp]
char b[16]="abcd";
memset(b, 0,32);//越界

3、野指针
使用free或delete释放了内存后,没有将指针设置为NULL,也就是指向不可用内存区域的指针。
野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。野指针的成因主要有三种:
1、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
[cpp]
char *p =NULL;
char *str = (char *)malloc(100);
char *p =NULL; char *str = (char *)malloc(100);

2、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
3、指针操作超越了变量的作用范围。这种情况让人防不胜防。
[cpp]
class A
{
public void Func(void)
{
cout << "Func of class A" << endl;
}
};

void Test(void)
{
A *p;
{
A a;
p = &a; // 注意 a 的生命期
}
p->Func(); // p是“野指针”
}

在Test执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了野指针。


4、分配不成功,使用该内存
编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行。
如果使用malloc()或new()来申请内存,应该使用if(q == NULL)或if(q != NULL)进行防错处理。

5、分配成功,但未初始化
犯这种错误主要有两个起因:
一是没有初始化的观念;
二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。
内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

6、试图修改常量
在函数参数前加上const修饰符,只是给编译器做类型检查用的。编译器进制修改这样的变量,但是并不强制,我们完全可以使用强制类型转换来处理,一般不会出错。
然后,我们的全局常量和字符串使用强制类型转换来处理在运行时仍然会出错。因为他们是放在“rodata”里面的,而“rodata”内存页面时不允许修改的。

7、返回指向临时变量的指针
栈里面的变量是临时的,当前函数执行完成时,相关的临时变量和参数都被清除了。不允许把只想这些临时变量的指针返回给调用者,因为这样的指针指向的数据是随机的,会给程序带来严重后果。

8、对同一指针删除两次
[cpp]
A* a = new A();
A* b = a;
delete a;
delete b;
上面的代码就出现了两次删除同一个指针的问题了。
我们第一次delete会安全的释放掉*a,并且由a所指向的内存会被安全地返回到堆上。 而我们并没有返回该指针new的操作就把相同的指针第二次传递到delete()函数中,而且把之前*a中的对象传递给析构函数,然后把由a指向的内存第二次传递给该堆,这样的操作是灾难性的,因为这可能会破坏该堆及其自由内存表。
当我们不再使用指针并试图把该指针删除时,一定要慎重地考虑如何删除,而且一个指针只能删除一次。

9、p指向数组,调用delete p
当指针p指向数组时,不允许执行delete p操作。这是因为当p指向某种内置类型的数组时不能省略delete p[]中的[]。
在delete p中,底层解除分配原语是operator delete (void*),而delete [] p底层解除分配原语是operator delete[] (void*)。

10、内存耗尽
解决方法:
1、判断指针是否为NULL, 如果为NULL, 则使用exit(1)函数终止整个程序的运行。
2、判断指针是否为NULL, 如果为NULL, 则使用return 语句终止本函数。
3、为new和malloc()函数预设异常处理函数。
4、捕获new抛出的异常信息,并试图恢复。


new()/delete()函数的使用:
来看一下new的使用:
[cpp]
int *p = new int [length];
他相对于下面的malloc简单多了,这是因为new内置了sizeof,类型转换和类型安全检查功能,对于非内部数据类型而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式:
[cpp]
class A
{
public:
A(void);
A(int x);
...
};

void T(void)
{
A *a = new A;
A *b = new A(1);
...
delete a;
delete b;
}

但是当我们用new创建对象数组的时候,只能使