1 内存管理方式
1.1 虚拟内存空间认识
在嵌入式程序设计中,程序的允许基于特点的嵌入式系统允许,比如使用的Linux系统。
在Linux系统中,程序的允许会创建一个进程,对于进程是资源管理的最小单位。在32位的系统中,每一个进程的允许都会创建一个虚拟的4G内存空间:地址序号介于:0x0 ~ 0xffff ffff。
1.2 内存空间的分配方式
对于虚拟内存空间的分配,主要包含两种方式:分别为静态内存分配和动态内存分配。
1.2.1 静态内存分配
所谓的静态内存分配,指的是所分配的内存空间,是在程序运行过程中实现空间的自动开辟和释放,空间的生命周期和大小是固定。
- 在程序运行的时候,按照事先约定的大小来进行空间的大小的管理。
- 静态内存空间:存储的是局部变量以及全局变量
- 静态内存空间:主要包含的是静态存储区和栈区数据。
1.2.2 动态内存分配
所谓的动态内存分配,指的是所分配的内存空间,是在程序运行过程中按照设计者的需求手段开辟和释放,空间的生命周期和大小都是由设用者决定。
- 在程序运行的时候,按照程序者设定的大小管理(根据使用者数据量的大小来分配空间)。
- 动态内存空间:使用指针管理,存储在堆区。
2 动态内存管理
2.1 动态内存开辟和释放的API
#include <stdlib.h>
void *malloc(size_t size);
功能:动态开辟指定字节数的内存空间
参数:
size表示需要开辟空间的字节数;
一般使用大于0的值,表示开辟指定字节大小的内存空间,成功返回所开辟的内存空间的起始地址,内存空间的数据未做初始化设置;
可以设置为0值,可能会NULL;返回一个地址值但是指向的空间不能访问,只能用free释放空间。
返回值:成功返回内存空间的起始地址,失败返回NULL。
void free(void *ptr);
功能:用来动态释放由malloc、calloc和realloc动态开辟的存储空间。
参数:ptr表示动态内存空间的起始地址
释放的是ptr指向内存空间的信息:所开辟的存储空间以及内存的相关信息。
注意:在内存空间释放之后,需要将指针值设置为NULL,在对内存访问的时候,可以运用指针的指针判断内存空间是否已经释放:
1) 如果ptr==NULL,说明内存空间已经释放;不能继续通过指针访问;
2) 如果ptr!=NULL,说明内存空间未释放,可以继续访问。
void *calloc(size_t nmemb, size_t size);
参数:动态开辟指定元素空间的大小和元素个数的内存空间
参数:
参数1:nmemb表示的需要存储元素的个数;
参数2:size表示每一个元素所占空间的字节数。
功能等价于:malloc(nmemn*size);
返回值:成功返回开辟内存空间的起始地址,失败返回NULL。
void *realloc(void *ptr, size_t size);
功能:改变ptr指向内存空间的大小
参数:
参数1:ptr已开辟动态存储空间的起始地址,
参数2:size表示新动态存储空间的大小
1) size <= ptr指向原有开辟内存空间的大小,对原有空间裁剪得到新的内存空间。
新内存空间的起始地址等于原来内存空间的起始地址并返回,多出来的部分空间会自动释放。
2) size > ptr指向原有开辟内存空间的大小:会根据后面空闲空间的大小决定
如果在原有开辟内存空间之后,有为被开辟使用的空闲内存空间,且size < ptr指向原有空间的大小+空闲空间的大小
在原有开辟内存空间之后追加开辟内存空间,并返回原有开辟内存空间的起始地址。
否则:重新找空间开辟,并将原有空间的数据拷贝到新开辟的空间中,还会自动释放原有开辟的空间。
返回值:成功返回新开辟空间的起始地址。失败返回NULL
2.2 内存管理函数实例
1.malloc、calloc函数实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p;
printf("p:%p\n", p);
p = malloc(32); /* 动态开辟32字节的存储空间 */
if (p == NULL) {
perror("malloc error");
return -1;
}
printf("p:%p\n", p);
strcpy(p, "hello test"); /* 将字符串数据拷贝到堆区空间中 */
printf("p : %s\n", p);
free(p); /* 释放堆区空间 */
p = NULL;
if (p == NULL) {
p = calloc(16,1); /* 动态开辟存储16个元素,每个元素空间为1字节的存储空间 */
if (p == NULL) {
perror("calloc error");
return -1;
}
printf("p:%p\n", p);
}
*p = 'A';
printf("p : %s\n", p);
free(p); /* 释放空间 */
p = NULL;
return 0;
}
2.realloc函数实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char *p;
printf("p:%p\n", p);
p = malloc(32); /* 动态开辟32字节空间 */
if (p == NULL) {
perror("malloc error");
return -1;
}
printf("p:%p\n", p);
p = realloc(p, 64); /* 改变空间的大小 */
printf("p:%p\n", p);
free(p); /* 释放空间 */
p = NULL;
}
2.3 内存泄漏
所谓的内存泄露(Memory Leak),指的是在程序中动态开辟的内存空间,因为指针的修改或者其它原因在程序结束的时候,对应的空间未做释放,此时导致内存泄漏。会影响系统中其它程序的正常运行,主要导致程序运行效率降低,可能会出现程序的卡顿或者死机等现象。
在程序设计过程中,需要尽可能的避免内存泄漏。
1) 在对于动态内存指针的访问结束的时候,需要注意指针指向的内存空间是否调用free做内存释放。
2) 可以内存资源的检索,找到已内存泄漏地址空间,对其进行内存释放。
3) 程序结束系统重启,会自动做内存资源的回收。
3 内存分布图
是一个基本内存分布图,其中实际应用过程会有一定调整:
- 堆区和栈区没有固定的分隔解析,是可变的。
- 在内核空间和栈区空间之间,包含到环境变量存储区。存储系统运行的环境变量
- 内存分布实例:
#include <stdio.h>
#include <stdlib.h>
int data_a = 123;
int bss_a;
int data_b = 123;
int bss_b;
void test()
{
}
int main()
{
int *p = malloc(2);
/* 输出的函数的地址 */
printf("test : %p\n", test);
printf("main : %p\n", main);
/* 输出数据常量的地址 */
printf("%p\n", "1234567890");
/* 输出.data段的地址 */
printf("&data_a : %p\n", &data_a);
printf("&data_b : %p\n", &data_b);、
/* 输出.bss段的地址 */
printf("&bss_a : %p\n", &bss_a);
printf("&bss_b : %p\n", &bss_b);
/* 输出堆区地址 */
printf("p: %p\n", p);
/* 输出栈区地址 */
printf("&p : %p\n", &p);
free(p);
p = NULL;
}
通过输出结果发现:地址值逐一递增。
对于.data和.bss的区别,前者编译好的程序空间更大。