C

来源:互联网 发布:怪兽汉化 知乎 编辑:程序博客网 时间:2024/05/17 07:07

参考及转载:

动态内存分配基础:http://blog.csdn.net/qq_29924041/article/details/54897204

空指针、野指针:http://www.cnblogs.com/fly1988happy/archive/2012/04/16/2452021.html

realloc:http://www.cnblogs.com/ladd/archive/2012/06/30/2571420.html

http://www.cnblogs.com/duger/archive/2013/08/29/3288974.html

总体概括


在《进程地址空间》:http://blog.csdn.net/kyang_823/article/details/78109448中,已经学会了进程地址空间及内存分配方式:堆段、栈段、数据段。动态分配即是在堆段中分配内存:在分配中完全由程序员进行申请多大内存和内存的free释放,动态内存分配的生存期完全由程序员决定,也是程序员唯一能自由分配的内存区,使用非常灵活,使用的好坏直接决定系统的性能和稳定,同时也很容易产生内存分配错误。(因为堆段内存为全局的,不会随函数调用结束而结束,因此如果分配使用后忘记释放,很容易造成内存溢出)

堆空间相对代码空间、栈空间、全局变量空间,它是没娘的孩子。因为系统除去这些保留的空间,剩下的都是自由空间,它的头上插了根草标,表示这些空间没有使用,谁都可以自由的使用。 
  堆空间唯一有用的时候,就是你的程序在执行mallocreallocnew等操作时,堆空间就像站街女等待你的挑选。不过,操作系统的眼光好一点,它会通过malloc等函数帮你选好合适的站街女,选好的站街女就是这几个函数的返回值。 
  站街女用完了之后,就用freedelete等操作撵走。

动态分配中使用的函数

1、malloc函数:

需要用到头文件malloc.h或者stdlib.h
原型:void *malloc(size_t size) 

  • 备注:void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据(比如是char还是int或者其他数据类型)

功能:允许从空闲内存池中分配一块连续内存,但不初始化

参数:size参数实际就是一个所需字节数的整数,如malloc(20)

返回:分配成功则返回一个指向该内存块的通用类型的指针,在使用时可根据需要做强制类型转换;分配失败则返回NULL(空指针)——>因此使用malloc需要判断指针非空 

如:int p = (int )malloc(n*sizeof(int));// 在空闲内存池中分配连续内存n*sizeof(int)个字节的堆内存空间(不初始化),返回 int*

Tips:申请malloc的时候尽量去给它进行一下初始化,防止后面出现一些不确定性的东西; 

malloc的生命周期:只要没有调用free这个函数,进程没有结束,那么此时,malloc 的生命周期就会一直存在在内存中;它是存放在堆空间中的,它不会因为你去函数调用的结束自动去释放,堆当中的内存是全局的

malloc、calloc和free的程序实例:

#include<malloc.h>#include<stdio.h>void out(int *p, int num) {for (int i = 0; i != num; i++)printf("%d  ", *(p + i));}int main() {int n = 10;int *p1 = (int *)malloc(n * sizeof(int));//malloc使用必须判断非空if (p1 == NULL)printf("malloc error\n");//分配成功out(p1, n); printf("\n");//分配一连串内存,但不初始化for (int i = 0; i != n; i++)//对malloc分配内存进行手动初始化为0*(p1 + i) = 0;out(p1, n); printf("\n");free(p1);//必须通过free释放堆区内存out(p1, n); printf("\n");//此时p1为野指针(声明但没初始化的指针),其指向内存混乱p1 = NULL;//野指针尽量置成NULL//out(p1, n);//调用NULL指针会出错int *p2 = (int *)calloc(n, sizeof(int));//函数原型不同,并且分配的内存会初始化为0//calloc使用必须判断非空if (p2 == NULL)printf("malloc error\n");//分配成功out(p2, n); printf("\n");for (int i = 0; i != n; i++)*(p2 + i) = i*i;out(p2, n); printf("\n");free(p2);//必须通过free释放堆区内存//out(p2, n);//此时p2为野指针p2 = NULL;out(p2, n);return(0);}

