目录
[1. 为什么要有动态内存分配](#1. 为什么要有动态内存分配)
[2. malloc和free](#2. malloc和free)
[2.1 malloc](#2.1 malloc)
[2.2 free](#2.2 free)
[3. calloc和realloc](#3. calloc和realloc)
[3.1 calloc](#3.1 calloc)
[3.2 realloc](#3.2 realloc)
[4. 常见的动态内存的错误](#4. 常见的动态内存的错误)
[4.1 对NULL的解引用操作](#4.1 对NULL的解引用操作)
[4.2 对动态开辟内存空间的越界访问](#4.2 对动态开辟内存空间的越界访问)
[4.3 对非动态开辟内存使用free释放](#4.3 对非动态开辟内存使用free释放)
[4.4 使用free释放动态开辟内存的一部分](#4.4 使用free释放动态开辟内存的一部分)
[4.5 对同一块动态内存多次释放](#4.5 对同一块动态内存多次释放)
[4.6 (仅使用不释放)内存泄露](#4.6 (仅使用不释放)内存泄露)
[5. 动态内存笔试题分析](#5. 动态内存笔试题分析)
[5.1 练习1](#5.1 练习1)
[5.2 练习2](#5.2 练习2)
[5.3 练习3](#5.3 练习3)
[6. 柔性数组](#6. 柔性数组)
[6.1 柔性数组的特点](#6.1 柔性数组的特点)
[6.2 柔性数组的使用](#6.2 柔性数组的使用)
[6.3 柔性数组的优势](#6.3 柔性数组的优势)
1. 为什么要有动态内存分配
我们已经掌握的内存开辟方式有:创建变量,创建数组申请空间。
// 申请空间
int main()
{
int a = 0; // 向内存申请4个字节的空间
int arr[10]; // 向内存申请40个字节的连续空间
return 0;
}
但是上面开辟空间的方式有两个特点:
- 开辟的空间大小是固定的
- 数组在声明的时候,必须指定数组的长度,数组空间一旦确定就不能调整
但是在生活中,我们对空间的需求不仅仅是固定的,还有一些情况,我们需要的空间大小需要在程序运行的时候才知道,而这些情况,固定的空间大小已经不能满足需求了。因此:
C语言引入了动态内存开辟,让程序员自己可以申请空间和释放空间,就比较灵活了。
2. malloc和free
2.1 malloc
C语言提供了一个动态内存开辟的函数:
代码块
void* malloc (size_t size);功能:向内存的堆区申请一块连续可用的空间,并返回指向这块空间的起始地址。
-
如果开辟成功,则返回这块空间的起始地址。
-
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
-
返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
-
如果参数size为0,malloc的行为标准是未定义的,取决于编译器。
// malloc 代码演示
#include <stdlib.h>
int main()
{
// 申请20个字节的空间,用来存放5个整数
int* p = (int*)malloc(20);
// 判断空间是否申请成功
if (p == NULL)
{
printf("申请失败");
}
// 申请成功// 使用空间 for (int i = 0; i < 5; i++) { *(p + i) = 1 + i; } for (int i = 0; i < 5; i++) { printf("%d ", p[i]); } return 0;
}
2.2 free
C语言提供了另一个函数free,专门用来做动态内存的释放和回收的。
代码块
void free (void* ptr);
free函数是专门用来释放动态开辟的内存。
如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数ptr是NULL指针,则函数什么事都不做。
接上面的代码:
free(p);
p = NULL;
3. calloc和realloc
3.1 calloc
calloc函数也是向内存申请空间的,但是它会将初始化为0。
代码块
void* calloc (size_t num, size_t size);
函数的功能是为num个大小的sizeof元素开辟一个空间,并且把每个字节初始化为0.
与malloc的区别在于calloc会在返回地址之前把申请的空间的每个字节初始化为0.
#include <stdio.h>
#include <stdlib.h>
// calloc 代码演示
int main()
{
int* p = calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
// 使用空间
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p);
p = NULL;
return 0;
}
3.2 realloc
realloc函数的出现让动态内存管理更加灵活。
有时候我们会发现过去申请的空间太小了又或是太大了,为了合理的使用内存,我们会对内存的大小进行调整,realloc函数就可以做到对动态开辟内存的调整。
代码块
void* realloc (void* ptr, size_t size);
参数:
- ptr是要调整的内存地址
- size调整之后的新大小,单位:字节
返回值:
- 返回值是调整之后的内存的起始位置
注意事项:
- 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
代码演示
int main()
{
int* p = malloc(5 * sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
// 使用空间
for (int i = 0; i < 5; i++)
{
printf("%d ", p[i]);
}
// 空间突然不够用,需要存放6~10
int * p2 = realloc(p, 40);
if (p2 == NULL)
{
perror("realloc");
free(p);
p = NULL;
return 1;
}
p = p2;
// 使用新的空间
for (int i = 5; i < 10; i++)
{
p[i] = i + 1;
}
// 释放空间
free(p);
p = NULL;
return 0;
}
4. 常见的动态内存的错误
4.1 对NULL的解引用操作
// 1. 对NULL的解引用操作
int main()
{
int* p = (int*)malloc(INT_MAX);
/*if (p == NULL)
{
return 1;
}*/
for (int i = 0; i < 10; i++)
{
p[i] = i + 1;
}
free(p);
p = NULL;
return 0;
}
我们正确的代码是在使用空间前进行判断p是否为NULL,如果我们不判断,malloc函数申请空间也不是100%成功的,万一造成空指针的解引用,不就发生错误了。
一般情况下,比较猴急的小伙伴会犯这样的错误。
4.2 对动态开辟内存空间的越界访问
// 2.对动态开辟内存空间的越界访问
int main()
{
int* p = (int*)malloc(5*sizeof(int));
if (p == NULL)
{
return 1;
}
for (int i = 0; i < 10; i++) // 越界访问
{
p[i] = i + 1;
}
free(p);
p = NULL;
return 0;
}
4.3 对非动态开辟内存使用free释放
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
// 使用
// ...
free(p);
p = NULL;
return 0;
}
free只能释放动态开辟内存空间。
4.4 使用free释放动态开辟内存的一部分
int main()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 5; i++)
{
*p = i + 1;
p++;
}
free(p);
p = NULL;
return 0;
}
这段代码是运行不了的。
4.5 对同一块动态内存多次释放
void* test()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
// 使用
free(p);
}
int main()
{
int *p = test();
free(p);
p = NULL;
}
释放了多次,系统会崩溃。
4.6 (仅使用不释放)内存泄露
void* test()
{
int* p = (int*)malloc(10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
}
int main()
{
test();
//
//
}
5. 动态内存笔试题分析
5.1 练习1
void GetMefmory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMefmory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
这段代码打印了什么?
这段程序崩溃了,什么也打印不出。
原因:1.解引用空指针。str传递给GetMemory函数的时候,采用的是传值调用,形参p其实是str的一份拷贝,当我们把malloc申请的空间的起始地址存放在p中时,不会修改str,str依然为NULL。
2.没有free释放空间存在内存泄漏。
正确写法:
void GetMefmory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMefmory(&str);
strcpy(str, "hello world"); // 把hello world 拷贝到 str中
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
5.2 练习2
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
这段代码会不会打印hello world呢?
不会,会打印出一堆随机值。为什么呢?我们来画图分析。

正确写法:
char* GetMemory(void)
{
static char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
5.3 练习3
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;
}
只有一个错误:没有释放,内存泄露。
6. 柔性数组
C99中,结构中的最后一个元素允许时未知大小的数组,这就叫做柔性数组成员。例如:
struct st_type
{
int i;
int a[]; // 柔性数组成员
};
6.1 柔性数组的特点
- 结构中的柔性数组成员前面,必须至少一个其他成员
- sizeof返回这种结构大小不包括柔性数组的内存
- 包含柔性数组的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如:

6.2 柔性数组的使用
struct S
{
int n;
int arr[]; // 柔性数组成员
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 10 * sizeof(int));
if (ps == NULL)
{
perror("malloc");
return 1;
}
// 使用空间
ps->n = 100; // 给n赋值100
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = 1 + i;
}
// 调整空间
struct S* tem = realloc(ps, sizeof(struct S) + 20 * sizeof(int));
if (tem == NULL)
{
perror("realloc");
return 1;
}
// 使用空间
// ...
free(ps);
ps = NULL;
return 0;
}
6.3 柔性数组的优势
上面我们写了柔性数组的使用,这种情况还有一种写法。
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
// 使用空间
ps->n = 100; // 给n赋值100
int* ptr = (int*)malloc(10 * sizeof(int));
if (ptr == NULL)
{
perror("malloc");
return 1;
}
ps->arr = ptr;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i + 1;
}
// 调整空间
// 使用空间
// ...
free(ptr);
free(ps);
ps = NULL;
return 0;
}
这段代码和上面代码比较一下。
柔性数组更加方便且不容易出错,下面这段代码free了两次,容易发生空间泄露的问题。同时柔性数组减少了内存碎片,更加安全。