14/100
发布文章
加粗
斜体
标题
删除线
无序
有序
待办
引用
代码块
运行代码
图片
视频
表格
超链接
投票
导入
导出
保存
撤销
重做
模版
使用富文本编辑器
目录
发文助手
语法说明
[1.为什么会存在动态内存分配?](#1)
[2. 动态内存函数的介绍](#2)
- [2.1 malloc函数和free函数](#21)
- [2.2 calloc函数](#22)
- [2.3 realloc](#23)
[ 3. 常见的动态内存错误](#3)
- [3.1 对NULL指针的解引用操作](#31)
- [3.2 对动态开辟空间的越界访问](#32)
- [3.3 对非动态开辟内存使用free释放](#33)
- [3.4 使用free释放一块动态开辟内存的一部分](#34)
- [3.5 对同一块动态内存多次释放](#35)
- [3.6 动态开辟内存忘记释放(内存泄漏)](#36)
<h1 id ="1">1.为什么会存在动态内存分配?
我们现在知道的开辟内存方式是创建变量,创建数组。而这些东西是在栈区上开辟空间的,一旦创建完成,是无法更改的,是固定死的。就像数组,在创建数组时,已经指定了数组的大小,编译后无法再更改。
这时候就需要**动态内存分配**。
<h1 id = "2">2. 动态内存函数的介绍
<h1 id = "21">2.1 malloc函数和free函数
**1.malloc函数**
学习新函数时,就从库里面找函数的声明,解析来学习。
```cpp
void* malloc (size_t size);
```
该函数的功能是,向堆区申请一块size个字节大小的空间。
**如果申请成功,返回申请的空间的起始地址,如果申请失败,返回空指针NULL。** 至于malloc函数在库里的为什么是void*,这是因为malloc函数也不知道使用者需要这块空间来存放什么,具体要什么类型的指针,使用者自己决定。
**如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。**
下面举个例子来深入了解:

在这段代码中,我们用了malloc函数来社区内40个字节的空间,(一个int是4个字节,*10是40字节)
但是这样写是不对的,前面说过,malloc是向内存中的堆区申请空间,如果申请失败,就返回NULL,如果现在不判断p的值,直接使用的话,就会出问题,所以我们应该判断p的有效性。

顺便提一下,perror是一个报错的函数,假如申请空间失败返回NULL,perror会报告相应的错误信息。来演示一下:
假如申请INT_MAX个字节的空间:

这是一个极大的数字,看看是否能够申请成功。

在这里,perror报告错误:没有足够的空间。具体的用法请学习一下。
<h1 id ="0">free函数
**free函数和malloc函数是成对出现的。**
free函数是专门用来释放申请的空间的。
具体用法如下:
```cpp
int main()
{
int* p = (int *)malloc(sizeof(int)*10);
if (p == NULL)
{
perror("malloc");
}
free(p);
p = NULL;
return 0;
}
```
在申请完空间之后,如果不用了,就需要释放掉,把申请的空间还给操作系统,即 有借有还,再借不难 的道理。
**总结:malloc函数是向堆区申请一块空间,如果申请成功,则返回该空间的首地址,如果申请失败,则返回NULL。**
**free函数是将申请的空间释放掉**
<h1 id ="22">2.2 calloc函数
calloc函数也是向堆区中申请内存的函数。具体参数如下:
```cpp
void* calloc (size_t num, size_t size);
```
第一个参数是申请的元素个数,第二个参数是申请的每个元素大小。
**注意:calloc为元素数组分配一个内存块,并将其所有位初始化为零。**
**也就是说,calloc函数不仅申请空间,还将该空间初始化为0。**
举个例子来证明:
```cpp
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
```
结果如下:

结果就是:将申请的空间自动初始化为0。
**calloc函数与malloc函数类似,只是malloc函数只负责申请空间,没有初始化,而calloc函数会申请空间并初始化。**
不过也不是说calloc函数比malloc函数更高级,calloc函数 初始化也会花费时间,malloc函数不初始化,所以它也节省了一部分时间。
**每个函数之间各有优缺点。**
所以在使用函数的时候,结合实际情况来使用。
<h1 id = "23"> 2.3 realloc函数
realloc函数,让我们动态申请的空间更加灵活。
当我们觉得动态申请的空间太大或者太小时,可以用realloc函数来调整动态申请的空间大小。
realloc函数的原型如下:
```cpp
void* realloc (void* ptr, size_t size);
```
**ptr是需要重新调整的空间的起始地址,**
**size 调整之后新大小**
注意:**是调整后的大小**
假如刚开始动态申请的空间是40字节,发现不够用了,想加大10个字节的空间,那么使用realloc函数重新调整空间时,参数就是50。
**realloc函数的返回值是调整后的空间的起始地址。**
既然返回调整后的起始地址,那会不会出现申请失败的情况?会不会出现空间不足以申请的情况?
会的。
**情况1**: 重新申请空间时,假如后面的空间足够大,那么realloc函数就会返回空间的首地址。原来有的数据不会变化:

**情况2: 如果重新申请空间失败,则返回NULL。**
**情况3:****如果在原来的空间后面没有足够大的空间来增容,realloc函数会**自动**在内存中的其他位置找一块满足我们要求的空间,并把原空间的数据拷贝到新空间中,然后返回新空间的起始地址。**

基于情况2和情况3,我们是不是用原空间的指针来接收呢?
```cpp
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return ;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
不够了,增加空间
int* p = (int*)realloc(p, sizeof(int) * 10);
```
还是刚才的例子,上面这段代码对吗?
不对。
因为在重新realloc调整空间的时候,如果用原空间的地址来接收的话,万一申请失败呢?
如果申请失败,realloc函数会返回一个NULL,如果用p来接收的话,p原来指向的空间的数据就丢失了!就找不到了。
所以不能用原空间p来接收realloc返回的地址,我们需要用一个临时指针来接收返回的地址。
```cpp
int* ptr = (int*)realloc(p, sizeof(int) * 10);
```
这样写才是正确的,然后判断ptr是否为空,如果不为空,再把这个ptr存的地址赋给p。
```cpp
if (ptr != NULL)
{
p = ptr;
ptr = NULL;(防止ptr成为野指针)
}
```
以上就是三个申请空间的函数的基本情况,根据需求,选择不同的函数。
<h1 id = "3">3. 常见的动态内存错误
<h1 id = "31">3.1 对NULL指针的解引用操作
```cpp
void test()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;
free(p);
}
int main()
{
test();
return 0;
}
```
这段代码的错误是,没有判断p的值就对p进行解引用,如果申请的空间失败,返回NULL,对NULL进行*操作,是非法的。
改正很简单:只需判断p是否为NULL即可。
```cpp
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
if (p == NULL)
{
perror("malloc");
return;
}
*p = 20;
free(p);
p = NULL;
}
```
改正结果如上。
<h1 id = "32">3.2 对动态开辟空间的越界访问
```cpp
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return;
}
for (i = 0; i < 20; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
```
上面这段代码的错误是,malloc函数只申请了10个整型大小的空间,(40)字节,但是在赋值的时候,i的范围取到了20,造成对后面的空间非法访问。
改正如下:只需把i的范围调整到i<10即可,或者最初申请的空间到20个整型大小(80字节)
```cpp
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return;
}
for (i = 0; i < 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
p = NULL;
}
```
<h1 id = "33">3.3 对非动态开辟内存使用free释放
```cpp
void test()
{
int a = 10;
int *p = &a;
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
```
上面代码的错误在于,a是一个变量,在内存中的栈区创建,不是用malloc等函数申请出来的空间,后面free(p)的时候,free释放的是堆区上申请的空间,明显两者有差异。释放是非法的。

运行时也产生错误,并且关掉这个程序会很卡顿。
改正方法有多种,可以把free(p)去掉。
<h1 id = "34">3.4 使用free释放一块动态开辟内存的一部分
```cpp
void test()
{
int* p = (int*)malloc(100);
if(p == NULL)
{
perror("malloc");
return;
}
p++;
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
```
上面代码的错误在于,申请的100字节的空间,用p来接收,但是p又++了,此时p不再指向申请的空间的起始地址,释放的时候,只释放一部分,剩下的空间没有释放完,造成**内存泄漏**。

运行时一样会报错。
所以申请的空间的起始地址不能丢失。
<h1 id = "35">3.5 对同一块动态内存多次释放
```cpp
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
int main()
{
test();
return 0;
}
```
重复释放也会出现错误。

运行时依然报错。
<h1 id = "36">3.6 动态开辟内存忘记释放(内存泄漏)
```cpp
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
```
在test函数内部申请的空间,除了test函数后,没有返回值,意味着申请的这块空间丢失了,没有人记得这块空间的存在,造成了内存泄露!
关于动态内存,你学会了吗?学会了不妨关注我吧!
- 3.1 对NULL指针的解引用操作
- 3.2 对动态开辟空间的越界访问
- 3.3 对非动态开辟内存使用free释放
- 3.4 使用free释放一块动态开辟内存的一部分
- 3.5 对同一块动态内存多次释放
- 3.6 动态开辟内存忘记释放(内存泄漏)
1.为什么会存在动态内存分配?
我们现在知道的开辟内存方式是创建变量,创建数组。而这些东西是在栈区上开辟空间的,一旦创建完成,是无法更改的,是固定死的。就像数组,在创建数组时,已经指定了数组的大小,编译后无法再更改。
这时候就需要动态内存分配。
2. 动态内存函数的介绍
2.1 malloc函数和free函数
1.malloc函数
学习新函数时,就从库里面找函数的声明,解析来学习。
void* malloc (size_t size);
该函数的功能是,向堆区申请一块size个字节大小的空间。
如果申请成功,返回申请的空间的起始地址,如果申请失败,返回空指针NULL。 至于malloc函数在库里的为什么是void*,这是因为malloc函数也不知道使用者需要这块空间来存放什么,具体要什么类型的指针,使用者自己决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
下面举个例子来深入了解:
在这段代码中,我们用了malloc函数来社区内40个字节的空间,(一个int是4个字节,*10是40字节)
但是这样写是不对的,前面说过,malloc是向内存中的堆区申请空间,如果申请失败,就返回NULL,如果现在不判断p的值,直接使用的话,就会出问题,所以我们应该判断p的有效性。
顺便提一下,perror是一个报错的函数,假如申请空间失败返回NULL,perror会报告相应的错误信息。来演示一下:
假如申请INT_MAX个字节的空间:
这是一个极大的数字,看看是否能够申请成功。
在这里,perror报告错误:没有足够的空间。具体的用法请学习一下。
free函数
free函数和malloc函数是成对出现的。
free函数是专门用来释放申请的空间的。
具体用法如下:
int main()
{
int* p = (int *)malloc(sizeof(int)*10);
if (p == NULL)
{
perror("malloc");
}
free(p);
p = NULL;
return 0;
}
在申请完空间之后,如果不用了,就需要释放掉,把申请的空间还给操作系统,即 有借有还,再借不难 的道理。
总结:malloc函数是向堆区申请一块空间,如果申请成功,则返回该空间的首地址,如果申请失败,则返回NULL。
free函数是将申请的空间释放掉
2.2 calloc函数
calloc函数也是向堆区中申请内存的函数。具体参数如下:
void* calloc (size_t num, size_t size);
第一个参数是申请的元素个数,第二个参数是申请的每个元素大小。
注意:calloc为元素数组分配一个内存块,并将其所有位初始化为零。
也就是说,calloc函数不仅申请空间,还将该空间初始化为0。
举个例子来证明:
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
}
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
结果如下:
结果就是:将申请的空间自动初始化为0。
calloc函数与malloc函数类似,只是malloc函数只负责申请空间,没有初始化,而calloc函数会申请空间并初始化。
不过也不是说calloc函数比malloc函数更高级,calloc函数 初始化也会花费时间,malloc函数不初始化,所以它也节省了一部分时间。
每个函数之间各有优缺点。
所以在使用函数的时候,结合实际情况来使用。
2.3 realloc函数
realloc函数,让我们动态申请的空间更加灵活。
当我们觉得动态申请的空间太大或者太小时,可以用realloc函数来调整动态申请的空间大小。
realloc函数的原型如下:
void* realloc (void* ptr, size_t size);
ptr是需要重新调整的空间的起始地址,
size 调整之后新大小
注意:是调整后的大小
假如刚开始动态申请的空间是40字节,发现不够用了,想加大10个字节的空间,那么使用realloc函数重新调整空间时,参数就是50。
realloc函数的返回值是调整后的空间的起始地址。
既然返回调整后的起始地址,那会不会出现申请失败的情况?会不会出现空间不足以申请的情况?
会的。
情况1: 重新申请空间时,假如后面的空间足够大,那么realloc函数就会返回空间的首地址。原来有的数据不会变化:
情况2: 如果重新申请空间失败,则返回NULL。
情况3:****如果在原来的空间后面没有足够大的空间来增容,realloc函数会自动在内存中的其他位置找一块满足我们要求的空间,并把原空间的数据拷贝到新空间中,然后返回新空间的起始地址。
基于情况2和情况3,我们是不是用原空间的指针来接收呢?
int main()
{
int* p = (int*)malloc(sizeof(int) * 5);
if (p == NULL)
{
perror("malloc");
return ;
}
int i = 0;
for (i = 0; i < 5; i++)
{
*(p + i) = i;
}
不够了,增加空间
int* p = (int*)realloc(p, sizeof(int) * 10);
还是刚才的例子,上面这段代码对吗?
不对。
因为在重新realloc调整空间的时候,如果用原空间的地址来接收的话,万一申请失败呢?
如果申请失败,realloc函数会返回一个NULL,如果用p来接收的话,p原来指向的空间的数据就丢失了!就找不到了。
所以不能用原空间p来接收realloc返回的地址,我们需要用一个临时指针来接收返回的地址。
int* ptr = (int*)realloc(p, sizeof(int) * 10);
这样写才是正确的,然后判断ptr是否为空,如果不为空,再把这个ptr存的地址赋给p。
if (ptr != NULL)
{
p = ptr;
ptr = NULL;(防止ptr成为野指针)
}
以上就是三个申请空间的函数的基本情况,根据需求,选择不同的函数。
3. 常见的动态内存错误
3.1 对NULL指针的解引用操作
void test()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;
free(p);
}
int main()
{
test();
return 0;
}
这段代码的错误是,没有判断p的值就对p进行解引用,如果申请的空间失败,返回NULL,对NULL进行*操作,是非法的。
改正很简单:只需判断p是否为NULL即可。
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
if (p == NULL)
{
perror("malloc");
return;
}
*p = 20;
free(p);
p = NULL;
}
改正结果如上。
3.2 对动态开辟空间的越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return;
}
for (i = 0; i < 20; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
上面这段代码的错误是,malloc函数只申请了10个整型大小的空间,(40)字节,但是在赋值的时候,i的范围取到了20,造成对后面的空间非法访问。
改正如下:只需把i的范围调整到i<10即可,或者最初申请的空间到20个整型大小(80字节)
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
perror("malloc");
return;
}
for (i = 0; i < 10; i++)
{
*(p + i) = i;//当i是10的时候越界访问
}
free(p);
p = NULL;
}
3.3 对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
上面代码的错误在于,a是一个变量,在内存中的栈区创建,不是用malloc等函数申请出来的空间,后面free§的时候,free释放的是堆区上申请的空间,明显两者有差异。释放是非法的。
运行时也产生错误,并且关掉这个程序会很卡顿。
改正方法有多种,可以把free§去掉。
3.4 使用free释放一块动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
if(p == NULL)
{
perror("malloc");
return;
}
p++;
free(p);
p = NULL;
}
int main()
{
test();
return 0;
}
上面代码的错误在于,申请的100字节的空间,用p来接收,但是p又++了,此时p不再指向申请的空间的起始地址,释放的时候,只释放一部分,剩下的空间没有释放完,造成内存泄漏。
运行时一样会报错。
所以申请的空间的起始地址不能丢失。
3.5 对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
int main()
{
test();
return 0;
}
重复释放也会出现错误。
运行时依然报错。
3.6 动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
在test函数内部申请的空间,除了test函数后,没有返回值,意味着申请的这块空间丢失了,没有人记得这块空间的存在,造成了内存泄露!
关于动态内存,你学会了吗?学会了不妨关注我吧!
语法说明
标题
文本样式
列表
图片
链接
目录
代码片
表格
注脚
注释
自定义列表
LaTeX 数学公式
插入甘特图
插入UML图
插入Mermaid流程图
插入Flowchart流程图
插入类图
快捷键
标题复制
# 一级标题
## 二级标题
### 三级标题
#### 四级标题
##### 五级标题
###### 六级标题
1.为什么会存在动态内存分配?
2. 动态内存函数的介绍
2.1 malloc函数和free函数
free函数
2.2 calloc函数
2.3 realloc函数
3. 常见的动态内存错误
3.1 对NULL指针的解引用操作
3.2 对动态开辟空间的越界访问
3.3 对非动态开辟内存使用free释放
3.4 使用free释放一块动态开辟内存的一部分
3.5 对同一块动态内存多次释放
3.6 动态开辟内存忘记释放(内存泄漏)
Markdown 5373 字数 406 行数 当前行 1, 当前列 0
HTML 4136 字数 253 段落
表格