一、内存分区:数据区+代码区+堆区+栈区
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);
}