美文网首页
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内存划分

    一、内存分区:数据区+代码区+堆区+栈区1、数据区:分为静态数据区,全局变量区的存储是放在一块的。即static,...

  • 19. java虚拟机总结-JVM 内存管理 (三)

    JVM 内存区域划分 1.为什么进行内存区域划分? Java自动内存管理机制是它和C++的区别所在。C++是手动内...

  • OC内存划分

    OC的底层实现是通过C/C++来实现的,所以内存划分和C比较相似OC内存划分为5个区代码区:代码段是用来存放可执行...

  • C++运算符重载实现(加号、左移、递增、赋值、关系运算符、函数调

    先来复习一下C++的内存分区问题~ C++内存分区 C++程序的内存分区可划分为四大内存分区:堆、栈、全局/静态存...

  • 必须知道的C语言知识细节:C程序编译后内存到底是如何布局

    C语言程序编译以后,代码和数据都必须存放在内存中由CPU执行。理解C程序内存是如何划分布局,才能真正理解C语言中变...

  • JAVA 一维数组

    数组 JVM内存划分 A:内存划分 JVM对自己的内存划分为5个区域 * a: 寄存器:内存和CUP之间 * b:...

  • JVM知识点总结

    JVM运行时内存划分 http://www.jianshu.com/p/a7ca8e3e15c0 JVM内存溢出详...

  • JVM底层原理之运行时数据区

    JVM内存区域的划分 和C/C++开发不同,在从事JAVA的开发过程中,我们对内存区域的关注相对较轻,但是了解和掌...

  • 内存划分

    静态储存区 内存在程序编译的时候就已经分配好,这快内存在程序的整个运行期间都存在。它主要存在静态数据、全局数据和常...

  • JVM的内存区域划分

    学过C语言的朋友都知道C编译器在划分内存区域的时候经常将管理的区域划分为数据段和代码段,数据段包括堆、栈以及静态数...

网友评论

      本文标题:c内存划分

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