从程序结果中可以看出:

  1. malloc分配成功的内存连续,并且不初始化——>建议手动初始化为0;calloc分配的内存连续,且初始化为0;
  2. 使用野指针,其指向的地址任意,很容易产生错乱——>建议野指针赋NULL;
  3. 调用空指针会出错。

malloc其他的容易产生错误的地方:

  1. 大多数实现分配的存储空间都要比要求的稍微大一些,用来存储管理信息(配快的长度、指向下一个分配块的指针....),因此如果在申请内存之外的野指针(“超过一个已分配区的尾端”或者“已分配区起始位置之前”)进行写操作,则会改写另一块的管理记录信息。——这种类型的错误是灾难性的,并且不会很快暴露出来,很难发现。
  2. 同时,这种野指针还可能访问动态分配缓冲区前后进行写操作,破坏的就不仅仅是管理记录信息。在动态分配缓冲区前后的存储空间很可能是用于其他动态分配的对象,可能改写了这些对象,这种错误更难找到错误源头。
  3. 忘记free——>内存泄漏

2、free函数

原型:void free(void *ptr)

功能:释放ptr指向的存储空间。被释放的空间通常被送入可用存储区池,以后可在调用malloc、realloc以及calloc函数来再分配。

参数:内存分配成功的返回指针


free之后,申请内存的那个指针就会变成野指针(本文末尾有野指针说明),再次访问就会出现野指针错误; 

所以尽量在操作之后:将野指针置为NULL(本文末尾有空指针说明); 

注意:free函数与malloc/calloc函数是成对出现的,所以程序是不能进行多次free的,否则会崩溃的

忘记free——>该进程的存储空间会连续增加,造成“内存泄漏”,此时由于过度换页开销,会造成性能下降。


3、calloc函数:

需要用到的头文件stdlib.h(区别1)

原型:void *colloc(size_t num_elements,size_t element_size); 

功能:功能同malloc是一样的,但是对分配成功的内存初始化为0(区别2)

参数:num_elements是所需的元素的数量,element_size是每个元素的字节数(区别3)

返回:同malloc函数一样 

也需要与free函数成对使用


4、realloc函数:

需要用到的头文件(stdlib.h)

原型:void *realloc(void *ptr,size_t new_size); 

功能:在指针ptr指向的内存基础上动态扩大或者缩小内存 

参数:ptr是指向先前通过malloc,calloc或realloc函数后分配的内存块的指针,new_size是内存块的新尺寸,可能大于或者小于原有内存尺寸;(设之前内存块的大小为 n,如果 size < n,那么截取部分的内容不会发生变化,如果 size > n,那么新分配的内存不会被初始化。)

realloc在C语言中也被称为动态数组; 

realloc函数使用的注意点: 

1):当扩展内存的时候,不会对添加进内存块的字节进行初始化 

2):若不能调整内存则返回NULL,但原有内存中的数据是不会发生改变的 

3):若第一个参数为NULL那么功能 等同与malloc函数,若第二个参数为0,那么会释放调用内存块

1.realloc(NULL,10*size(int)) 等同malloc(10*sizeof(int));2.realloc(p,0); 等同于free

4):当缩小或者扩大内存时,一般不会对其进行移动,若无法扩大内存块,那么会在别处分配新的内存块,然后把旧内存块的数据复制到新块中,并将旧块删除释放内存;

综上可以看出两种容易混淆的情况:

  1. 返回NULL有两种情况:realloc(ptr,0); 和 分配失败(堆内存不够)。
  2. 分配成功返回new_ptr时,可能和ptr相同,也可能不相同。


5、realloc使用Tips

realloc操作错误:http://c.biancheng.net/cpp/html/2536.html


注意:realloc函数不只是实现了一个功能,因此很容易产生错误

错误1:

