文章目录
引入
YY同学有一个神秘的活页本,用来记录每天发生事情,随着时间推移,活页本的纸张显得不够用了,YY同学买了活页纸补充进去。
int main()
{
int n;//在栈区开辟四个字节的空间
int arr[10];//在栈区开辟四十个字节的空间
return 0;
}
在我们以往的代码中,开辟空间的方式只有上述两种,其特点是
- 空间开辟⼤⼩是固定的。
- 数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知道,像数组那样开辟空间的⽅式就不能满⾜需求了。于是,C语言引入了动态内存管理,让程序员可以申请和释放空间
malloc && free
malloc
从C语言官网可以了解到,malloc函数原型为void* malloc (size_t)
,头文件为stdlib.h
,参数为size_t
类型,返回值为void*
。解释:参数为申请的空间大小,返回值为开辟空间的起始地址。
int main()
{
//申请十个整形的空间
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");//如果申请失败,打印失败原因
return 1;
}
return 0;
}
注意事项
- 如果申请成功,则返回开辟空间的起始地址
- 如果申请失败,则返回一个空指针,因此,在使用
malloc
函数时,一定要判断返回值是否为空 - 返回值的类型由程序员自己决定
free
C语⾔提供了另外⼀个函数free,专⻔是⽤来做动态内存的释放和回收的,函数原型如下:void free(void*)
简单理解就是传入一个地址(动态开辟的空间)将其释放
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if(p == NULL)
{
perror("malloc");
return 1;
}
free(p);
p = NULL;
return 0;
}
需要注意的是此时p中仍然存放着先前的地址,为野指针,需要将其赋值为NULL
YY:用malloc申请的空间有啥特别的呢?
- 空间大小可以改变
- 在堆区上申请
- 需要手动的回收空间,释放内存,防止内存泄漏
malloc与free一般成对出现
calloc && realloc
calloc
calloc函数与malloc函数类似,作用是开辟空间并且将内容赋值为0,其函数原型为void *calloc(size_t num,size_t size)
第一个参数为向内存申请类型的个数,第二个参数为向内存申请的类型大小
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
free(p);
p == NULL;
return 0;
}
需要初始化用calloc,不需要初始化用malloc
realloc
在写代码的过程中,多数情况下我们不能一次性开辟完全合适的空间,需要不断地调整,追加空间或者缩小空间,这时就需要用到realloc
函数
realloc函数是最符合动态内存管理的函数,作用是在原有动态开辟的内存的基础上继续开辟空间,其函数原型为void* realloc (void* ptr, size_t size);
第一个参数是指向一个被动态开辟好的内存块,第二个参数为想要开辟的新的大小,返回值为调整之后的内存的起始位置
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr!= NULL)
{
p = ptr;
}
free(p);
p = NULL;
return 0;
}
这里需要注意realloc
使用过程中的三种情况
- 后续空间充足,直接向后追加空间,并且返回旧地址
- 后续空间不足
- 在堆区找一块大小足够的空间
- 将原先的数据拷贝进来
- 释放旧的空间
- 返回新的地址
- 扩容失败,返回空指针
NULL
这里不可用p指针
来直接接收扩容后的地址,因为有可能会扩容失败,导致原有的数据丢失,所以创建一个新的指针来接收realloc
的返回值,若返回值不为空,则赋值给p
常见的动态内存的错误
对空指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
错误原因:没有对p
进行判空,容易导致对空指针解引用
改正为:
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
if (p == NULL)
{
perror("malloc");
return 1;
}
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
对非动态开辟的内存使用free释放
void test()
{
int a = 0;
int* p = &a;
free(p);
}
再次强调–>free
只能释放动态开辟的内存空间
使⽤free释放⼀块动态开辟内存的⼀部分
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
*p = i;
p++;//err
}
free(p);
p = NULL;
return 0;
}
此时p
已经不再指向起始位置,所以释放失败
改正为:
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
p[i] = i;
}
free(p);
p = NULL;
return 0;
}
对同一块空间多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
动态开辟内存但忘记释放(内存泄漏)
void test()
{
int flag = 1;
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
return;
}
if (flag)//err
{
return;
}
free(p);
p = NULL;
}
int main()
{
test();
//....
return 0;
}
上述代码虽然写了free
函数,但并未执行,导致申请的内存没有被释放掉,最终造成内存泄漏
柔性数组
柔性数组引入
struct S
{
int n;
int arr[];
};
以上代码我们声明了一个结构体S
,仔细观察发现结构体中的最后一个成员为整形数组,但是又和普通的数组有差别,其没有规定数组的大小,我们称这样的数组为柔性数组
柔性数组的特点
- 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
- sizeof 返回的这种结构⼤⼩不包括柔性数组的内存。
- 包含柔性数组成员的结构⽤malloc ()函数进⾏内存的动态分配,并且分配的内存应该⼤于结构的⼤⼩,以适应柔性数组的预期⼤⼩。
解释:第二条就是说,在用sizeof
计算结构体大小时,不会将柔性数组的大小计算在内。第三条是说,应该动态分配足够的大小,以提供给柔性数组使用
柔性数组代码举例
struct S
{
int n;
int arr[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));//动态申请内存
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->n = 10;//赋值
for (int i = 0; i < 10; i++)
{
ps->arr[i] = i + 1;
}
for (int i = 0; i < 10; i++)//使用
{
printf("%d ", ps->arr[i]);
}
printf("\n");
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 20 * sizeof(int));//扩容
if (ptr != NULL)//检验是否扩容成功
{
ps = ptr;
}
else
{
return 1;
}
for (int i = 0; i < 20; i++)//使用
{
printf("%d ", ps->arr[i]);
}
free(ps);//释放内存
ps = NULL;
return 0;
}
通过realloc
函数就可体现出柔性数组的大小是可以改变的
内存区域划分
C语言中将内存大体分为五个区域,下以表格的形式展示
区域名称 | 功能及特点 |
---|---|
内核空间 | 准们为操作系统开辟的一块空间,用户不可读写 |
栈区 | 在执⾏函数时,函数内局部变量的存储单元都可以在栈上创建,函数执⾏结束时这些存储单元⾃动被释放。栈内存分配运算内置于处理器的指令集中,效率很⾼,但是分配的内存容量有限。 栈区主要存放运⾏函数⽽分配的局部变量、函数参数、返回数据、返回地址等 |
堆区 | ⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配⽅式类似于链表 |
静态区 | 也叫数据段,存放全局变量、静态数据。程序结束后由系统释放 |
代码段 | 存放函数体(类成员函数和全局函数)的⼆进制代码 |
总结
以上是C语言中动态内存管理相关的知识,我们要像捡贝壳一样捡到自己的小篮子里哦,有不明白的地方欢迎留言,作者水平有限,文章不妥部分还请各位读者指正。