
目录
[2.2 malloc和free函数](#2.2 malloc和free函数)
[2.2.2 malloc和free函数的使用](#2.2.2 malloc和free函数的使用)
[2.2.4 总结](#2.2.4 总结)
[2.3 calloc函数](#2.3 calloc函数)
[2.3.1 calloc函数的使用](#2.3.1 calloc函数的使用)
[2.3.2 代码解析](#2.3.2 代码解析)
[2.3.3 malloc和calloc的区别](#2.3.3 malloc和calloc的区别)
[2.4 realloc函数](#2.4 realloc函数)
[2.4.1 realloc函数是用来干什么的?](#2.4.1 realloc函数是用来干什么的?)
[2.4.2 realloc函数的使用](#2.4.2 realloc函数的使用)
[3.2 对动态开辟空间的越界访问](#3.2 对动态开辟空间的越界访问)
[3.3 对非动态内存开辟空间使用free释放](#3.3 对非动态内存开辟空间使用free释放)
[3.4 使用free释放一块动态开辟空间内存的一部分](#3.4 使用free释放一块动态开辟空间内存的一部分)
[3.5 对一块动态内存多次释放](#3.5 对一块动态内存多次释放)
[3.6 动态开辟内存忘记释放(内存释放)](#3.6 动态开辟内存忘记释放(内存释放))
[4.1 题目一](#4.1 题目一)
[4.1.1 错误解析](#4.1.1 错误解析)
[4..1.2 代码修改](#4..1.2 代码修改)
[4.1.3 一个小疑惑](#4.1.3 一个小疑惑)
[4.2 题目二](#4.2 题目二)
[4.2.1 错误解析](#4.2.1 错误解析)
[4.3 题目三](#4.3 题目三)
[4.4 题目四](#4.4 题目四)
[5.1 柔性数组的定义](#5.1 柔性数组的定义)
[5.2 柔性数组的特点](#5.2 柔性数组的特点)
[5.4 柔性数组的优势](#5.4 柔性数组的优势)
1.为什么存在动态内存管理
cpp
int a=10;//固定的向内存申请4个字节的空间
int arr[10];//申请连续的空间,大小为40字节
缺点:一旦空间申请好,空间大小不会改变,我们在后续想要存储更多的内容,存储空间
会不够
那么动态内存管理的好处就来了:它可以让空间与内容适配度更高,实现利用的最大化
2.动态内存函数的介绍
2.1动态内存函数的位置介绍

我们的动态内存函数的位置是在堆区

C/C++程序内存分配的几个区域:
- 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等
- 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
- 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
- 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
2.2 malloc和free函数


2.2.1为什么指针类型是void*呢?
解答:malloc和free函数不知道申请的空间是用来存放什么数据的,所以在后续使用时,进行强制类型转换。
2.2.2 malloc和free函数的使用
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main()
{
//开辟一个空间,大小为40个字节,用来存储10个整型数据
int* p = (int*)malloc(40);
//检查空间是否开辟成功
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
//malloc开辟空间,但未把空间初始化
//赋值
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i) = i;
}
//打印
for (i = 0;i < 10;i++)
{
printf("%d ", *(p + i));
}
//归还空间
free(p);
//将p置为NULL,防止越界访问
p = NULL;
return 0;
}
2.2.3代码解析
cpp
//开辟一个空间,大小为40个字节,用来存储10个整型数据
int* p = (int*)malloc(40);
a.此时我们已经确定好开辟空间是用来存放什么数据的,我们就可以强制类型转化一下。
b.对于开辟空间的大小,不能太大,否则很容易出现开辟空间失败的情况
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
//开辟一个空间,大小为40个字节,用来存储10个整型数据
int* p = (int*)malloc(9999999999999);
//检查空间是否开辟成功
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
return 0;
}
输出结果

cpp
//检查空间是否开辟成功
if (p == NULL)
{
printf("%s\n", strerror(errno));
return 1;
}
a.为什么我们要检查一下空间是否开辟成功呢?
解答:若空间未开辟成功,那么p就是野指针,很危险
b.检查空间是否开辟成功
如果没有开辟成功,我需要看一下是为什么没有开辟成功,我们在这里不但可以使用strerror函数,还可以使用perror函数
cpp
perror("malloc");
cpp
//赋值
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i) = i;
}
为什么我们还要主动对其进行赋值呢?
解答:因为malloc函数在开辟空间时,它知到开辟空间的总大小,并不知道这个空间我们是怎么分配的,用来干什么的,那么这是就需要我们主动进行赋值
cpp
//归还空间
free(p);
//将p置为NULL,防止越界访问
p = NULL;
a.为什么我们要主动归还空间呢?
解答:就好比我们在图书馆借书,我们看完书之后一直不还,我们就占用了资源
空间也一样,如果我们不主动还,有时操作系统也不会主动回收,那么空间就会被浪费掉。
b.为什么我们还要主动将指针p置为NULL呢?
解答:我们在将空间释放掉是,指针p任指向原来的位置,这是p变成了野指针,很危险
2.2.4 总结
- 如果开辟成功,则返回一个开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针(因此malloc函数开辟空间是一定要检查)
- 返回值类型为void*,所以malloc函数并不知道开辟空间的类型,具体由作者自己决定
- 如果size为0,那么malloc行为是标准未定义的行为,大小取决于编译器
- 如果ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果ptr是NULL指针,则函数什么事都不用做
2.3 calloc函数

num:开辟元素的个数
size 每个元素的大小
2.3.1 calloc函数的使用
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
//开辟一个存储10个整型数据的空间
int* p = (int*)calloc(10, sizeof(int));
//检查空间是否开辟成功
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用开辟的空间
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *(p + i));
}
//释放空间
free(p);
p = NULL;
return 0;
}
2.3.2 代码解析
cpp
//使用开辟的空间
int i = 0;
for (i = 0;i < 10;i++)
{
printf("%d ", *(p + i));
}
为什么这里我们空间开辟之后就被初始化了呢?
解答:calloc函数在开辟空间时,就表明了是几个元素,是什么类型的,那么此时操作系统知到我们的意图,自动棒我们初始化了元素。
2.3.3 malloc和calloc的区别
- malloc函数:在开辟空间时只说明里总大小,因此空间没有被初始化,直接返回起始地址
- calloc函数:在空间时表明了元素个数和每个元素的大小,因此所有元素被初始化为0,然后返回起始地址
2.4 realloc函数

ptr: 要调整的内存地址
size:调整之后的空间大小
2.4.1 realloc函数是用来干什么的?
- 有时我们会出现申请空间不合理的情况,如果申请空间太大会浪费掉我们的空间,如果申请空间太小,我们没办法存储完我们的数据,为了更加准确的使用空间,我们一定会对内存的大小进行灵活的调整,那么进行这个操作的函数就是 --realloc函数。
- realloc函数的返回值是调整之后内存的起始位置
- realloc函数在调整原内存空间大小的基础上,还会将原来内存中的数据,移动到新的空间去
2.4.2 realloc函数的使用
cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
//在内存中开辟空间,大小为5个整型数据的大小
int* p = (int*)malloc(5 * sizeof(int));
//检查空间是否开辟成功
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
//发现不够,需要再开辟5个整型数据大小的空间
int* ptr = (int*)realloc(p, 10 * sizeof(int));
//检查空间是否开辟成功
if (ptr != NULL)
{
p = ptr;
}
//继续使用空间
//释放空间
free(p);
p == NULL;
return 0;
}
2.4.3新空间是怎么加的呢?
情况一:原空间后的空间足够大,则在原空间后面追加
情况二:原空间后面的空间不足,在内存中重新找一块满足需求大小的空间,同时把原空间数据搬过来,并把原空间的数据释放掉,返回新空间起始地址

why:我们用了一个新指针,而不直接用原来的指针呢?
解答:如果我们直接用了原来的指针,那么我们存储的内容就会丢失
3.常见的动态内存错误
3.1对NULL指针解引用操作
cpp
// 对NULL指针解引用操作
int* p = (int*)malloc(100);
int i = 0;
for (i = 0;i < 10;i++)
{
*(p + i) = 0;
}
这里我们没有对malloc开辟空间是否成功,如果空间开辟失败,那么p就是野指针
3.2 对动态开辟空间的越界访问
cpp
//对动态开辟空间的越界访问
int* p = (int*)malloc(100);
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0;i < 100;i++)
{
*(p + i) = 0;
}
这里malloc是开辟了100个字节的空间(相当于25个整型数据的空间),而不是100个整型数据的空间,而在后续的赋值中,却要给100个整型数据赋值,这就造成了越界访问
3.3 对非动态内存开辟空间使用free释放
cpp
int a = 10;
int* p = &a;
free(p);
p = NULL;
free针对的是堆区,而p位于栈区

3.4 使用free释放一块动态开辟空间内存的一部分
cpp
//使用free释放一块动态开辟空间内存的一部分
int* p = (int*)malloc(100);
if (p == NULL)
{
perror("malloc");
return 1;
}
p++;
free(p);
p = NULL;
代码中p++之后p不再指向起始位置

3.5 对一块动态内存多次释放
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 分配动态内存
int *ptr = (int *)malloc(sizeof(int));
if (ptr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 第一次释放内存
free(ptr);
// 错误:再次释放同一块内存
free(ptr);
return 0;
}
当你使用malloc()(C语言)、calloc()、realloc()或者new(C++)等函数来分配动态内存时,系统会在堆上为你分配一块指定大小的内存区域,并返回一个指向该区域起始地址的指针。当你使用free()(C语言)或者delete(C++)来释放这块内存时,系统会将这块内存标记为可用,归还给操作系统,以便后续的内存分配使用。
如果再次尝试释放同一块已经被释放的内存,就会引发问题,因为系统已经认为这块内存不再被使用,再次释放可能会破坏内存管理数据结构,导致程序崩溃或者产生不可预测的结果。
3.6 动态开辟内存忘记释放(内存释放)
动态内存分配是指在程序运行时根据需要分配内存空间。在许多编程语言中,程序员需要手动管理这些动态分配的内存,即负责在不再使用时释放它们。如果忘记释放,就会造成内存泄漏。
4.几道经典的笔试题
4.1 题目一
cpp
void Getmemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
Getmemory(str);
strcpy(str, "hello world");
printf(str);
}
结果:程序会因为引发异常而挂掉

4.1.1 错误解析
- str传给指针p时,p是str创建的临时拷贝,有自己的独立空间,当GetMemory函数申请了空间后,地址放在p中时,str依然为空,当GetMemory函数返回之后,strcpy拷贝时,形成了非法访问
- 在GetMemory内部,动态申请了内存,但没有释放,会形成内存泄漏
4..1.2 代码修改
cpp
void Getmemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
Getmemory(&str);
strcpy(str, "hello world");
printf(str);
}
4.1.3 一个小疑惑
为什么 printf(str); 是正确的呢?
解答:str存储的相当于字符串的首地址
cpp
printf("hello\n");
这也相当于将hello这个字符串的h传给了printf
cpp
char* p="hello\n";
printf(p);
4.2 题目二
cpp
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}

4.2.1 错误解析
当出GetMemory函数后,空间被回收,str只记住了p空间的位置,当开始使用时却发现那块空间已经不见了,形成了非法访问内存,这也被叫做 返回栈空间地址问题
4.2.2 代码修改
cpp
char* GetMemory(void)
{
char* p = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
4.3 题目三
cpp
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.3.1错误解析
这段代码在开辟并使用完空间后,没有释放空间,造成了内存泄漏
4.3.2代码修改
cpp
void GetMemory(char* p, int num)
{
*p = (char*)malloc(num);
}
void Test(void)
{
char* str = NULL;
GetMemory(str, 100);
strcpy(str, "hello");
printf(str);
free(str);
str = NULL;
}
free(str);
str=NULL;
4.4 题目四
cpp
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
4.4.1错误解析
因为free(str);之后,str成为野指针,if(str != NULL)语句不起作用。
4.4.2代码修改
cpp
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
str=NULL;
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
5.柔性数组
5.1 柔性数组的定义
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
cpp
typedef struct type
{ int i;
int a[0];//柔性数组成员
}type_a;
有些编译器会报错无法编译可以改成:
cpp
typedef struct type
{ int i;
int a[];//柔性数组成员
}type_a;
二选一,那个不报错就用那个
5.2 柔性数组的特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
cpp
typedef struct type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
5.3柔性数组的使用
cpp
int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
p->a[i] = i;
}free(p);
这样柔性数组成员a,相当于获得了100个整型元素的连续空间。
5.4 柔性数组的优势
优势一:方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
优势二:这样有利于访问速度.
连续的内存有益于提高访问速度,也有益于减少内存碎片。