《认清C++语言》之--内存管理
内存分配方式主要有三种:
1)从静态存储区域分配;内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量、static变量;
2)在栈上创建;执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限;
3)从堆上分配,即动态内存分配。使用malloc或new申请,free或delete释放。
常见内存错误及其对策:
1)内存分配未成功,但使用了它。
常用的解决办法是:在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数入口处用assert(p!=NULL)进行检查;如果使用malloc或new申请的内存,应该用if(p==NULL)或if(p!=NULL)来防错。
2)内存分配虽然成功,但是尚未初始化就引用了它。
犯此类错误的原因:一是没有初始化的好习惯;二是误以为内存的缺省初值全为零,导致引用初值出错(例如数组)。内存的缺省初值是什么并没有统一的标准,因此,无论用何种方式创建数组,都应该赋初值,即使是赋全零。
3)内存分配成功且已经初始化,但操作越过了内存的边界。
最常见的就是数组下标操作和for循环语句。
4)忘记了释放内存,造成内存泄露。
5)释放了内存却继续使用它。
5.1)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或“引用”,因为该内存在函数体结束时被自动销毁了;
5.2)使用free或delete释放了内存后,没有将指针设置为NULL,导致产生“野指针”。
指针和数组的对比:
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变;
指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。
如下代码:
char a[] = "hello";
a[0] = 'X'; //可以改变
cout<<a<<endl;
char *p = "world"; //注意p指向常量字符串
p[0] = 'X'; //编译器不会发现该错误
cout<<p<<endl;
指针p指向常量字符串"world"(位于静态存储区,内容为world/0),而常量字符串的内容是不可以修改的,该语句企图修改常量字符串的内容而导致运行错误(编译没错)。
如下代码:
char a[] = "hello world";
char *p = a;
cout<<sizeof(a)<<endl; //12字节
cout<<sizeof(p)<<endl; //4字节
void Func(char a[100])
{
cout<<sizeof(a)<<endl; //4字节,而不是100字节
}
用运算符sizeof可以计算出数组的容量(字节数)。sizeof(a)的值是12(注意别忘了’/0’)。sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。
注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
使用指针参数传递内存:
如果函数的参数是一个指针,那么不要指望该指针去申请动态内存。
如下代码:
void GetMemory(char *p, int num)
{
p = (char*)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); //str仍然为NULL
strcpy(str, "hello"); //运行错误
}
问题出在GetMemory中,编译器总是要为函数的每个参数制作临时副本,指针参数p的副本设为_p,编译器是_p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改,这也是指针可以用作输出参数的原因。但是在本例中,_p申请了新的内存,只是把_p所指的内存地址改变了,但是p却丝毫没变。因此,函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没用free释放申请的内存。
如果非要使用指针参数去申请内存时,应该改用“指向指针的指针“,如下代码:
void GetMemory(char **p, int num)
{
*p = (char*)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100); //注意是&str,而不是str
strcpy(str, "hello");
cout<<str<<endl;
free(str);
}
当然,我们也可以换一种方式,可以用函数返回值来传递动态内存(注意,就是堆上分配的),代码如下:
char* GetMemory(int num)
{
char *p = (char*)malloc(sizeof(char) * num);
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory(100);
strcpy(str, "hello");
cout<<str<<endl;
free(str);
}
用函数返回值来传递动态内存这种方法虽然好用,但常常有人把return语句用错啦!这里需要强调的是:用return语句返回的是在堆上分配的动态内存,而不是返回指向“栈内存“的指针,因为”栈内存”在函数结束时就自动消亡了!
如下代码:
char* GetString(void)
{
char p[] = "hello world";
return p; //编译器将提出警告
}
void Test(void)
{
char *str = NULL;
str = GetString(); //str的内容不再是NULL,也不是”hello world”,而是垃圾
cout<<str<<endl;
}
可以将上面代码改为:
char* GetString(void)
{
char *p = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetString();
cout<<str<<endl;
}
上面代码运行虽然不会出错,但函数GetString的设计理念是错误的!因为GetString内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内是恒定的,无论什么时候调用GetString,它返回的始终是同一个“只读”的内存块,那这个函数还有什么意义的,还不如设置一个常量算了。
free和delete:
free和delete只是把指针所指向德内存给释放掉,但并没有把指针本身销毁掉!指针(如p)被free以后它的地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p变成了“野指针”,如果此时不把p设置为NULL,会让人误以为p是个合法的指针。
如下代码:
char *p = (char*)malloc(100);
strcpy(p, "hello");
free(p); //p所指的内存被释放了,但是p所指的地址仍然不变
....
if(p != NULL) //没有起到防错的作用
{
strcpy(p, "world"); //出错
}
动态内存不会自动释放:
函数体内的局部变量在函数结束时自动消亡,但是如下代码:
void Func(void)
{
char *p = (char*)malloc(100); //动态内存不会自动释放
}
指针有一些似是而非的特征:
1)指针消亡了,并不表示它所指的内存会被自动释放;
2)内存被释放了,并不表示指针会消亡或者成为NULL指针。
杜绝野指针:
“野指针”不是NULL指针,而是指向“垃圾”内存的指针。
“野指针”的成因主要有两种:
1)指针变量没有初始化,任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应该被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
char *p = NULL;
char *str = (char*)malloc(100);
2)指针被free或delete之后,没有设置为NULL。
malloc/free和new/delete的区别:
1)malloc和free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存;
2)对于非内部数据类型的对象而言,光用malloc和free无法满足动态对象的要求。因为malloc和free是库函数而不是运算符,故不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于malloc和free上;
3)因此,我们C++语言需要new和delete;注意,new和delete不是库函数。
4)如果用free释放new创建的动态对象,那么该对象因无法执行析构函数而可能导致程序出错;如果用delete释放malloc申请的动态内存,理论上程序不会出错,但程序可读性很差。
内存耗尽:
如果申请动态内存时找不到足够大的内存块,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)
{
cout<<"内存耗尽"<<endl;
exit(1);
}
.....
}
3)为new和malloc设置异常处理函数。
posted on 2010-05-26 20:26 android开发实例 阅读(251) 评论(0) 编辑 收藏 举报