为什么需要动态内存管理?创建一个数组,我们要为数组指定大小,int arr[10];,这属于静态创建一个数组,数组arr存放在栈上。这样的创建方式有一些局限性,小了呢不够用,大了呢又浪费空间,因此要引入动态内存管理。

动态创建一个数组,不再受元素个数的限制,当元素个数与容量相等时,可以很方便地扩容。

如何动态内存管理,我们来介绍几个函数。

malloc

1
void* malloc (size_t size);

参数size为要为空间开辟的字节数,开辟成功后返回值为该空间的首地址,失败则返回NULL.当size为0时,要看编译器如何处理,具体返回什么不确定

动态开辟内存后,不需要再使用这块空间时,要使用free函数释放内存。,否则会内存泄漏。free释放后这块内存可以再次被分配,但被释放的空间的值没有被改变,它仍然指向相同(无效)的位置。

free专门用来释放动态分配的空间,如果空间为空,不执行任何操作。切记不可以用free来释放静态分配的内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include<stdlib.h>
int main()
{
int* p =(int*)malloc(40); //动态开辟10字节的空间
if (p == NULL) { //判断是否开辟成功
perror("malloc");
return;
}
for (int i = 0; i < 10; i++) {
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p); //释放p空间
return 0;
}

calloc

1
void* calloc (size_t num, size_t size);

malloc相似,但是会在开辟后为空间初始化为0.参数num为要分配的元素数,size为每个元素的大小,总的内存空间为num*size个字节。

与malloc用法一致,不在举例。

realloc

1
void* realloc (void* ptr, size_t size);

该函数可以为动态空间扩容,参数ptr为要扩容的空间,size为扩容后的内存大小。

扩容成功返回该空间的首地址,失败返回空,所以为空间扩容时先创建一块临时变量指向该空间,为临时变量扩容,扩容成功再让要扩容的空间等于临时变量,以防扩容失败内容丢失。

扩容会遇到两种情况,1种是该块空间后没有额外的空间来扩容,这时会分配一块新空间,将旧空间的内容移到新空间,返回值自然也是新空间的首地址,2是该块空间空间充足,这就可以在该空间后连续扩容,无需再寻找新的空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(40); //动态开辟10字节的空间
if (p == NULL) { //判断是否开辟成功
perror("malloc");
return;
}
for (int i = 0; i < 10; i++) {
*(p + i) = i;
printf("%d ", *(p + i));
}
//扩容
int* tmp=(int*)realloc(p, 80);
if (tmp != NULL) { //判断是否扩容成功
p = tmp;
}
for (int i = 10; i < 20; i++) {
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
return 0;
}

输出结果为0~19,扩容成功。

问题

了解了动态内存分配函数的一些基础使用后,我们来看一些常见的问题。

代码1:这段代码有什么问题?

1
2
3
4
5
6
7
8
9
10
11
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}

p是一个局部变量,是实参的一份临时拷贝,出了GetMemory函数会自动销毁。我们为p开辟了100个字节的空间,出了函数后p销毁,找不到这块空间的首地址,却没对该空间进行内存释放,会造成内存泄漏。

此外str仍是空指针,将“hello world”拷贝到一块空指针,会造成非法访问,程序崩溃。

我们可以试着修改,将传值调用改成传址调用,这样p和str指向了同一块空间,最后别忘了free释放内存。

1
2
3
4
5
6
7
8
9
10
11
12
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str); //传地址
strcpy(str, "hello world");
printf(str);
free(str); //别忘了释放哦~
}

代码2:

1
2
3
4
5
6
7
8
9
10
11
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}

p是一个局部变量,是实参的一份临时拷贝,return p返回的是局部变量p的首地址,str收到了这个地址。不巧的是出了GetMemory函数p这块地址就还给了操作系统,所以str找不到这块地址,造成了野指针。