目录
1.引入
在学习动态内存管理之前,我们已经知道的内存开辟方式有:
- int a = 5; //在栈空间上开辟四个字节
- char arr[10] = {0}; //在栈空间上开辟10个字节的连续空间
这两种方式开辟内存空间的方式不同,但有一个共同点就是他们所申请的内存的大小是固定的,无法根据程序的具体需要来分配空间,这样可能会造成内存溢出、内存浪费等问题。
为了适应程序的具体需求,我们可以通过一些动态内存函数来优化程序。
2.动态内存函数的介绍
2.1 mallco
函数原型:
void* malloc (size_t size);
函数所在库:
<stdlib.h>
介绍:
这个函数用于向内存申请一块连续可用的空间,申请的结果有两种情况:
- 系统内存足够,内存空间开辟成功,返回一个指向开辟好空间的起始位置的指针。
- 系统内存不足够,内存空间开辟失败,则返回一个 NULL 指针,因此malloc的返回值一定要检查是否为空。
malloc的返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者自己来决定。
特别的,如果参数 size 为 0,malloc的行为标准是未定义的,取决于编译器。
2.2 free
函数原型:
`void free (void* ptr);`
函数所在库:
<stdlib.h>
介绍:
free函数在动态内存分配中至关重要,它用于将之前分配的内存块返回给操作系统,以便其他程序可以重新使用它们。这样做可以有效地管理内存,避免内存泄漏问题,并提高程序的性能和资源利用率。
使用free函数是动态内存管理的关键步骤之一,在不再需要分配的内存时务必调用该函数(尤其是在编写较大规模的项目时),以确保内存的正确释放。
特别的,如果参数 ptr 指向的空间不是动态开辟的(比如是一个确定大小的数组),那么free函数的行为是未定义的。并且,当ptr指针是空指针时,free函数什么操作也不会进行。
下面是一个动态申请内存并释放的样例代码:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
free(p); //p指向的位置不存在 -> p 成了野指针
p = NULL;
return 0;
}
2.3 calloc
函数原型:
void* calloc (size_t num, size_t size);
函数所在库:
<stdlib.h>
介绍:
函数的功能是为 `num `个大小为 `size` 的元素开辟一块空间,并且把空间的每个字节初始化为0。
calloc与函数malloc的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。
2.4 realloc
函数原型:
void* realloc (void* ptr, size_t size);
函数所在库:
<stdlib.h>
介绍:
`ptr`:指向之前用`malloc`, `calloc`, `realloc`开辟的内存区
特别的,当`ptr`为`NULL`时,`realloc`和`malloc`的功能相同
`size` 是调整之后的新大小
realloc可以用于改变之前动态分配内存块的大小,或者在需要时申请新的内存块。
对于realloc函数的返回值,是一个指向被调整过的内存块,这个内存块既有可能是`ptr本身`也有可能是`一个新的区域`:
- 若后面还有足够的连续的空间,足够继续开辟,那么就在后面开辟并返回原内存块的首地址
- 若后面连续的空间不够继续开辟了,那么就在内存中找一个位置开辟一个新的内存块,然后将旧空间中的数据拷贝到新空间并释放旧空间,返回值是新内存块的地址
我们可以调试以下代码来验证:
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
int *ptr = realloc(p, 80);
if(ptr != NULL)
{
p = ptr;
}
else
{
perror("realloc");
return 1;
}
for (int i = 0; i < 20; i++)
{
printf("%d ", *(p + i));
}
free(p);
return 0;
}
在监视窗口中:
当用realloc扩大较少(相对)空间时, realloc返回的指针就是p ,即p == ptr。
当用realloc扩大较大(相对)空间时, realloc返回的指针指向的是一个新的空间, 即p != ptr
3.几种常见的动态内存分配错误
3.1对空指针的解引用操作
申请的空间过大,动态内存分配无法完成,malloc返回的是NULL指针,若进行解引用会报错
void test()
{
int *p = (int *)malloc(INT_MAX);
*p = 20;
free(p);
}
3.2对动态开辟空间的越界访问
即使是动态分配的空间,大小也是有界限的,当申请的空间小于使用的空间,就会造成越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
perror("malloc");
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
3.3对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
}
3.4使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
3.5对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
... ...
free(p);//重复释放
}
3.6动态开辟内存忘记释放(内存泄漏)
动态申请的内存空间,不会因为出 了作用域而自动销毁!
在这个程序中,malloc申请的空间发生内存泄漏,这片空间既不能释放也不能使用。
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
return 0
}
4.经典笔试题
4.1题目一
该代码能正常打印出"hello world"吗?
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
答案:不能
解析:str传给GetMemorysh函数后还是指向一个空指针,在调用strcpy时会对str进行解引用,而空指针无法解引用,程序崩溃退出。
4.2题目二
该代码能正常打印出"hello world"吗?
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
答案: 不能
解析:p的生命周期只存在于GetMemory函数中,函数退出,p就被销毁了,再赋值给str,那么str就是一个野指针
4.3题目三
请问运行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);
}
答案:没有释放申请的内存
4.5题目四
请问运行Test 函数会有什么样的结果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
答案:hello world 但判断逻辑有问题
解析:本题考查点在 if(str != NULL)之前要有一句 str = NULL; 这是在free后的安全做法