美文网首页
c内存划分

c内存划分

作者: SuperDing | 来源:发表于2016-09-14 08:36 被阅读0次

    一、内存分区:数据区+代码区+堆区+栈区
    1、数据区:分为静态数据区,全局变量区的存储是放在一块的。
    即static,const修饰的变量、常量、全局变量都定义在此区,此区定义的变量未初始化,系统则会自动初始化为0
    初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放空间
    const:修饰的变量只能读不能修改他的值,表现的为一个常量的特性。他只能在初始化的时候给他赋值,其他任何时候都不能给他赋值。
    2、代码区:存放程序中的普通代码(存放函数体的二进制代码)。
    3、堆区(heap):一般由程序员手动申请以及释放, 若程序员不释放,程序结束时可能由OS回收 注意它与数据结构中的堆是两回事,分配方式类似于链表。
    有malloc()/calloc()/recalloc()/new()来分配内存,
    生命周期有free()/delete()决定何时释放分配的内存,此区使用灵活,空间相对较大。

    4、栈区(stack): 该区是有编译器自动分配的一块内存区域,存放函数的参数值、局部变量的值等,甚至函数的调用过程都是用栈来完成,其操作方式类似于数据结构中的栈
        效率高,有编译器自动分配和释放(函数开始的时候分配,结束的时候释放)
    5、文字常量区:常量字符串就是放在这里 程序结束后由系统释放空间
    

    下面的例子可以完全展示不同的变量所占的内存区域:

    //main.cpp

    int a = 0; 全局初始化区
     char *p1; 全局未初始化区

    void main()
     {
      int b; //栈中
      char s[] = "abc"; //栈中
      char *p2; //栈中
      char *p3 = "123456"; //123456\0在文字常量区,p3在栈上
      static int c =0; //全局(静态)初始化区

    //以下分配得到的10和20字节的区域就在堆区
      p1 = (char *)malloc(10);
      p2 = new char[20];//(char *)malloc(20);
      strcpy(p1, "123456"); //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方
     }
    注意:strcpy()函数用法:定义一个字符串char a[20],和一个字符串c[]="i am a teacher!";把c复制到a中就可以这样用:strcpy(a,c);


    char *p="zxcvbnm";
    (p+1)='d';//修改第二个字符 因为p和“zxcvbnm”没有保存在同一个区,不能修改。断错误
    char *q="zxcvbnm"; //两哥指针的地址相同
    指针p 和指针 q都保存在栈区,但字符串zxcvbnm保存在文字常量区,因为没有保存在一个区,通过指针获取某个字符是不对的
    并且因为在文字常量区已经开辟存放zxcvbnm的空间,在赋值zxcvbnm的时候不会再开辟空间,会把第一次开辟的地址赋予他们。


    ****手动在内存上分配一块空间:在堆上用malloc()函数分配一块连续空间
    函数原型:(void *)malloc(int size)
    头文件:malloc.h/stdlib.h
    解析:内存分配好了之后,malloc函数返回这块内存的首地址,默认为void *型。故需要强制转换成你需要的类型,括号里是您需要分配的内存的字节数
    例:char *p=(char *)malloc(100)
    注:所申请的内存必须小于堆上面某一整块内存,才能成功,故malloc函数请一块内存有可能是不成功的,所以我们需要判断一下他是否成功,
    用if(NULL!=p)来验证是否分配成功, 通常是成功的
    ****手动释放动态内存:
    free(p),同时,p=NULL;
    释放p所指向的那块内存,这里虽然把p指向的那块内存给释放掉了,但p的值仍没有变,这个地址还是存在的,只是不能再访问这个内存,所以再次使用它之前先要置NULL。


    二、栈(stack)和堆(heap)具体的区别

    1、在申请方式上
      栈(stack): 现在很多人都称之为堆栈,这个时候实际上还是指的栈它由编译器自动管理,无需我们手工控制
    例如,声明函数中的一个局部变量 int b 系统自动在栈中为b开辟空间;在调用一个函数时,系统自动的给函数的形参变量在栈中开辟空间
      堆(heap): 申请和释放由程序员控制,并指明大小容易产生memory leak

    在C中使用malloc函数

    如:p1 = (char *)malloc(10);

    在C++中用new运算符

    如:p2 = new char[20];//(char *)malloc(10);

    但是注意p1本身在全局区,而p2本身是在栈中的,只是它们指向的空间是在堆中

    2、申请后系统的响应上

    栈(stack):只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出
      堆(heap): 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,
    然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,
    这样,代码中的delete或free语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,
    系统会自动的将多余的那部分重新放入空闲链表中
      3、申请大小的限制

    栈(stack):在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。
    这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,
    在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow
    因此,能从栈获得的空间较小 例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)当然,我们可以修改:打开工程,依次操作菜单如下:
    Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit大的值,可能增加内存的开销和启动时间。

    堆(heap): 堆是向高地址扩展的数据结构,是不连续的内存区域(空闲部分用链表串联起来)。正是由于系统是用链表来存储空闲内存,自然是不连续的,
    而链表的遍历方向是由低地址向高地址,一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的由此可见,
    堆获得的空间比较灵活,也比较大。

    4、分配空间的效率上

    栈(stack):栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高
    但程序员无法对其进行控制
      堆(heap):是C/C++函数库提供的,由new或malloc分配的内存,一般速度比较慢,而且容易产生内存碎片显然,堆的效率比栈要低得多

    5、堆和栈中的存储内容

    栈(stack):在函数调用时,第一个进栈的是主函数中子函数调用后的下一条指令(子函数调用语句的下一条可执行语句)的地址,然后是子函数的各个形参在大多数的C编译器中,
    参数是由右往左入栈的,然后是子函数中的局部变量。
    注意:静态变量是不入栈的。 当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,
    也就是主函数中子函数调用完成的下一条指令,程序由该点继续运行
      堆(heap):一般是在堆的头部用一个字节存放堆的大小,堆中的具体内容有程序员安排

    6、存取效率的比较

    这个应该是显而易见的拿栈上的数组和堆上的数组来说:

    void main()
      {
      int arr[5]={1,2,3,4,5};
      int *arr1;
      arr1=new int[5];
      for (int j=0;j<=4;j++)
      {
      arr1[j]=j+6;

    }
      int a=arr[1];
      int b=arr1[1];
      }

    上面代码中,arr1(局部变量)是在栈中,但是指向的空间确在堆上,两者的存取效率,当然是arr高,因为arr[1]可以直接访问,
    但是访问arr1[1],首先要访问数组的起始地址arr1,然后才能访问到arr1[1]


    三、内存的释放:
    学过C语言的都知道,内存分配了用完之后是要释放的,都是到malloc和calloc函数以及free函数。那么分配了内存之后是不是真就free(pointer)这么简单呢?

     这里提及要注意的地方:参数pointer必须是调用malloc或calloc函数后返回的指针,而给free函数传递其它的值可能会造成死机或者结果是灾难性的。
                           重点是指针的值,而不是用来申请动态内存的指针本身。
    

    可以看下代码,

    假如先前有void * p =malloc(sizeof(double)*6);

    也有double * dp=(double )malloc(sizeof(double)6));

    那么此刻如果free(dp)就会出现不可预知的错误,free(p)是正确的,

    若又p=dp,(或者p=(void *)dp),然后free(p)也是正确的

    所谓灾难性:无非就是释放内存中出现把不该释放的东西给释放了,然后引起了一些问题。

    那么,怎么来验证free(dp)就是错误的呢?这也许是个内存泄露的问题,呵呵。

    可以试下这样一段代码:

    for(;;)

    {

    double * p=malloc(sizeof(double)*6);

    free(p);

    }

    然后,看看你的内存是否超支(不够)了?

    正确的说法:

    假如先前有double * p =malloc(sizeof(double)*6);

    那么此刻如果free(p+1)就会出现不可预知的错误,free(p)是正确的,

    原因是:分配内存函数对该地址值及对应的内存块有记忆,而p+1不在记忆列表里,所以free(p+1)会引发灾难性错误

    可以试下这样一段代码:

    for(;;)

    {

    double * p=malloc(sizeof(double)*6);

    free(p+1);

    }

    再看看realloc函数,它可以用来重新分配经m,c,r三者分配的内存。那么重新分配真的是给一块新的地址嘛?

      事实上并不是这样的,r有两个参数,一个是指针,引用之前分配的内存,重新分配的内存是在原来基础之上,大小则由第二个参数决定。
      也就是说,如果你家庭总收入6000元,总管(通常是母的)给儿子分配了1000元的零花钱,现在由于一些"不可抗力"因素,
      要重新分配money,那么,传递参数realloc(1000元的地址,newsize),newsize<=1000U。而本质上是将儿子手中的money根据newsize抽走一部分,然后剩下的会做一些处理。
    

    动态内存分配的一些原则:

    1、需要时分配,用完就释放,特别是堆上的(资源很有限)。

    2、避免分配大量小块内存,因为堆上内存的分配由于有系统开销,所以分配许多的小内存比分配几块大内存开销要大,而已不便于释放和管理。

    3、编程的时候始终把用户有限的内存放在心上,分配了就要考虑在哪里释放。

    4、循环中分配内存一定要小心翼翼

    5、释放内存之前,确保不会无意中覆盖堆上分配的内存地址,否则会出现内存泄露

    //手动分配一块内存给学生,输入学生的个人基本信息,并打印出来。

    include <stdio.h>

    include <malloc.h>

    struct student
    {
    int num;
    char *name;
    char sex;
    float score;
    };
    void main()
    {
    struct student *p=(struct student *)malloc(sizeof(struct student));
    if(NULL==p)
    {
    printf("is wrong");
    return ;
    }
    p->num=1;
    p->name="xxx";
    p->sex='m';
    p->score=90;
    printf("%d %s %c %.1f",p->num,p->name,p->sex,p->score);
    }

    //手动分配的内存只能通过指向他的指针来访问他,所以这个指针的值不要搞丢了

    struct node
    {
    int data;
    struct node *next;
    };

    struct node *head =(struct noode *)malloc(sizeof(struct node));

    struct node *p =(struct noode *)malloc(sizeof(struct node));

    head->next=p;

    p->next=q;

    q->next=NULL;

    创建一个链表链接

    include <stdio.h>

    include <malloc.h>

    struct node
    {
    int data;//数据域
    struct node *next;//指针域
    };

    struct node * create()
    {
    int n,i;
    printf("请输入要创建链表的节点数");
    scanf("%d",&n);
    getchar();
    //创建头节点
    struct node *head=(struct node *)malloc(sizeof(struct node));
    //新建p节点,首节点
    struct node *p =(struct node *)malloc(sizeof(struct node));
    printf("请输入数据:");
    scanf("%d",&p->data);
    getchar();
    //链接头节点和首节点
    head->next=p;
    for(i=1;i<n;i++)
    {
    //新建q节点
    struct node *q =(struct node *)malloc(sizeof(struct node));
    printf("请输入数据:");
    scanf("%d",&q->data);
    getchar();
    p->next=q;
    p=q;
    }
    p->next=NULL;
    return head;
    }
    void print(struct node *head)
    {
    struct node *p=head->next;
    while(p)
    {
    printf("%d\n",p->data);
    p=p->next;
    }
    }
    //头插
    struct node * T_insert(struct node *head)
    {
    struct node *q =(struct node *)malloc(sizeof(struct node));
    printf("input data:");
    scanf("%d",&q->data);
    getchar();
    q->next=head->next;//先连接后面的节点,再连接前面的节点
    head->next=q;
    return head;
    }

    void main()
    {
    struct node *head;
    head=create();
    print(head);
    head=T_insert(head);
    print(head);
    }

    相关文章

      网友评论

          本文标题:c内存划分

          本文链接:https://www.haomeiwen.com/subject/cjlvettx.html