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最小值为4Bytecommit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。

  碎片问题:对于堆来讲,频繁的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、成为了野指针使用freedelete释放了内存后,没有将指针设置为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;}



0 0