最后不要将返回结果再赋值给ptr,即ptr=realloc(ptr,new_size)是不建议使用的,因为如果内存分配失败,ptr会变为NULL,如果之前没有将ptr所在地址赋给其他值的话,会发生无法访问旧内存空间的情况,所以建议使用temp=realloc(ptr,new_size)
void *ptr = realloc(ptr, new_size);if (!ptr) {    // 错误处理}
这里就引出了一个内存泄露的问题,当realloc() 分配失败的时候,会返回NULL。但是参数中的 ptr 的内存是没有被释放的。如果直接将realloc()的返回值赋给ptr。那么当申请内存失败时,就会造成ptr原来指向的内存丢失,造成内存游离和泄露。

正确的处理应该是这样:
void *new_ptr = realloc(ptr, new_size);if (!new_ptr) {    // 错误处理。}ptr = new_ptr

错误2:

如果new_size小于old_size,只有new_size大小的数据会被保存,可能会发生数据丢失,慎重使用。
如果new_size大于old_size,可能会分配一块新的内存,这时候ptr指向的内存会被释放,ptr成为野指针,再访问的时候会发生错误


自己写的realloc用法:

1,ptr和new_ptr指向相同(num=3;new_num=6)

#include<malloc.h>#include<stdlib.h>#include<stdio.h>int main(){int num=3;int* ptr=(int*)malloc(num*sizeof(int));printf("--------------malloc---------------\n");if(ptr==NULL)printf("malloc error\n");//分配成功printf("num:%d\n",num);printf("malloc输出:");for(int i=0;i!=num;i++)printf("%d ",*(ptr+i));printf("\n");printf("ptr:%p\n",ptr);//malloc之后ptr指向地址printf("malloc初始化:");for(int i=0;i!=num;i++){*(ptr+i)=(i+10)*(i+10);printf("%d ",*(ptr+i));}printf("\n");int new_num=6;int* new_ptr=(int*)realloc(ptr,new_num*sizeof(int));printf("--------------realloc--------------\n");printf("new_num:%d\n",new_num);printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址if(new_ptr==NULL){if(new_num==0){printf("new_ptr==NULL ——> new_num==0\n");exit(0);}printf("realloc error\n");exit(0);}printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");free(new_ptr);printf("------------free(new_ptr)-------------\n");printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");/*free(ptr);printf("--------------free(ptr)---------------\n");printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");//free(new_ptr);free(ptr)之后再free(new_ptr)会发生错误*/ptr=NULL;new_ptr=NULL;//最后将两个野指针都置空return(0);}


可以看出num=3;new_num=6时,ptr和new_ptr相同,即指向同一个地址,因此可以使用ptr和new_ptr对此段内存进行操作。从ptr和new_ptr输出的数据来看,第3个数据之后都未初始化。

而在free(ptr)之后,释放了分配的堆空间,则ptr和new_ptr都指向了垃圾内存,即都变成了野指针,再使用ptr和new_ptr操作该段内存,则会超出预期结果,应该避免这种操作。直接将两个指针都置为NULL,并且此后不再使用空指针。

注意:同时能用free(new_ptr)释放内存,并且不能在free(ptr)之后再free(new_ptr),或者在free(new_ptr)之后再free(ptr)。


为了避免realloc错误1,采用此处的判断和错误处理策略(多了一部判断new_num==0),当new_num==0时,也进入错误处理,并且此时已经free了ptr的内存。同时返回NULL,因此new_ptr=NULL。


2,ptr和new_ptr不相同,即已经移动(num=3;new_num=1024)

#include<malloc.h>#include<stdlib.h>#include<stdio.h>int main(){int num=3;int* ptr=(int*)malloc(num*sizeof(int));printf("--------------malloc---------------\n");if(ptr==NULL)printf("malloc error\n");//分配成功printf("num:%d\n",num);printf("malloc输出:");for(int i=0;i!=num;i++)printf("%d ",*(ptr+i));printf("\n");printf("ptr:%p\n",ptr);//malloc之后ptr指向地址printf("malloc初始化:");for(int i=0;i!=num;i++){*(ptr+i)=(i+10)*(i+10);printf("%d ",*(ptr+i));}printf("\n");int new_num=1024;int* new_ptr=(int*)realloc(ptr,new_num*sizeof(int));printf("--------------realloc--------------\n");if(new_ptr==NULL){if(new_num==0){printf("new_ptr==NULL ——> new_num==0\n");exit(0);}printf("realloc error\n");exit(0);}printf("new_num:%d\n",new_num);printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");free(new_ptr);printf("------------free(new_ptr)-------------\n");printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");/*free(ptr);printf("--------------free(ptr)---------------\n");printf("ptr:%p  new_ptr:%p\n",ptr,new_ptr);//realloc之后ptr和new_ptr指向地址printf("ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(ptr+i));printf("\n");printf("new_ptr输出8个数据:");for(int i=0;i!=8;i++)printf("%d ",*(new_ptr+i));printf("\n");//free(new_ptr);free(ptr)之后再free(new_ptr)会发生错误*/ptr=NULL;new_ptr=NULL;//最后将两个野指针都置空return(0);}

realloc之后可以看出ptr和new_ptr的指向不同,并且通过ptr和new_ptr操作内存时,ptr的输出数据为任意数据,即ptr已经被free掉,new_ptr访问内存正确。

free(new_ptr)之后为什么还能通过new_ptr正确访问到数据,不懂???

同时若执行注释掉的代码,不执行free(new_ptr)段的代码,因为ptr之前已经被free,因此double free会产生错误。


空指针

空指针不指向任何实际的对象或者函数。反过来说,任何对象或者函数的地址都不可能是空指针
空指针是一个特殊的指针,因为这个指针不指向任何地方。这意味任何一个有效的指针如果和空指针进行相等的比较运算时,结果都是false。
在程序中,得到一个空指针最直接的方法就是运用预定义的NULL,这个值在多个头文件中都有定义。
如果要初始化一个空指针,我们可以这样, 
[cpp] view plain copy
  1. int *ip = NULL;  
校验一个指针是否为一个有效指针时,我们更倾向于使用这种方式
[cpp] view plain copy
  1. if(ip != NULL)  
 而不是
[cpp] view plain copy
  1. if(ip)  
为什么有人会用if(ip)这种方式校验一个指针非空,而且在C++中不会出现错误呢?而且现在很多人都会这样写。
原因是这样的,
[cpp] view plain copy
  1. // Define   NULL   pointer   value   
  2. #ifndef   NULL   
  3. #   ifdef   __cplusplus   
  4. #     define   NULL      0   
  5. #   else   
  6. #     define   NULL      ((void   *)0)   
  7. #   endif   
  8. #endif //   NULL   
在现在的C/C++中定义的NULL即为0,而C++中的true为≠0,所以此时的if(ip)和if(ip != NULL)是等效的。

空指针向了内存的什么地方(空指针的内部实现)?

标准并没有对空指针指向内存中的什么地方这一个问题作出规定,也就是说用哪个具体的地址值(0x0 地址还是某一特定地址)表示空指针取决于系统的实现。我们常见的空指针一般指向 0 地址,即空指针的内部用全 0 来表示(zero null pointer,零空指针);也有一些系统用一些特殊的地址值或者特殊的方式表示空指针(nonzero null pointer,非零空指针),具体请参见C FAQ。

在实际编程中不需要了解在我们的系统上空指针到底是一个 zero null pointer 还是 nonzero null pointer,我们只需要了解一个指针是否是空指针就可以了——编译器会自动实现其中的转换,为我们屏蔽其中的实现细节。注意:不要把空指针的内部表示等同于整数 0 的对象表示——如上所述,有时它们是不同的。


野指针

野指针不是空指针,是一个指向垃圾内存的指针

形成原因

1.指针变量没有被初始化

任何指针变量被刚创建时不会被自动初始化为NULL指针,指针的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
[cpp] view plain copy
  1. char* p = NULL;  
  2. char* str = (char*)malloc(1024);  

2.指针被free或者delete之后,没有设置为NULL,让人误以为这是一个合法指针

free和delete只是把指针所指向的内存给释放掉,但并没有把指针本身给清理掉。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下),在做如下指针校验的时候会逃过校验,此时的p不是一个NULL指针,也不指向一个合法的内存块,造成后面程序中指针操作的失败。
[cpp] view plain copy
  1. if(p != NULL)  

3.指针操作超越了变量的作用范围

(1)指针访问变量的越界操作:由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃
(2)指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针,如下
[cpp] view plain copy
  1. A *p; // A为一个自定义对象  
  2. {  
  3.     A a;  
  4.     p = &a; // 注意 a 的生命期 ,只在这个程序块中(花括号里面的两行),而不是整个test函数  
  5.  }  
  6.  p->Func();  // p是“野指针”  




原创粉丝点击