内存管理
计算机中的内存是分区来管理的,程序和程序之间的内存是独立的,不能互相访问,比如QQ和浏览器分别所占的内存区域是不能相互访问的。
程序内存结构
内存分配方式
静态分配:代码段和数据段在编译器编译的时候分配空间
动态分配:栈区由系统分配,堆区由程序员调用Malloc等函数进行分配
堆区内存管理函数
malloc free
#include <stdlib.h>
函数原型:
void *malloc(size_t size)
//分配变量
int *p1=(int*)malloc(sizeof(int)/sizeof(char));
if(NULL==p1)
{
printf("malloc failed !!!\n");
}
free(p1);
//分配一维数组
int *p2=(int*)malloc(sizeof(int)/sizeof(char)*10);
if(NULL==p2)
{
printf("malloc failed !!!\n");
}
free(p2);
//分配二维数组
int (*p3)[10]=(int*[10])malloc(sizeof(int)/sizeof(char)*5*10);
if(NULL==p3)
{
printf("malloc failed !!!\n");
}
free(p3);
//分配不连续的二维数组 =====>哈希
int* *p4=(int**)malloc(sizeof(int*)/sizeof(char)*3);
if(NULL==p4)
{
printf("malloc failed !!!\n");
}
free(p4);
int i;
for(i=0;i<3;i++)
{
p4[i]=(int*)malloc(sizeof(int)/sizeof(char)*4);
if(NULL==p4[i])
{
printf("malloc failed !!!\n");
}
}
for(i=0;i<3;i++)
{
free(p[i]);
}
free(p4);
realloc
void *realloc(void *ptr, size_t size)
realloc()函数用来从堆上分配内存,当需要扩大一块内存空间时,realloc()试图直接从堆上当前内存段后面的字节中获得更多的内存空间,如果能够满足,则返回原指针;如果当前内存段后面的空闲字节不够,那么就使用堆上第一个能够满足这一要求的内存块,将目前的数据复制到新的位置,而将原来的数据块释放掉。如果内存不足,重新申请空间失败,则返回NULL
int *ptr1=(int * )realloc(ptr, new_amount);
if(NULL == ptr1) return;
ptr = ptr1;
注: 用新的指针接收realloc返回值,分配成功,再将其赋给原指针
calloc
void *calloc(size_t nmemb, size_t size)
{
void *p;
size_t total;
total = nmemb * size;
p = malloc(total);
if (p != NULL)
memset(p, ‘\0’, total);
return p;
}
calloc是malloc函数的简单包装,它的主要优点是把动态分配的内存进行初始化,全部清零。其 操作及语法类似malloc()函数。
memset
原型:void *memset(void *s, int c, size_t n);
将s中当前位置后面的n个字节 用 c 替换并返回 s
#include <stdio.h>
#include <string.h>
int main()
{
char a[]="ahfkhasdf";
char*p=a;
printf("%s\n",a);
//字符串清零
p=(char*)memset(p,0,sizeof(a)/sizeof(a[0]));
printf("%s\n",a);
return 0;
}
堆栈的区别
1、 | 管理方式不同 | 栈编译器自动管理,无需程序员手工控制;而堆空间的申请释放工作由程序员控制,容易产生内存泄漏。 |
---|---|---|
2、 | 空间大小不同 | 栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,当申请的空间超过栈的剩余空间时,将提示溢出。因此,用户能从栈获得的空间较小。 堆是向高地址扩展的数据结构,是不连续的内存区域。因为系统是用链表来存储空闲内存地址的,且链表的遍历方向是由低地址向高地址。由此可见,堆获得的空间较灵活,也较大。栈中元素都是一一对应的,不会存在一个内存块从栈中间弹出的情况。 |
3、 | 是否产生碎片 | 对于堆来讲,频繁的malloc/free(new/delete)势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低(虽然程序在退出后操作系统会对内存进行回收管理)。对于栈来讲,则不会存在这个问题。 |
4、 | 增长方向不同 | 堆的增长方向是向上的,即向着内存地址增加的方向;栈的增长方向是向下的,即向着内存地址减小的方向 |
5、 | 分配方式不同 | 堆都是程序中由malloc()函数动态申请分配并由free()函数释放的;栈的分配和释放是由编译器完成的,栈的动态分配由alloca()函数完成,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行申请和释放的,无需手工实现。 |
6、 | 分配效率不同 | 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行。堆则是C函数库提供的,它的机制很复杂,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大的空间,如果没有足够大的空间(可能是由于内存碎片太多),就有需要操作系统来重新整理内存空间,这样就有机会分到足够大小的内存,然后返回。显然,堆的效率比栈要低得多。 |
常见的内存的错误(Segmentation fault )
| 1、 | 指针没有指向一块合法的内存 | 定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。浅显的例子就不举了,这里举几个比较隐蔽的例子。
struct student
{
char * name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name,“Jimy”);
stu.score = 99;
return0;
}
很多初学者犯了这个错误还不知道是怎么回事。这里定义了结构体变量 stu,但是他没想到这个结构体内部char*name 这成员在定义结构体变量 stu 时,只是给 name 这个指针变量本身分配了 4 个字节。 name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用 strcpy 函数时,会将字符串"Jimy"往乱码所指的内存上拷贝,而这块内存 name 指针根本就无权访问,导致出错。解决的办法是为 name 指针 malloc 一块空间。 |
---|
2、 |
char *p1 = “abcdefg”;
char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
strcpy(p2,p1); |
| 3、 | 内存分配成功但未初始化 | |
| 4、 | 内存越界 | 内存分配成功,且已经初始化,但是操作越过了内存的边界。这种错误经常是由于操作数组或指针时出现“多1”或“少 1”。 |
| 5、 | 内存泄露 | 会产生泄漏的内存就是堆上的内存(这里不讨论资源或句柄等泄漏情况),也就是说由malloc 系列函数或new 操作符分配的内存。如果用完之后没有及时 free 或 delete,这块内存就无法释放,直到整个程序终止。 |
| 6、 | 内存释放之后 | 释放完块内存之后,没有把指针置 NULL,这个指针就成为了“野指针”,也有书叫“悬垂指针”。这是很危险的,而且也是经常出错的地方。所以一定要记住一条: |
| 7、 | 内存已经释放了,但是继续通过指针来使用 | 第一种:就是上面所说的, free( p)之后,继续通过 p 指针来访问内存。解决的办法就是给 p 置NULL。
第二种:函数返回栈内存。这是初学者最容易犯的错误。比如在函数内部定义了一个数组,却用 return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。(解决:将局部变量转为静态变量,这样,变量就存入到数据段中,而不是栈上)
第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法是重新设计程序,改善对象之间的调用关系。 |
| 8、 | 栈溢出 | 栈上空间比较小,堆区空间很大
自测:
windows 2m
linux 8m
尽量不要在栈上(局部变量)定义很大的内存结构,例如数组
通过malloc等函数进行堆上空间分配 |