目录
C语言内存管理总结
文章目录
前言
一、内存管理简介以及常见的内存使用错误
二、内存分类
1.栈区(stack)
2.全局区
3.常量区
4.堆区(heap)
三、malloc(),calloc(),realloc()函数
1.malloc:
2.calloc:
3.realloc:
四、strcpy(),memcpy(),memmove()函数
1.strcpy:
2.memcpy:
3.memmove:
4.memset:
五、栈区(stack)
六、堆区(heap)
申请方式:
申请后系统的响应:
申请大小的限制:
申请效率的比较:
堆和栈中的存储内容:
七、全局区(静态区)
八、常量区
九、常见错误之内存越界与内存泄露(Memory Leak)
1.内存越界
十、内存碎片解决
总结
虽然我们现在的计算机系统的内存已经在“宏大”的方向发展,但学会内存管理,并不为一件坏事,相反,这种“优良传统”在各类场景下会给我们带来益处,在计算机系统中,特别是嵌入式系统中,内存资源是十分有限的。尤其是对于移动端的开发者来说,硬件资源的限制使得其在程序的设计中首先要考虑的问题就是,如何去合理的分配那“一丢丢”的内存资源。
日常中我们可能遇到的错误:
而内存管理不当会导致些什么了?
1.内存泄露
2.越界访问
3.内存出错
C语言为用户提供了很多相应的AP接口,如malloc(),realloc(),calloc(),free(),new()等函数,需要开发者进行手动管理,许多高级语言都有内存自动回收的机制,比如python,很遗憾,C语言没有,也便需要我们自行解决内存释放的问题
栈(stack)又名堆栈,它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。
静态区存放程序中所有的全局变量和静态变量,程序结束后有系统释放。
常量字符串就是放在这里的。 程序结束后由系统释放。
一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
这三个函数它们都能分配堆内存,并且返回内存的首地址,如果失败就返回NULL
函数原形:
void *malloc(
size_t size
);
该函数会在堆上分配一个size(byte)大小的内存,不对内存进行初始化,所以其内存值是随机的
示例:
#include
#include
int main()
{
int *ptr;
ptr = (int *)malloc(sizeof(int));
if (ptr == NULL)
{
printf("memory allocation error!!");
exit(1);
}
printf("请输入一个整数 :");
scanf("%d", ptr);
printf("你输入的整数是 :%d\n",*ptr);
free(ptr);
return 0;
}
结果:
函数原形:
void *calloc(
size_t number,
size_t size
);
该函数与malloc函数几乎一致,唯一不同是它将分配count个size大小的内存空间并自动初始化该内存空间为0。
函数原型
void *realloc(
void *memblock,
size_t size
);
该函数可将ptr内存大小动态变化,增大或变小,但在某种程度,这样的代码段运行效率会变低,不太建议使用这个函数。
示例:
#include
#include
#include
int main()
{
int i, num;
int count = 0;
int* ptr = NULL;
do
{
printf("请输入一个整数(输入-1表示结束):");
scanf("%d", &num);
count++;
ptr = (int *)realloc(ptr, count *sizeof(int));
if (ptr == NULL)
{
exit(1);
}
ptr[count - 1] = num;
} while (num != -1);
printf("输入的整数分别是 :");
for (i = 0; i < count; i++)
{
printf("%d ", ptr[i]);
}
putchar('\n');
free(ptr);
return 0;
}
头文件:
#include
函数原型:
char *strcpy(
char *strDestination,
const char *strSource
);
把src
所指由\0
结束的字符串复制到dest
所指的数组中。
注意事项:
src
和dest
所指内存区域不能重叠,且dest
必须有足够的空间来容纳src
的字符串,src
的结尾必须是'\0'
,返回指向dest
的指针。
函数原型:
void *memcpy(
void *dest,
const void *src,
size_t count
);
由src
所指内存区域复制 count
个字节到dest
所指内存区域。
注意事项:
函数返回指向
dest
的指针和strcpy
相比,memcpy
不是遇到\0
就结束,而一定会拷贝n
个字节注意src
和dest
所指内存区域不能重叠,否则不能保证正确
函数原型:
void *memmove(
void *dest,
const void *src,
size_t count
);
函数功能:与 memcpy
相同。
注意事项:
src
和dest
所指内存区域可以重叠,memmove
可保证拷贝结果正确,而memcpy
不能保证。函数返回指向dest
的指针。
函数原型:
void *memset(
void *dest,
int c,
size_t count
);
常用于內存空间的初始化。将已开辟内存空间s
的首n
个字节的值设为值c
,并返回s
。
示例代码:
#include
#include
#include
//模拟memcpy函数实现
void * MyMemcpy(void *dest, const void *source, size_t count)
{
assert((NULL != dest) && (NULL != source));
char *tmp_dest = (char *)dest;
char *tmp_source = (char *)source;
while (count--)//不判断是否重叠区域拷贝
*tmp_dest++ = *tmp_source++;
return dest;
}
//模拟memmove函数实现
void * MyMemmove(void *dest, const void *src, size_t n)
{
char temp[256];
int i;
char *d =(char*) dest;
const char *s =(char *) src;
for (i = 0; i < n; i++)
temp[i] = s[i];
for (i = 0; i < n; i++)
d[i] = temp[i];
return dest;
}
int main()
{
//strcpy进行字符串拷贝
//注意: 1. src字符串必须以'\0'结束, 2. dest内存大小必须>=src
char a[5];
//char b[5] = "ABC";//字符串结尾会自动的有\0 , 此处 b[4]就是'\0'
char b[5];
b[0] = 'A';
b[1] = 'B';
b[2] = 'C';
b[3] = '\0';//必须加\0,否则strcpy一直向后寻找\0
strcpy(a, b);
printf("%s\n", a);
//memcpy函数, 直接拷贝内存空间,指定拷贝的大小
int a2[5];
int b2[5] = { 1,2,3,4,5 };//不需要'\0'结束
memcpy(a2, b2, 3 *sizeof(int) );//指定拷贝的大小, 单位 字节数
printf("%d , %d ,%d\n" , a2[0] , a2[1], a2[2]);
MyMemcpy(a2 + 3, b2 + 3, 2 * sizeof(int));
printf("%d , %d \n", a2[3], a2[4]);
//演示内存重叠的情况
char a3[6] = "123";
//MyMemcpy(a3 + 1, a3, 4); //得到11111
memcpy(a3 + 1, a3, 4);//虽然它是正确的,但是不保证,重叠拷贝应该避免使用它
printf("%s\n", a3);
//memmove功能与memcpy一样,但是了考虑了重叠拷贝的问题,可以保证正确
char a4[6] = "123";
//MyMemmove(a4 + 1, a4, 4);//可以保证正确
memmove(a4 + 1, a4, 4);//可以保证正确
printf("%s\n", a4);
//memset比较简单, 把内存区域初始化化为某个值
char a5[6];
memset(a5, 0, 6);
for (int i = 0; i < 6; ++i)
{
printf("%d", a5[i]);
}
return 0;
}
堆和栈的比较:
道理很简单,如其名称一样,内存越界也就是你申请了一块内存,但在你使用这块内存的时候,你使用的范围超出了你申请到的内存的范围导致内存越界
- 访问到野指针指向的区域,越界访问
- 数组下标越界访问
- 使用已经释放的内存
- 企图访问一段释放的栈空间
- 容易忽略 字符串后面的
'\0'
注意:
strlen
所作的是一个计数器的工作,它从内存的某个位置(可以是字符串开头,中间某个位置,甚至是某个不确定的内存区域)开始扫描,直到碰到第一个字符串结束符'\0'
为止,然后返回计数器值( 长度不包含’\0’)。
#include
#include
#include
char * fun()
{
char arr[10];
return arr;
}//arr是栈内存,离开此花括号,栈被释放回收
int main()
{
//1.访问到野指针指向的区域,越界访问
char *p;//没有初始化,野指针,乱指一气
//strcpy(p, "hello");//非法越界访问
//2.数组下标越界访问
int * p2 = (int *)calloc(10, sizeof(int));
for (size_t i = 0; i <= 10; i++)
{
p2[i] = i;//很难察觉的越界访问, 下标越界
}
//3.使用已经释放的内存
char *p3 = (char *)malloc(10);
free(p3);
if (p3 != NULL)//这里if不起作用
{
strcpy(p3, "hello");//错误,p3已经被释放
}
//4.企图访问一段释放的栈空间
char *p4 = fun(); //p4指向的栈空间已经被释放
strcpy(p4, "hello");
printf("%s\n",p4);
//5.容易忽略 字符串后面的'\0'
char *p5 = (char *)malloc(strlen("hello"));//忘记加1
strcpy(p5, "hello");//导致p5的长度不够,越界
return 0;
}
2.内存泄露(Memory Leak)
前面相信大家都看到了free(ptr);这一简短的代码,其作用便是释放我们在堆上申请的内存,防止程序的未释放,造成系统内存的浪费,导致内存运行速度减慢,甚至是系统崩溃等后果
虽然现在的电脑的内存已经普遍较高,最低显存现在都是8G以上,所以很多人似乎对内存释放这件事并不担心
如果你也是抱着这样的心态:那请试试下面这行代码hhhh
看看你的电脑能撑多久呢?
#include
#include
int main()
{
int *ptr = NULL;
while(1)
{
ptr = malloc(1024);
}
return 0;
}
内存碎片一般是由于空闲的內存空间比要连续申请的空间小,导致这些小内存块不能被充分的利用,当你需要分配大的连续内存时,尽管剩余内存的总和足够,但系统找不到连续的内存,所以导致分配失败malloc/free
大量使用会造成内存碎片
解决方法:
free
,只有内存都空闲的时候,才释放给操作系统。这样减少了 malloc
、free
次数,从而提高效率。设计思路:
文章内容大部分来源于百度,CSDN,以及视频以下为这些资料的链接,在这里统一贴出来