
我走的并不快,但会一直走,会一直加油
文章目录
动态内存管理
前提知识
1.头文件
- 本章提到的"free,malloc,calloc,realloc"都是在"stdlib.h"这个头文件中
2.传空指针NULL/野指针
-
所有标准 C 的字符 / 字符串 / 内存函数,传入++空指针
NULL/野指针++,行为都是「未定义」,程序大概率直接崩溃- free(NULL) 合法
realloc(NULL, size)等价于malloc(size),但不等价于calloc(calloc 会初始化 0,realloc 不会)
-
空指针解引用非法
3.判空操作
-
perror的用法:
- 头文件:只需要stdio.h
- 使用时在后面加上括号和一个双引号字符串,如果出错,会自动打印出"字符串+冒号+错误信息"
- 比如:perror("错误是")
- perror("malloc")------在malloc后面用,意为"malloc的错误是xxx "
-
判空操作:
-
在使用malloc或者calloc或realloc后,都要对得到的指针进行判空,标准如下:
cppif(p == NULL) { perror("malloc");//假设是通过malloc创建了指针p对应的内存 return 1;//返回1代表程序异常失败 } else { //进行操作 } -
动态内存管理
(1)free
1.使用方式
-
必须在「最后一次使用这块内存之后」,「指针失效之前」调用
-
free(指针)
- 其中的指针是malloc,calloc或realloc这些动态内存函数申请的空间对应的首地址
- 如果申请失败 就没有内存,不需要释放
- 可以传NULL
- 其中的指针是malloc,calloc或realloc这些动态内存函数申请的空间对应的首地址
-
在使用完free后,对应的指针变成野指针 ,这时候要将该指针置为NULL
cppfree(p); p=NULL;
(2)malloc
1.英文
- memory allocate:内存分配
2.使用方式
-
void* malloc (size_t size);
- 后面括号直接加上想创建的字节长度
- 返回值是泛型指针,如果想赋给其他类型的指针,在赋值之前需要强制类型转换
-
使用完成后需判断是否使用成功,成功了就继续,没成功(空指针NULL)就退出程序(return 1)
-
示例1:
cpp#include<stdio.h> #include<stdlib.h> int main() { int* pa = (int*)malloc(40);//直接申请40个字节的空间 if (pa == NULL) { perror("malloc"); return 1; } else { for (int i = 0; i < 10; i++) { pa[i] = i;//pa是地址,相当于数组名,pa[i]相当于" *(pa+i)" } } for (int i = 0; i < 10; i++) { printf("%d ", pa[i]);//pa是地址,相当于数组名,pa[i]相当于" *(pa+i)" } printf("\n"); free(pa); pa = NULL; return 0; } -
示例2:
使用malloc函数模拟开辟一个3*5的整型二维数组,开辟好后,使用二维数组的下标访问形式,访问空间
不要忘了判空和最后 释放内存

(3)calloc
1.英文
- contiguous allocate:连续分配
- clear allocate:清零分配
2.和malloc的差别
-
传参改变:void* calloc (size_t num, size_t size);
变成了两个参数:
- 想创建几个:num
- 每个几个字节:size
-
创建的指针对应的内存会初始化为全0;
(4)realloc
1.英文
- reallocate:重新分配内存
2.注意事项
-
void* realloc (void* ptr, size_t size);
- 也是泛型指针,需要在前面强制类型转换
- realloc传参,第一个参数是地址,第二个参数是想要改变成的大小(相当于malloc的参数)
-
realloc和前面提到的calloc和malloc一样,都可能会出现错误,所以使用之前要先判空:
3.两种功能
-
改变malloc或calloc创建的地址对应的内存空间的大小
-
(假设通过malloc/calloc创建的指针为a)
-
使用realloc时,先初始化一个新的指针(避免原指针丢失),
- 然后对新的指针判空,如果是空指针,就perror报个错并且return 1,在return之前,还要free(a),这个可千万别忘了,忘了会内存泄漏
-
如果生成成功,就将tmp传给a
cpp#include<stdio.h> #include<stdlib.h> //先使用calloc创建一个能存5个int的内存,然后放进去12345,然后通过realloc修改成能放10字节,然后再放6到10 int main() { int* a = (int*)calloc(5 ,sizeof(int)); //判断是否是空指针 if (a == NULL) { perror("calloc"); return 1; } else { //先打印看看是不是初始化为全0 printf(" 先打印看看是不是初始化为全0:\n"); for (int i = 0; i < 5; i++) { printf("%d ", a[i]); } printf("\n"); printf(" 将5个数字放进去:\n"); for (int i = 0; i < 5; i++) { a[i] = i + 1; printf("%d ", a[i]); } printf("\n"); //使用realloc,将长度变成十个字节 int* tmp = (int*)realloc(a, 10 * sizeof(int)); //判断是否是空指针 if (tmp == NULL) { perror("realloc"); free(a);//如果失败了,不要忘了把该释放和置空 return 1; } else { //这时候再将tmp的地址进行使用 a = tmp; tmp = NULL; printf("后面再放6到10:\n"); for (int i = 0; i < 5; i++) { a[i + 5] = i+6; } for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } } } //千万不要忘了释放和置空 free(a); a = NULL; return 0; } -
-
实现和malloc/calloc相同的功能:生成新的动态内存
- 只需要传参时,第一个参数是NULL即可
4.分类讨论
-
realloc使用失败
- 返回NULL,原内存保持不变------使用tmp并判空
-
成功
-
情况一:返回原地址
-
情况二:返回新的起始地址,原地址的内存会被释放
如何正确使用
- 使用tmp------
int* tmp = (int*)realloc(a, 10 * sizeof(int)); - 成功后修改原指针变量的指向------
a = tmp; - 将tmp置为空------
tmp = NULL;
- 使用tmp------
-
常见的错误
(1)创建失败,对空指针解引用
- 如果malloc/calloc/realloc创建失败会得到空指针NULL,对空指针解引用是非法现象
cpp
int* p=(int*)malloc( INT_MAX );//INT_MAX是整形类型的最大值,p是个空指针
*p=3;//对空指针解引用
(2)对内存越界访问
cpp
int* p = (int*)malloc(2 * sizeof(int));
for (int i = 0; i <= 2; i++)
{
p++;
printf("%d ", p[i]);
}
(3)free的对象不是动态开辟内存的首元素的指针
cpp
int* p = (int*)malloc(2 * sizeof(int));
p++;
free(p);
(4)对同一块动态开辟内存多次使用free
cpp
int* p = (int*)malloc(2 * sizeof(int));
free(p);
free(p);
(5)忘记释放动态开辟的内存
cpp
int* p = (int*)malloc(2 * sizeof(int));
for (int i = 0; i <= 2; i++)
{
p++;
printf("%d ", p[i]);
}
return 0;
(6)使用free释放非动态开辟的内存
cpp
int a=0;
int* p=&a;
free(p);
动态内存经典笔试题分析
(1)传值调用+没有free
cpp
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
int main()
{
Test();
return 0;
}
-
错误:
- p和str是两个指针变量,在GetMemory开始之时,二者本来相同,但是这个函数将p的值进行了改变,从而实现了malloc,但是和str没有关系
- 对p进行malloc后,没有free,导致"内存泄露"
-
两种修改方案:
-
GetMemory修改参数,改成传址调用,并且在Test中使用后要free并置空
cpp#include <stdio.h> #include <stdlib.h> #include<string.h> void GetMemory(char** p) { *p = (char*)malloc(100); } void Test(void) { char* str = NULL; GetMemory(&str); strcpy(str, "hello world"); printf(str); free(str);// 必须free,避免内存泄漏 str=NULL; // 置空,防止野指针 } int main() { Test(); return 0; } -
GetMemory直接返回malloc创造的地址
cpp#include <stdio.h> #include <stdlib.h> #include<string.h> char* GetMemory( ) { return (char*)malloc(100); } void Test(void) { char* str = GetMemory( );//没有参数,直接返回char*指针 strcpy(str, "hello world"); printf(str); free(str);// 必须free,避免内存泄漏 str=NULL; // 置空,防止野指针 } int main() { Test(); return 0; }
-
(2)访问局部变量的指针
cpp
#include <stdio.h>
#include <stdlib.h>
char* GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
char p[] = "hello world";只在函数中存在,是局部的,函数使用完后自动销毁,所以p从GetMemory传回去后已经是空指针NULL了,不能打印空指针
(3)没有free
cpp
#include <stdio.h>
#include <stdlib.h>
#include<string.h>
void GetMemory(char** p, int num)
{
*p = (char*)malloc(num);//后面没有free
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
int main()
{
Test();
return 0;
}
-
p = (char)malloc(num);//后面没有free
-
应该将test改为
cppvoid Test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); free(str); str = NULL; }
-
(4)free完后成为野指针
cpp
#include <stdio.h>
#include <stdlib.h>
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
-
野指针不是NULL,if语句执行,strcpy访问野指针,非法访问内存
-
修改(free完了要置空):
cppfree(str); str=NULL;
柔性数组
1.是什么
-
结构体中,末尾的一个"无长度"的数组,能用 malloc /calloc/realloc进行大小修改
cppstruct st_type { int i; int a[0];//柔性数组成员 //如果报错,可以写成int a[] };
2.特点
-
柔性数组必须在结构体最后一位
-
前面必须有结构体成员
-
可以使用malloc/calloc/realloc分配内存
- 分配的内存必须大于不算上柔性数组的大小
-
sizeof算不到柔性数组的大小

- 我觉得这是为了在对柔性数组进行大小创建的时候更方便,如果我想直接让柔性数组大小为3个int,可以直接写成:
struct st_type* tmp = (struct st_type*) malloc( sizeof(s)+3*sizeof(int) );
- 我觉得这是为了在对柔性数组进行大小创建的时候更方便,如果我想直接让柔性数组大小为3个int,可以直接写成:
3.优点
为啥不在结构体中直接放一个数组(的首元素地址)呢?
-
两种方式的比较:
-
使用柔性数组
cppstruct st_type { int i; int a[];//柔性数组成员 } ; int main() { ///直接通过柔性数组的方式进行创建 柔性数组有100个int ///将结构体中的i设为100 ///将柔性数组遍历赋值 struct st_type* s = (struct st_type*)malloc(sizeof(struct st_type) + 100 * sizeof(int)); s->i = 100; for (int j = 0; j < 100; j++) { (s->a)[j] = j;///s->a:拿到柔性数组a的首地址 ; (s->a)[j]:访问柔性数组a中的元素 } free(s); s = NULL; return 0; } -
结构体中放数组(首元素地址)
*cppstruct st_type { int i; int *a;///通过malloc直接创建数组 } ; int main() { struct st_type* ps = (struct st_type*)malloc( sizeof(struct st_type) ); ps->i = 100; ps->a = (int*)malloc(100 * sizeof(int)); for (int j = 0; j < 100; j++) { ps->a[j] = j; } free(ps->a); ps->a = NULL; free(ps); ps = NULL; return 0; }
-
- 优点:
- 做⼀次free就可以把所有的内存也给释放掉,放心
- 有益于提⾼访问速度,也有益于减少内存碎⽚。
C/C++中程序内存区域划分
-
栈区**(stack)**:局部变量,函数参数、返回数据、返回地址等
- 效率高
- 容量有限
-
堆区(heap): 存放通过 malloc() 等动态分配的内存
- ⼀般由程序员分配释放, 若程序员不释放,程序结束时可能由OS(操作系统)
回收 。分配⽅式类似于链表。
-
数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放。
-
代码段:存放函数体(类成员函数和全局函数)的⼆进制代码