内存管理中的一些知识

2018年8月1日

20:44

1.内存分配方式

内存分配方式有三种:

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用 malloc 申请任意多少的内存,程序员自己负责在何时用 free 释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。  

2.常见的内存错误:

       1.内存分配未成功,却使用了它。

没有意识到内存分配会不成功。常用解决办法是, 在使用内存之前检查指针是否为 NULL。如果指针 是函数的参数,那么在函数的入口处用assert ( p ! =NULL) 进行检查。如果是用 malloc 来申请内存,应该用 i f ( p==NULL) 或 i f ( p ! =NULL) 进行防错处理。

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

犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。

内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

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

例如在使用数组时经常发生下标“多 1”或者“少 1”的操作。特别是在 for 循环语句中,循环次数很容易搞错,导致数组操作越界。

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

含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

动态内存的申请与释放必须配对,程序中 malloc free 的使用次数一定要相同, 否则肯定有错误。

      5.释放了内存却继续使用它。有三种情况:

1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

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

3)使用 free  释放了内存后,没有将指针设置为 NULL。导致产生“野指针”。

 

注意:

a.用 ma ll oc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。防止使用指针值为 NULL 的内存。

b.不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

c.避免数组或指针的下标越界,特别要当心发生“多  1”或者“少  1”操作。

d.动态内存的申请与释放必须配对,防止内存泄漏。

e.用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产生“野指针”。

 

3.指针与数组的对比

指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。指针可以随时指向任意类型的内存块,它的特征是“可变”,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

修改内容

char a [] = hello ;

a [ 0 ] = X;

char  * p = world;        / / 注意 p 指向常量字符串

p [ 0 ] = X;        //编译器不能发现该错误

 

字符数组 a 的容量是 6 个字符,其内容为 hello\0a 的内容可以改变,如 a[0]= ‘X’。指针 p 指向常量字符串“world”(位于静态存储区,内容为 world\0),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

 

内容复制与比较

不能对数组名进行直接复制与比较。下例中,若想把数组 a 的内容复制给数组 b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数 strcpy 进行复制。同理,比较 b 和 a 的内容是否相同,不能用 i f ( b==a) 来判断,应该用标准库函数 s t rcmp 进行比较。

语句 p = a 并不能把 a 的内容复制指针 p,而是把 a 的地址赋给了 p。要想复制 a 的内容,可以先用库函数 ma ll oc 为 p 申请一块容量为 s t r l en( a)+1 个字符的内存,再用 s t rcpy 进行字符串复制。同理,语句 i f ( p==a) 比较的不是内容而是地址,应该用库函数 s t rcmp 来比较。

// 数组

char a [] = " hello" ;

char b [ 10 ];

strcpy( b ,  a) ;/ / 不能用b = a ;

if ( strcmp( b ,  a) == 0 )// 不能用i f ( b == a)

// 指针

int len = strlen(a) ;

char * p =  ( char  * ) malloc( sizeof ( char) * ( len+1 )) ; strcpy( p, a) ;// 不要用 p = a ;

i f ( strcmp( p,  a) == 0 )// 不 要 用 if ( p == a)

 

4.指针参数是如何传递内存的

如果函数的参数是一个指针,不要指望用该指针去申请动态内存。下面的程序中Tes t 函数的语句 Get Memory( str ,  200 )并没有使  str 获得期望的内存,st r  依旧是 NULL

void Get Memory( char *p , int num)

{

p = ( char * ) malloc( sizeof ( char) * num) ;

}

void Test ( void)

{

char * str = NULL ;

Get Memory( str , 100 ) ;// s t r 仍然为 NULL strcpy( str ,  " hello" ) ;// 运行错误

}

 

毛病出在函数 Get Memory 中。编译器总是要为函数的每个参数制作临时副本,指针参数 p 的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p 的内容,就导致参数 p 的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p 申请了新的内存,只是把_p 所指的内存地址改变了,但是 p 丝毫未变。所以函数 Get Memory 并不能输出任何东西。事实上,每执行一次   Get Memory  就会泄露一块内存,因为没有用 free 释放内存。

5.野指针

“野指针”不是  NULL 指针,是指向“垃圾”内存的指针。人们一般不会错用  NULL

指针,因为用 if 语句很容易判断。但是“野指针”是很危险的,if 语句对它不起作用。

“野指针”的成因主要有两种:

1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为 NULL 指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存。例如

char * p = NULL ;

char * s t r = ( char * ) malloc( 100 ) ;

2)指针 p free 或者 delete 之后,没有置为 NULL,让人误以为 p 是个合法的指针。

 

6.malloc/free 的使用

函数 malloc 的原型如下:

void * malloc( size_t size) ;

用 malloc 申请一块长度为 l eng t h 的整数类型的内存,程序如下:

Int * p = ( int * ) malloc( sizeof ( int ) * length) ;

我们应当把注意力集中在两个要素上:“类型转换”和“s i zeof ”。

malloc 返回值的类型是 void  *所以在调用 malloc 时要显式地进行类型转换,将

void * 转换成所需要的指针类型。

malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。

 

函数 free 的原型如下:

void free( void * memblock ) ;

指针 p 的类型以及它所指的内存的容量事先都是知道的,语句 f ree( p) 能正确地释放内存。如果 p 是 NULL 指针, 那么 f ree 对 p 无论操作多少次都不会出问题。如果 p 不是 NULL 指针,那么 f ree 对 p 连续操作两次就会导致程序运行错误。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章