【C语言进阶】- 动态内存管理
动态内存管理
- 1.1 为什么存在动态内存分配
- 1.2 动态内存函数介绍
- 2.1 malloc函数的使用
- 2.2 free函数的使用
- 2.3 calloc函数的使用
- 2.4 realloc函数的使用
- 3.1 常见的动态内存错误
- 3.2 常见笔试题
1.1 为什么存在动态内存分配
我们已经掌握的内存开辟方式有:
int val = 20;//在栈空间上开辟四个字节
char arr[10] = { 0 };//在栈空间上开辟10个字节的连续空间
但是上述的开辟空间的方式有两个特点:
1. 空间开辟大小是固定的。
2. 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。
这时,就需要动态开辟内存了...
1.2 动态内存函数介绍
开辟内存
void* malloc (size_t size);
malloc函数是在堆中连续开辟size个字节的空间,返回值为空间的起始地址,开辟失败返回NULL指针
释放内存
void free (void* ptr);
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
2.1 malloc函数的使用
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
/*
void* malloc( size_t size );
*/
int main()
{
int arr[10] = { 0 };
// 动态内存开辟
int* p = (int*)malloc(40); // 开辟40个字节
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1; // 给main函数返回1表示存在问题
}
// 使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p); // 释放内存空间,不然会内存泄漏,可以通过调试窗口 p,10 来观看数据
p = NULL; // 防止野指针,因为它指向的空间已经释放了,已经还给操作系统了,p如果不置空的话,这时候还记得地址
return 0;
}
还未free时的内存状态
free后的内存状况
p=NULL 是为了防止野指针,因为它指向的空间已经释放了,已经还给操作系统了,p如果不置空的话,这时候还记得地址,如果访问的话就非法访问内存了
2.2 free函数的使用
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a; // 不是动态开辟的
free(p);
p = NULL;
int* p2 = NULL; // 什么事都不做
free(p2);
return 0;
}
free函数是只能释放动态内存的,不是动态开辟的会报错
2.3 calloc函数的使用
void* calloc (size_t num, size_t size);
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
printf("%s\n",strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i < 10; i++) {
printf("%d ",*(p+i));
}
// 释放堆中的内存
free(p);
p = NULL;
return 0;
}
2.4 realloc函数的使用
void* realloc (void* ptr, size_t size);
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
}
// 扩容
// 要用新的指针来接收并且判断,否则,如果扩容失败会变成野指针,本来指向40个字节,扩容失败突然指向NULL
int* ptr = (int*)realloc(p, 80);
if (ptr != NULL)
{
p = ptr;
}
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
需要注意的事,realloc在开辟内存空间时,存在2种情况
malloc和realloc函数的关联
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* ptr = (int*)realloc(NULL, 40); // 相当于 malloc(40)
return 0;
}
3.1 常见的动态内存错误
1.对NULL指针的解引用操作
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p = NULL) // err
{
return 1;
}
*p = 20;
free(p);
p = NULL;
return 0;
}
这里如果动态开辟内存失败,p为NULL,对NULL指针进行访问就会有问题
2. 对动态开辟空间的越界访问
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p = NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
int i = 0;
for (i = 0; i <= 10; i++) // 越界了
{
p[i] = i;
}
free(p);
p = NULL;
return 0;
}
3. 对非动态开辟内存使用free函数
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a = 10;
int* p = &a;
free(p);
p = NULL;
return 0;
}
4. 使用free释放一块动态开辟内存的一部分
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
// 这里p的位置被改变了,free(p)时,只释放掉了部分内存
*p = i;
p++;
}
free(p);
p = NULL;
return 0;
}
5. 对同一块空间的多次释放
#include <stdio.h>
#include <stdlib.h>
int main()
{
int* p = (int*)malloc(40);
free(p);
p = NULL;
free(p);
return 0;
}
6.动态开辟内存忘记释放(内存泄漏)
#include <stdio.h>
#include <stdlib.h>
void test()
{
int* p = (int*)malloc(100);
int flag = 0;
scanf("%d",&flag);
if (flag == 5)
{
return;
}
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
当flag为5时,就不会释放内存
3.2 常见笔试题
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void GetMemory(char* p) // 堆中的p
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL; // 栈中的p
GetMemory(str); // 传递的是null,不是str的地址
strcpy(str, "hello world"); // 将str给到空指针
printf(str);
}
int main()
{
Test();
return 0;
}
str为NULL,传递给GetMemory函数,GetMemory函数里的p是堆中的所开辟的p,然后给它开辟空间,然后返回到Test函数,这时的str依旧为NULL,然后调用strcpy函数,是把"hello world"给到NULL指针,会出现错误。
#include <stdio.h>
char* GetMemory(void)
{
char p[] = "hello world"; // 这里是局部变量,放置在栈中, 函数执行完就被销毁了,还给操作系统了
//char* p = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory(); // 野指针,非法访问内存了
printf(str);
}
int main()
{
Test();
return 0;
}
这里GetMemory后,返回的是局部变量的地址,放在栈中,但是这个函数执行完后这个变量就被销毁了,所以再通过指针访问这块内存空间就是非法访问内存了。
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
// 下列为修改后的代码
//free(str);
//str = NULL;
}
int main()
{
Test();
return 0;
}
这个很容易,动态开辟内存后未能后free