Malloc
函数原型
- 开辟一个大小的空间,并且返回该空间的首地址
- 如果开辟失败了则会返回空指针
- 所以使用Malloc之前都需要判断一下
- malloc开辟的空间在堆区中是连续的,如果不使用则需要free否则会导致野指针的出现
例子
#include <stdio.h>
#include<stdlib.h>
int main()
{
int *pa = (int *)malloc(20); //pa所指向的是malloc的首地址
if (pa == NULL) { //判断返回值是否为空
perror("Main");
return 1;
}
int i = 0;
for (int i = 0; i < 5; i++)
{
*(pa + i) = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d\n", pa[i]);
}
//使用完毕后回收空间
free(pa);
pa = NULL;
return 0;
}
动态内存开辟失败的情况
Free
- 动态空间释放函数,一般使用完成都需要释放,参数是动态内存所指向的指针
- 释放的指针不是动态内存开辟的空间所指向的指针是未定义的
- 释放空指针什么都不做
calloc
函数说明
- num开辟的元素个数,每个元素占多少字节
- 开辟成功后会返回首地址并把元素置成0
例子
#include <stdio.h>
#include<stdlib.h>
int main()
{
int* pa = (int*)calloc(20, sizeof(int));
if (pa == NULL) { //判断返回值是否为空
perror("Main");
return 1;
}
int i = 0;
for (int i = 0; i < 20; i++)
{
printf("%d\n", pa[i]);
}
//使用完毕后回收空间
free(pa);
pa = NULL;
return 0;
}
realloc
- ptr需要扩容的源地址,需要扩容的大小(字节)
扩容可能产生的情况
- 情况1 会直接扩容并且返回源地址
- 情况2 如果遇到后续空间不足,则会另外开辟一个新的大小的空间并把源地址的数据拷贝放到新空间,并把源空间释放返回新开辟空间的地址
- 空间不足会返回空指针
- 所以扩容后需要一个新的指针来判断
int main()
{
int* pa = (int*)calloc(20, sizeof(int));
if (pa == NULL) { //判断返回值是否为空
perror("Main");
return 1;
}
int i = 0;
for (int i = 0; i < 20; i++)
{
*(pa + i) = i;
}
for (int i = 0; i < 20; i++)
{
printf("%d\n", pa[i]);
}
int* ptr = (int*)realloc(pa, 20 * sizeof(int));//扩容20个整型
if (ptr != NULL) { //用一个临时的指针来判断内存够不够
pa = ptr;
}
//使用完毕后回收空间
free(pa);
pa = NULL;
return 0;
}
realloc实现malloc
int*pa = (int*)realloc(NULL,40);
使用动态内存的常见错误
对空指针解引用
int main() {
int* pa = (int*)malloc(999999999);
int i = 0;
for (int i = 0; i < 10; i++)
{
*(pa + i);
}
return 0;
}
这样可能造成问题的,所以在使用malloc养成判断是否开辟成功
越界访问
int main()
{
int* pa = (int*)malloc(10 * sizeof(int)); //开辟10个空间
if (pa == NULL) {
perror("malloc");
return 1;
}
int i = 0;
for (int i = 0; i < 40; i++) //malloc只管前10个元素 会造成越界访问
{
*(p + i) = i;
}
return 0;
}
需要注意开辟的空间大小
释放非动态开辟的指针
int main()
{
int arr[10] = { 0 };
int* ptr = arr;
//操作
free(ptr);
ptr = NULL;
return 0;
}
使用 free 释放一块动态开辟内存的一部分
int main()
{
int* pa = (int*)malloc(10 * sizeof(int)); //开辟10个空间
//是否开辟成功
if (pa == NULL) {
perror("malloc");
return 1;
}
int i = 0;
for (int i = 0; i < 10; i++) //
{
*pa++ = i;
}
return 0;
}
这样会导致一个问题,pa原本指向动态开辟的第一位的位置,但是pa往后移后就无法找到最开始的位置,无法完全释放掉动态开辟的内存,注意避免在Malloc中使用指针偏移
忘记释放内存
void test() {
int* pa = (int*)malloc(10);
if (pa == NULL) {
perror("malloc");
}
//忘记释放
}
int main()
{
test();
return 0;
}
忘记释放内存会导致内存泄漏的问题出现,malloc和free要成对的使用
重复释放
int main()
{
int* p = malloc(10*sizeof(int));
if (p == NULL) {
return 1;
}
int i = 0;
for (i = 0; i < 10; i++) {
p[i] = i;
}
// 释放
free(p);
// 一时脑热,再一次释放
free(p);
return 0;
}
这样重复释放会导致程序崩溃,所以释放后记得将指针置为空指针,接下来free就不会执行任何操作
柔性数组
概念
-
结构体变量的最后一个成员是未知大小的数组
struct s { int n; int arr[]; // }; //第二种 struct s { int n; int arr1[10]; };
柔性数组的特点
- 不参与结构体大小的计算
- 柔性数组前必须包含一个元素
- 带有柔性数组的结构体的空间必须使用malloc来申请,申请的大小必须大于结构体的大小
使用场景
- 场景:将结构体中所有的成员变量全部在堆区开辟
int main()
{
struct s s1;
//开辟10个空间的数组
struct s* ps =(struct s*)malloc(sizeof(s1) + 10 *sizeof(int));
if (ps == NULL)
return;
int i = 0;
for (int i = 0; i < 10; i++)
{
ps->n = 100;
ps->arr[i] = i;
}
//扩容
struct s* ptr =(struct s *)realloc(ps, sizeof(s1) + 15*sizeof(int) );
if (ptr != NULL) {
ps = ptr;
}
for (int i = 0; i < 15; i++)
{
ps->n = 100;
ps->arr[i] = i;
}
free(ps);
ps = NULL;
return 0;
}
开辟后内存如下:
图片来源:添加链接描述
使用结构体来实现
int main()
{
struct s s1;
struct s* ps = (struct s*)malloc(sizeof(s1));
if (ps == NULL) {
return 1;
}
int* pa = (int* )malloc(sizeof(int) * 3);
if (pa != NULL) {
ps->parr = pa;
}
//扩容
int *ptr = realloc(ps->parr, sizeof(int) * 10);
if (ptr != NULL)
ps->parr = ptr;
free(pa);
pa = NULL;
free(ps);
ps = NULL;
return 0;
}
这样不好的原因是需要两次malloc,两次malloc就对应了两次的free
开辟后内存如下:
图片来源添加链接描述
> 这样不好的原因是需要两次malloc,两次malloc就对应了两次的free,占用内存
占