为什么要有动态内存分配
在之前我们已经掌握了内存开辟的方式
int a = 0;//在栈空间开辟四字节的空间
char s[20] = { 0 };//在栈空间开辟连续的20字节空间
但是我们通过上述方法开辟的空间是固定的,数组的空间定义后也是不能更改的
C语言中就引入了动态内存开辟,让我们自己申请空间,就相对灵活了
四个函数
malloc
头文件:stdlib.h
void* malloc (size_t size);
这个函数用于在堆区内存申请一块连续的空间(相当于数组),如果申请成功返回空间的起始地址,申请失败返回空指针(使用时要判断),返回类型是void *使用时自行定义返回值。如果size的值为0,结果未定义,由编译器决定
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p1 = malloc(20);
if (p1 == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p1+i)= i;
printf("%d ", *(p1 + i));
}
return 0;
}
输出结果
free
void free (void* ptr);
专门用于释放和回收动态内存,本质是将空间的权限还给操作系统,失去对这块内存的使用权,并不是自动变成空指针。操作后 ptr 变成野指针,需要置空指针。
free(p1)
p1 = NULL
参数指向的空间必须是起始地址并且是动态开辟的,如果是空指针则什么都不做。
calloc
void* calloc (size_t num, size_t size);
用于开辟num个size字节的空间,并初始化为0,除此之外与malloc没有区别
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p1 = calloc(5,sizeof(int));
if (p1 == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", *(p1 + i));
}
return 0;
}
输出结果
realloc
void* realloc (void* ptr, size_t size);
用于对内存大小灵活调整,调整ptr指向的内存为size字节,并返回起始地址。
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p1 = calloc(1,sizeof(int));
if (p1 == NULL)
{
perror("calloc");
return 1;
}
int* p3= (int*)realloc(p1, 40);
for (int i = 0; i < 5; i++)
{
*(p3 + i) = i;
printf("%d ", *(p3 + i));
}
return 0;
}
如果p1后面的空间大于等于40字节则正常开辟,并返回p1的地址,如果空间小于40字节则在内存中选取另一块40字节的空间,作为开辟的空间,并将p1原来的数据复制过来然后返回新开辟空间的地址
realloc也可以完成malloc的功能
realloc(NULL,20)=malloc(20);
常见动态内存错误
1 开辟空间后没有判断,容易对空指针解引用
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;
free(p);
}
2 动态内存越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
}
for (i = 0; i <= 10; i++)
{
* (p + i) = i;
}
free(p);
}
3 对非动态内存的free释放
void test()
{
int a = 10;
int* p = &a;
free(p);
}
4 free释放空间的时候没有指向空间的起始位置
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);
}
5 对同一块内存的多次free释放(释放一次内存后必须置空指针)
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);
}
6 在局部变量动态开辟的内存没有使用free释放,这个空间将无法使用(malloc calloc realloc开辟的内存空间只有在程序结束时才会回收),发送内存泄漏
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
尽量做到
1 谁申请的空间谁释放,如果不能释放,应该告诉使用的人,记得释放。
2 释放空间之后,紧接着置空指针
动态内存经典笔试题解析
第一题
下面程序运行会产生什么现象
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
结果
程序崩溃
原因
void GetMemory(char* p)
{
p = (char*)malloc(100); //形参p获得malloc开辟的100字节空间的起始地址
}
char* str = NULL;
GetMemory(str); //退出函数后,形参p销毁,无法将地址传给实参str,str还是空指针
修改
如果在函数中想修改实参,那么应该传递实参的地址,通过对地址的解引用来修改实参
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
或者函数返回p的地址,这里不做过多解释
第二题
下面程序运行会产生什么现象
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
结果
程序不可控,是否输出不确定
原因
退出函数后,函数空间被销毁,也就是说,p所指向的空间被操作系统回收,然而函数返回了p的地址,这时候p就是空指针。如果内存没被覆盖就可以成功打印,如果被覆盖那么打印随机值或者程序崩溃
柔性数组
C99,结构体的最后一个成员可以是一个未知大小的数组,这个数组就是柔性数组
柔性数组的特点
柔性数组前面必须有一个或多个成员,结构体大小不包括柔性数组,sizeof访问的时候也不返回柔性数组的大小。用malloc函数进行分配内存,大小应该大于结构体的大小。也可以用realloc调整大小
举个例子
#include<stdio.h>
#include<stdlib.h>
struct S
{
int n;
int a[];
};
int main()
{
struct S* s1 = { 0 };
s1 = malloc(sizeof(s1) + 5 * sizeof(int));
if (s1 == NULL)
{
perror("malloc");
return 1;
}
else
{
for (int i = 0; i < 5; i++)
{
s1->a[i] = i;
printf("%d ", s1->a[i]);
}
}
//调整空间
struct S* str = realloc(s1, 1000);
if (str != NULL)
{
s1 = str;
}
free(str);
str = NULL;
return 0;
}
模拟实现柔性数组
struct S
{
int a;
int* arr;
};
int main()
{
struct S* s1 = (struct S*)malloc(sizeof(struct S));
//struct S* s1 = (struct S*)malloc(sizeof(s1));
if (s1 == NULL)
return 1;
int* p = (int*)malloc(20);
if (p == NULL)
return 2;
s1->arr = p;
for (int i = 0; i < 4; i++)
{
s1->arr[i] = i + 1;
printf("%d ", s1->arr[i]);
}
//调整空间
int* p1 = realloc(s1->arr, 10 * sizeof(int));
if (p1 == NULL)
return 3;
s1->arr = p1;
free(s1->arr);
s1->arr = NULL;
free(s1);
s1 = NULL;
return 0;
}