目录
一、为什么存在动态内存分配
二、动态内存函数的介绍
2.1malloc和free
2.2calloc
2.3realloc
三、常见的动态内存错误
3.1对NULL指针的解引用操作
3.2对动态开辟空间的越界访问
3.3对非动态开辟的内存使用free释放
3.4使用free释放一块动态开辟内存的一部分
3.5对同一块内存多次释放
3.6动态开辟内存忘记释放(内存泄漏)
四、几个经典的题
我们已经掌握的内存开辟方式有:
int a = 10;//在栈空间开辟四个字节
char arr[10] = {10};//在栈空间开辟10个字节的连续空间
上述的开辟空间的方式有两个特点:
1.空间开辟大小是固定的;
2.数组在申请的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述情况,有时候我们需要的空间大小在程序运行的时候才能知道,如此数组的编译时开辟空间的方式就不能满足了。这个时候可以使用动态开辟了。
//这个函数内存申请一块连续可用的空间,并返回指向这块空间的指针
void* malloc(size_t size);
//C语言提供了另外一个函数free,专门就用来做动态内存的释放和回收的
//free函数用来释放动态开辟的内存
void free(void* ptr);
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
如果参数ptr是NULL指针,则函数声明事都不做。
int main()
{
//申请
int* str = (int*)malloc(sizeof(int) * 5);
if (str == NULL)
{
printf("malloc %s", strerror(errno));
exit(-1);
}
//使用
for (int i = 0; i < 5; i++)
{
*(str +i) = i + 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", *(str +i));
}
//释放
free(str);
str = NULL;
return 0;
}
void* calloc(size_t num, size_t size);
函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每一个字节初始化为0;
与函数malloc的区别在于calloc会在返回地址之前把申请的空间的每一个字节初始化为全0
int main()
{
//创建了一块大小为10个大小为int类型的空间,默认初始化成0
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("calloc:%s", strerror(errno));
exit(-1);
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p+i));
}
//释放
free(p);
p = NULL;
return 0;
}
void* realloc(void* ptr, size_t size);
realloc函数的出现让动态内存管理更加灵活
有时我们会发现过去申请的空间太小,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。那realloc函数就可以做到对动态开辟内存大小的调整。
1.原有空间之后有足够大的空间:要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不会发生变化。
2.原有空间之后没有足够的空间:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
printf("malloc:%s\n", strerror(errno));
exit(-1);
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
for (int i = 0; i < 5; i++)
{
printf("%d ", *(p+i));
}
int* ptr = (int*)realloc(p, sizeof(int) * 10);
if (ptr == NULL)
{
printf("realloc:%s\n", strerror(errno));
exit(-1);
}
else
{
p = ptr;
for (int i = 5; i < 10; i++)
{
p[i] = i + 1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
}
//释放
free(p);
p = NULL;
return 0;
}
void test1()
{
int* p = (int*)malloc(20);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
void test2()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(-1);
}
for (int i = 0; i <= 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
}
void test3()
{
int a = 10;
int* p = &a;
//对非动态开辟的内存使用free释放
free(p);
}
void test4()
{
int* p = (int*)malloc(100);
p++;//p指向后面的地址
free(p);//必须提供起始地址的地址来free
}
void test5()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
malloc、calloc、realloc等所申请的空间不想使用需要free释放,如果不使用free释放程序结束之后,也会由操作系统回收,如果不使用free释放,程序也不结束,那么会造成内存泄漏
void test6()
{
int* p = (int*)malloc(100);
if (p != NULL)
{
*p = 20;
}
}
int main()
{
test6();//动态开辟的内容忘记释放(内存泄漏)
while (1);
return 0;
}
//传值调用,不会影响str,str依然为NULL,
//1.strcpy函数调用失败,原因是对NULL的解引用操作,程序会崩溃
//2.没有释放,会造成内存泄漏
//
void GetMemory1(char* p)
{
p = (char*)malloc(100);
}
void test7()
{
char* str = NULL;
GetMemory1(str);
strcpy(str, "hello world");
printf(str);
}
//GetMemory2函数内部创建的数组是临时的,虽然返回了p给str,但数组的内存出了函数就会归还给操作系统,
//而str依然保持了数组的起始地址,这时如果使用str, str就是野指针
//
char* GetMemory2()
{
char p[] = "hello world";
return p;
}
void test8()
{
char* str = NULL;
str = GetMemory2();
printf(str);
}
//传址调用,str指向malloc分配出来的起始地址,但是最后没有释放,会造成内存泄漏
void GetMemory3(char** p, int num)
{
*p = (char*)malloc(num);
}
void test9()
{
char* str = NULL;
GetMemory3(&str, 100);
strcpy(str, "hello");
printf(str);
//释放
//free(str);
}
//在free后没有将str置空,str指向的内存空间被还给操作系统了,此时str是野指针,往str里拷贝字符串会形成非法访问
void test10()
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//free完后要将str置为空
str = NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}