《认清C++语言》之–内存管理

内存分配方式主要有三种:

1)从静态存储区域分配;内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在,例如全局变量、static变量;

2)在栈上创建;执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限;

3)从堆上分配,即动态内存分配。使用mallocnew申请,freedelete释放。

 

常见内存错误及其对策:

1)内存分配未成功,但使用了它。

常用的解决办法是:在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数入口处用assert(p!=NULL)进行检查;如果使用mallocnew申请的内存,应该用if(p==NULL)if(p!=NULL)来防错。

 

2)内存分配虽然成功,但是尚未初始化就引用了它。

犯此类错误的原因:一是没有初始化的好习惯;二是误以为内存的缺省初值全为零,导致引用初值出错(例如数组)。内存的缺省初值是什么并没有统一的标准,因此,无论用何种方式创建数组,都应该赋初值,即使是赋全零。

 

3)内存分配成功且已经初始化,但操作越过了内存的边界。

最常见的就是数组下标操作和for循环语句。

 

4)忘记了释放内存,造成内存泄露。

 

5)释放了内存却继续使用它。

5.1)函数的return语句写错了,注意不要返回指向“栈内存”的“指针”或“引用”,因为该内存在函数体结束时被自动销毁了;

5.2)使用freedelete释放了内存后,没有将指针设置为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,它返回的始终是同一个“只读”的内存块,那这个函数还有什么意义的,还不如设置一个常量算了。

 

freedelete

freedelete只是把指针所指向德内存给释放掉,但并没有把指针本身销毁掉!指针(如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)指针被freedelete之后,没有设置为NULL

 

malloc/freenew/delete的区别:

1mallocfreeC++/C语言的标准库函数,new/deleteC++的运算符。它们都可用于申请动态内存和释放内存;

2)对于非内部数据类型的对象而言,光用mallocfree无法满足动态对象的要求。因为mallocfree是库函数而不是运算符,故不在编译器控制权限之内,不能把执行构造函数和析构函数的任务强加于mallocfree上;

3)因此,我们C++语言需要newdelete;注意,newdelete不是库函数。

4)如果用free释放new创建的动态对象,那么该对象因无法执行析构函数而可能导致程序出错;如果用delete释放malloc申请的动态内存,理论上程序不会出错,但程序可读性很差。

 

内存耗尽:

如果申请动态内存时找不到足够大的内存块,mallocnew将返回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)为newmalloc设置异常处理函数。

 

原文链接: https://www.cnblogs.com/android-html5/archive/2010/05/26/2534038.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    《认清C++语言》之--内存管理

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/11186

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月7日 上午12:54
下一篇 2023年2月7日 上午12:55

相关推荐