在了解和学习之前,我们需要了解为什么要有动态内存管理?
#include
void* malloc(size_t size)
我们可以通过如此的操作来分配内存。参数的单位是字节(size_t),如果申请内存成功返回起始地址,反之则返回NULL。
注:如果参数size为0,malloc的行为标准是未定义的,这主要取决于编译器,不同编译器的结果不同。
int main()
{
//申请10个整型的空间
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
//空间申请失败
perror("malloc failed");
return 1;
}
else{}//可以使用这40个空间
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i + 1; //给申请的空间赋值
}
free(p);
p = NULL;
return 0;
}
free(p)
是通过代码的方式释放内存,如果不释放,程序结束后系统会自动释放。
释放后 p指向的空间不属于当前程序,但依然可以找到这个函数。此时p就成了野指针。
因此,我们需要p = NULL;
来释放p所指向的空间,防止造成内存泄漏。
malloc 和 free 函数需要配套使用。
1.动态内存大小是可以调整的
2.开辟空间的位置不同,malloc函数申请的空间是在堆区,数组是在栈区
3.malloc 函数申请的空间需要手动释放,数组使用完毕后自动释放
内核空间(用户代码不能读写)
栈区(向下增长):进入函数创建,出函数销毁。栈内存分配运算内置与处理器的指令集中,效率高,但内存容量有限。
内存映射段(文件映射、动态库、匿名映射)
堆区(向上增长):一般由程序员释放,若程序员不释放,程序结束时会由OS(操作系统)回收。分配方式类似于链表
数据段(全局数据、静态数据)即静态区:程序结束后由系统释放
代码段(可执行代码/只读常量)数据不可修改:存放函数体(类成员函数和全局函数)的【二进制】代码
局部变量,形式参数放在 栈区
如:int* str1; int str2; char str3;
动态申请的内存,都放在 堆区
如:(int*)malloc(100);
全局变量,静态变量放在 数据段
如:int a; static int b;(全局变量中的) static int c;(局部变量中的)
常量放在 代码段
如:“abcedefg” 1234567890
void* calloc(size_t num, size_t size)
分配内存并初始化为0
参数num是分配的元素个数
参数size是每个元素的大小
申请空间的大小为num * size
申请失败返回NULL
申请成功返回申请空间的指针
int main()
{
//申请10个整型的空间
//malloc(10 * sizeof(int));不进行初始化
int* p = (int*)calloc(10, sizeof(int));//进行初始化,并初始化为0
if (p == NULL)
{
perror("calloc failed");
return 1;
}
//可以使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));//p[i]
}
//释放
free(p);
p == NULL;
return 0;
}
void* realloc(void* ptr, size_t size)
调整内存大小
参数ptr是指向原内存的指针的起始位置
参数size是新的内存大小
调整成功后返回调整后的内存地址,失败则返回NULL
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc failed");
return 1;
}
//可以使用空间
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));//p[i]
}
//调整空间 -- 希望变成20个整型空间
int* tmp = realloc(p, 20 * sizeof(int));
if (tmp != NULL)
{
p = tmp;
}//调整时不要直接使用p接收,可能失败使p变成空指针
//使用
free(p);
p == NULL;
return 0;
}
realloc函数在调整空间时有两种情况:
1.原内存足够,只需调整大小
2.原内存不够,分配新的内存,并拷贝原数据到新内存,同时释放旧的空间,返回新的地址
使用realloc函数时,需要注意指针的变化
realloc 函数不仅能调整空间,也可以申请空间
int main()
{
int* p = (int*)realloc(NULL, 40);//==malloc(40)
if (p == NULL)
{ }
return 0;
}
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
//使用
int i = 0;
//if (p != NULL)
//{
// perror("malloc");
// return 1;
//}
for (i = 0; i < 10; i++)
{
p[i] = i;//*(p + i) = i;
}
free(p);
p = NULL;
return 0;
}
malloc函数可能返回空指针,对空指针解引用会产生错误
需要判断指针是否为空,避免解引用错误
int main()
{
int a = 10;
int* p = &a;
//......
//......
free(p);
p = NULL;
//此时运行时会报错
return 0;
}
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
int i = 0;
if (p != NULL)
{
perror("malloc");
return 1;
}
for (i = 0; i < 5; i++)
{
*p = i;
p++;
}
free(p);//错误写法
p = NULL;
return 0;
}
会报错,因为此时p指向的不是内存的起始位置
正确的释放方式应该是释放p指向的内存的起始位置
不能从中间某个值开始释放一部分动态内存
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
//使用
int i = 0;
if (p != NULL)
{
perror("malloc");
return 1;
}
free(p);
free(p);//错误写法
p = NULL;
return 0;
}
会报错,此时p指向的内存已经被释放
相当于对野指针进行释放
但如果上一次释放时将p置为NULL(空指针),则不会报错
void test()
{
int flag = 1;
int* p = (int*)malloc(100);
if (p != NULL)
{
return 1;
}
if (flag)
{
return;//提前返回,内存没有释放
}
free(p);
p = NULL;
}//内存泄漏
int main()
{
test();
//......
//......
return 0;
}
动态内存管理是一把双刃剑,它能够提供灵活的内存管理方式,但同时也会带来风险。
C99中,结构体最后一个元素是【未知大小的数组】,这就是【柔性数组】的成员。
struct S
{
int n;
char c;
double d;
int arr[];//未知大小数组 - arr就是柔性数组成员
};
柔性数组的特点:
1.结构中柔性数组成员面前必须至少一个其他成
2.sizeof 返回的这种结构大小【不包括】柔性数组的内存
3.包含柔性数组成员的结构用malloc()函数进行动态内存分配,并且分配的内存一个大于结构的大小,以适应柔性数组的预期大小
创建柔性数组的结构体变量
不能直接struct S s;
必须先声明数组的大小,再声明结构体变量
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int));
if (ps == NULL)
{
perror("malloc()");
return 1;
}
//使用这些空间
ps->n = 100;
for (int i = 0; i < 20; i++)
{
ps->arr[i] = i + 1;
}
//调整ps指向空间的大小
struct S* tmp = (struct S*)realloc(ps, sizeof(struct S) + 30 * sizeof(int));
if (tmp != NULL)
{
ps = tmp;
}
else
{
return 1;
}//防止调整失败,导致所有数组都销毁
//使用
for (int i = 0; i < 30; i++)
{
printf("%d ", ps->arr[i]);//前20个元素确定,后面的为随机值
}
//释放
free(ps);
ps = NULL;
return 0;
}
我们也可以采用另一种方式实现柔性数组的内存分配
struct A
{
int n;
int* arr;
};
int main()
{
struct A* ps = (struct A*)malloc(sizeof(struct A));
if (ps == NULL)
{
perror("malloc()");
return 1;
}
int* p = (int*)malloc(20 * sizeof(int));
if (p != NULL)
{
ps->arr = p;
}
else
{
return 1;
}
ps->n = 100;
int i = 0;
//给arr中的20个元素赋值为1~20
for (i = 0; i < 20; i++)
{
ps->arr[i] = i + 1;
}
//调整空间
int* tmp = (int*)realloc(ps->arr, 40 * sizeof(int));
if (tmp != NULL)
{
ps->arr = tmp;
}
else
{
perror("realloc()");
return 1;
}
for (i = 0; i < 40; i++)
{
printf("%d ", ps->arr[i]);//前20个元素确定,后面的为随机值
}
//释放
free(ps->arr);//先释放arr中开辟的内存空间
ps->arr = NULL;//可写可不写
free(ps);
ps = NULL;
return 0;
}
对比:
第一种使用柔性数组的方案更好,尽量使用第一种
优点:1.方便内存释放
2.有利于内存访问速度(影响有限),减少内存碎片
第二种易出现内存泄漏的问题