程序员成长之旅——动态内存管理
为什么存在动态内存分配
我们已经掌握的内存开辟的方式有:
int hr = 20;//在栈上开辟四个字节的空间
char a[10] = {0};//在栈上开辟连续10个字节的空间,在编译的时候开辟
但是这么开辟还是有两大缺点
- 开辟的大小是固定的
- 如果是要在运行的时候在开辟空间的话,这样是不可以的
这时候我们就引入了动态内存开辟。
动态内存函数的介绍
malloc 和 free
C语言提供了一个动态内存开辟的函数:
void *malloc( size_t size );
这个函数向内存申请了一块连续可用的空间,并返回一个指针指向这个空间
- 开辟成功的话,则返回一个指向开辟好空间的一个指针
- 开辟失败的话,则返回空指针,因此我们要判断返回的指针是否为空
- 返回值为void*,因此我们并不知道返回的类型是什么,具体使用的时候在决定
- 如果size为0,那么malloc的行为是标准还是未定义的,取决于编译器
C语言提供了另一个函数,用来释放动态开辟的空间:
void free (void* ptr);
free函数用来释放动态内存开辟的内存
- 如果ptr指向的空间不是动态内存开辟的,那么free是未定义的
- 如果ptr = NULL,那么free是不做任何释放的
malloc 和 free都声明在 stdlib.h头文件中。举个例子:
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
////代码1
//int num = 0;
//scanf("%d", &num);
//int arr[num] = { 0 };
//代码2
int* ptr = NULL;
//ptr = (int*)malloc(10 * sizeof(int));
ptr = (int*)malloc(INT_MAX);
if (NULL == ptr)//判断开辟是否失败
{
printf("%s\n", strerror(errno));
//perror("use malloc"); 不需要头文件 相对简单
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(ptr + i) = 0;
}
free(ptr);//释放ptr所指向的动态内存
ptr = NULL;//是否有必要? 这个是要指向空的,因为free释放之后它还是指向那块空间,所以要置空
return 0;
}
calloc
C语言还提供了一个函数为calloc,calloc也用来动态内存开辟。原型如下:
扫描二维码关注公众号,回复:
8624802 查看本文章
void* calloc(size_t num,size_t size);
- 函数的功能是为num个大小为size的元素开辟一个空间,并且把每个空间的字节初始化为0。
- 与malloc的区别只在于会初始化这个字节为0。
举个例子
int main()
{
int* p = calloc(10, sizeof(int));
if (NULL != p)
{
//使用空间
}
free(p);
p = NULL;
return 0;
}
在有要求申请的空间字节必须初始化为0的时候我们就用calloc,但是没有要求的话,效率高的话就用malloc。
realloc
- realloc函数的出现让动态内存开辟更加灵活
- 有时候我们发现开辟的空间太小了,或者太大了,这时候用realloc就可以灵活调整这个内存空间。函数原型如下:
void* realloc(void* ptr,size_t size);
- ptr是要调整的内存的初始地址
- size是调整之后新字节大小
- 返回值是调整之后的内存首地址
- 这个函数调整之后还会将原数据移动到新的内存空间中
- realloc在调整内存空间有两种情况
情况1.原有空间之后有足够大的空间
情况2.原有空间之后没有足够大的空间
情况1 当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2 当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。 由于上述的两种情况,realloc函数的使用就要注意一些。
举个例子:
int main()
{
int* ptr = malloc(100);
if (ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//这样申请是有很大弊端的,假设申请失败,之前的数据也会消失
//代码2
int* p = NULL; p = realloc(ptr, 1000);
if (p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
ptr = NULL;
return 0;
}
realloc(NULL,10),像这样第一个传空指针的话,用法就类似于malloc。
常见的动态内存的错误
- 对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
- 对动态开辟空间的越界访问
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;//当i是10的时候越界访问
}
free(p);
}
- 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p);//ok?
}
- 使用free释放动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
- 对同一块动态内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
- 动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
}
几个经典的笔试题
笔试题
int* f1(void)
{
int x = 10;
return (&x);
}
int main()
{
int* p = f1();
printf("%d\n", p);
return 0;
}
题目一
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello word");//在这崩溃
printf(str);
}
int main()
{
Test();
return 0;
}
请问运行Test 函数会有什么样的结果?
str 和 p是不同的空间,而p开辟之后,出了空间会销毁,str还是NULL,所以会崩溃。
还有一处错误是内存泄漏。也就是没有free。
改正:
题目二
char* GetMemory(void)
{
char p[] = "hello world";//局部变量存在栈中,出函数就销毁
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
请问运行Test 函数会有什么样的结果?
局部变量存在栈中,出函数就销毁
题目三
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);
}
void Test(void) {
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
请问运行Test 函数会有什么样的结果?
free(str);
str = NULL;要加上,因为上面会有内存泄漏
题目四
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "word");
printf(str);
}
}
int main()
{
Test();
return 0;
}
请问运行Test 函数会有什么样的结果?
非法访问内存
free(str)后面应该加上 str = NULL