c语言指针初探 一 内存管理
来源:互联网 发布:微信跳转淘宝app 编辑:程序博客网 时间:2024/05/22 14:26
一 内存分配方式
内存分配有3种方式:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数的时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放(这就是不能返回栈内存中的数据的原因),栈内存分配运算内存于处理器的指令集中,效率很高,但是分配的容量有限。
(3)从堆上分配,也称作动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序猿自己负责何时free或delete掉,动态内存的生存周期由我们决定,使用灵活,问题也最多。
堆和栈究竟有什么区别?
好了,我们回到我们的主题:堆和栈究竟有什么区别?
主要的区别由以下几点:
1、管理方式不同;
2、空间大小不同;
3、能否产生碎片不同;
4、生长方向不同;
5、分配方式不同;
6、分配效率不同;
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改:
打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。
注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
从这里我们可以看到,堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家尽量用栈,而不是用堆。
虽然栈有如此众多的好处,但是由于和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。
无论是堆还是栈,都要防止越界现象的发生(除非你是故意使其越界),因为越界的结果要么是程序崩溃,要么是摧毁程序的堆、栈结构,产生以想不到的结果,就算是在你的程序运行过程中,没有发生上面的问题,你还是要小心,说不定什么时候就崩掉,那时候debug可是相当困难的:)
malloc 与free 是c++/c语言的标准库函数,new/delete是c++的运算符,它们都可以用于申请动态内存和释放内存。
c++/c程序中,指针和数组在很多地方都互相替换着用,让人产生错觉,两者是差不多的。
数组要么在静态存储区被创建(全局数组),要么在栈上被创建,数组名对应着(不是指向喔)一块内存。其地址在生命周期内保持不变。
指针可以随时指向任意类型的内存快。
1、修改常量内容
#include <iostream>using namespace std;int main(){ char a[]="xello"; a[0]='h'; cout<<a<<endl; char *p="xord"; //注意p指向常量字符串 p[0]='w'; //编译器不能发现该错误 cout<<p<<endl; return 0;}作为程序猿竟然没有让它输出hello word。
指针p指向常量字符串(位于静态存储区),常量字符串的内容是不可以修改的。所以此程序要运行错误。
2、试图用指针参数申请动态内存
#include <iostream>using namespace std;void GetMemory(char *p,int num){ p=(char *)malloc(sizeof(char)*num);}void Test(){ char *str=NULL; GetMemory(str, 100); //str任然为空 strcpy(str, "hello word"); //运行错误 cout<<str<<endl;}int main(){ Test(); return 0;}哎呀,hello word 竟然又出不来,我们来看看什么原因:
毛病出在GetMemory中,编译器总是要为函数的每个参数制作一个临时副本,指针参数p的副本就是)_p,编译器使 _p=p。如果函数体内的程序修改了_p的内容,就导致参数p的内容做相应的修改,这就是指针可以用做输出参数的原因。本程序中,_p申请了新的内存,只是把_p所值的内存地址改变了,但是p没有改变,所以helloWord没办法出来呀。
而且还会泄露一块内存。
如果非要用指针参数去申请内存,那么应该使用 指向指针的指针。
#include <iostream>using namespace std;void GetMemory(char **p,int num){ *p=(char*)malloc(sizeof(char)*num);}void Test(){ char *str=NULL; GetMemory(&str, 100); strcpy(str, "hello word"); cout<<str<<endl; free(str);}int main(){ Test(); return 0;}
终于对了。解释一下,所谓的指向指针的指针:char **p相当指向的是一个char *p2的一个指针,当传入参数时,让_p=p;即是让_p的内容等于p的内容,这里也就是**_p等于**p所指的内容,**p所指的内容就是**p指针所指的那个指针的内容,也就是一级指针(相当于*p),本题中值的是*str,所以就为str分配空间。(自己理解,若有不对,请指出,谢谢)
下面是以我自己理解写的程序:
#include <iostream>using namespace std;void GetMemory(char **p,int num){ *p=(char*)malloc(sizeof(char)*num);}void Test(){ char **str=NULL; char *str2=NULL; str=&str2; GetMemory(str, 100); strcpy(str2, "hello word"); cout<<str2<<endl; free(str2);}int main(){ Test(); return 0;}
还是会输出 hello word 哟
利用函数返回值来传递动态内存
#include <iostream>using namespace std;char *GetMemory(int num){ char *p=(char*)malloc(sizeof(char)*num); //堆内存 return p;}void Test(){ char *str=NULL; str=GetMemory(100); strcpy(str, "hello word"); cout<<str<<endl; free(str);}int main(){ Test(); return 0;}这个好理解吧,强调一下,不用用return返回指向 栈内存 的指针,因为该内存在函数结束时就自动牺牲了。
例如:
#include <iostream>using namespace std;char *GetString(){ char p[]="hello word"; //栈内存 return p; //编译器会警告你的}void Test(){ char *str=NULL; str=GetString(); cout<<str<<endl; //垃圾值}int main(){ Test(); return 0;}
#include <iostream>using namespace std;char *GetMemory(int num){ char *p="hello word"; return p;}void Test(){ char *str=NULL; str=GetMemory(100); cout<<str<<endl;}int main(){ Test(); return 0;}
哎呀,这个怎么没出错呀----------虽然程序没有运行错误,但是设计理念错误。字符串常量位于静态存储区,它在程序生命周期内恒定不变,不论什么时候调用那个函数,它返回的都是同一块只读内存。
3、不要使用或者释放已经释放的内存块
#include <iostream>using namespace std;int main(){ int *pN1=new int(8); int *pN2=pN1; delete pN2; cout<<*pN1<<endl; delete pN1; return 0;}delete pN2已经释放了申请的空间,所以对pN1的访问已经没有意义了,执行delete pN1相当于对已经释放的内存再次释放,此时系统会报错的,(释放空指针是没有问题的)。
4、成为了野指针(使用free或delete释放了内存后,没有将指针设置为NULL。导致产生“野指针”。)
#include <iostream>using namespace std;int main(){ char *p=(char*)malloc(sizeof(char)*100); strcpy(p, "hello"); free(p); //p所指的内存被释放,但是p所指的地址任然不变 if(p!=NULL) //没有防错的作用 { strcpy(p,"word"); //出错(有时可能会正常输出) cout<<p<<endl; } return 0;}
- c语言指针初探 一 内存管理
- c语言指针初探 一指针运算与多级指针
- C语言指针初探
- C语言指针初探
- c语言指针初探 一 指针与数组
- c语言指针初探 一指针与引用
- c语言指针初探 一 指针与引用(二)
- C语言指针初探 一 指针与函数
- C语言内存管理(数组,指针)
- C语言内存管理总结-野指针
- c语言内存管理、野指针、malloc
- c语言 内存初探
- 理解C指针:(一)指针初探
- C语言内存管理-字符数组与字符指针
- C语言内存管理-字符数组与字符指针
- C语言内存管理-字符数组与字符指针
- C语言 内存管理--指针的函数传递
- c语言中的内存管理(重点:动态内存申请释放,野指针,指针函数)
- vs2012 opencv2.4.9配置 出现msvcp120d.dll文件丢失问题
- Map
- C++设计模式之建造者模式(二)
- 基数排序
- html
- c语言指针初探 一 内存管理
- ADO.NET-查询和检索数据
- oracle 两个时间相减结果
- Redhat Linux 硬盘挂载方法!!!
- TCO14 2A L1: SixteenBricks, Greedy
- C++中 overload 、override、overwrite 之间的区别
- 吗,
- c# 分割字符串中的\r\n
- 基数排序