前言:
不管是C/C++都会大量的使用动态内存管理,并且使用C/C++实现数据结构时,也会使用动态内存管理,因此这部分内容的重要性可见一斑!!其中动态内存管理与之前数组的学习有所关联,先一起来回顾一下吧老铁们~
当然如果想了解其他的内容,具体可以看:
石油人单挑所有-CSDN博客https://blog.csdn.net/2302_80345385?spm=1011.2266.3001.5343
目录
1、回顾数组的相关部分
在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。这个规则只有两个例外,sizeof 返回整个数组所占用的字节而不是一个指针所占用的字节。单目操作符&返回一个指向数组的指针,而不是一个指向数组第1个元素的指针的指针。
除了优先级不同以外,下标表达式array[value]和间接访问表达式*(array+(value))是一样的。因此,下标不仅可以用于数组名,也可以用于指针表达式中。不过这样一来,编译器就很难检查下标的有效性。指针表达式可能比下标表达式效率更高,但下标表达式绝不可能比指针表达式效率更高。但是,以牺牲程序的可维护性为代价获得程序的运行时效率的提高可不是个好主意。
指针和数组并不相等。数组的属性和指针的属性大相径庭。当我们声明一个数组时,它同时也分配了一些内存空间,用于容纳数组元素。但是,当我们声明一个指针时,它只分配了用于容纳指针本身的空间。
当数组名作为函数参数传递时,实际传递给函数的是一个指向数组第1个元素的指针。函数所接收到的参数实际上是原参数的一份拷贝,所以函数可以对其进行操纵而不会影响实际的参数。但是,对指针参数执行间接访问操作允许函数修改原先的数组元素。数组形参既可以声明为数组,也可以声明为指针。这两种声明形式只有当它们作为函数的形参时才是相等的。
数组也可以用初始值列表进行初始化,初始值列表就是由一对花括号包围的一组值。静态变量(包括数组)在程序载入到内存时得到初始值。自动变量(包括数组)每次当执行流进入它们声明所在的代码块时都要使用隐式的赋值语句重新进行初始化。如果初始值列表包含的值的个数少于数组元素的个数,数组最后几个元素就用缺省值(0)进行初始化。如果一个被初始化的数组的长度在声明中未给出,编译器将使这个数组的长度设置为刚好能容纳初始值列表中所有值的长度。字符数组也可以用一种很像字符串常量的快速方法进行初始化。
多维数组实际上是一维数组的一种特型,就是它的每个元素本身也是一个数组。多维数组中的元素根据行主序进行存储,也就是最右边的下标率先变化。多维数组名的值是一个指向它第1个元素的指针,也就是一个指向数组的指针。对该指针进行运算将根据它所指向数组的长度对操作数进行调整。多维数组的下标引用也是指针表达式。当一个多维数组名作为参数传递给一个函数时,它所对应的函数形参的声明中必须显式指明第2维(和接下去所有维)的长度。由于多维数组实际上是复杂元素的一维数组,一个多维数组的初始化列表就包含了这些复杂元素的值。这些值的每一个都可能包含嵌套的初始值列表,由数组各维的长度决定。如果多维数组的初始化列表是完整的,它的内层花括号可以省略。在多维数组的初始值列表中,只有第1维的长度会被自动计算出来。
我们还可以创建指针数组。字符串的列表可以以矩阵的形式存储,也可以以指向字符串常量的指针数组形式存储。在矩阵中,每行必须与最长字符串的长度一样长,但它不需要任何指针。指针数组本身要占用空间,但每个指针所指向的字符串所占用的内存空间就是字符串本身的长度。
警告的总结
1.当访问多维数组的元素时,误用逗号分隔下标。
2.在一个指向未指定长度的数组的指针上执行指针运算。
编程提示的总结
1.一开始就编写良好的代码显然比依赖编译器来修正劣质代码更好。
2.源代码的可读性几乎总是比程序的运行时效率更为重要。
3.只要有可能,数的指针形参都应该声明为const。
4.在有些环境中,使用register关键字提高程序的运行时效率。
5.在多维数组的初始值列表中使用完整的多层花括号能提高可读性。
2、动态内存分配的必要性
我们已经掌握的内存开辟方式有:
int n=10;//在栈空间上开辟四个字节
char b='a';
int arr1[20]={0};
char arr[20]={0};//在栈空间上开辟20个字节的连续空间
上面的内存申请方式,一旦申请好空间,大小就无法调整
这在实际运用中时非常危险的,例如现在要使用这些空间存储用户的信息,当用户量较小时可以正常存储,当随着时间的积累,一旦用户量超过设置的空间大小,那么新的用户信息就无法存储,也就导致用户信息丢失,这是非常危险的!!!
以上开辟空间的方式有两个特点
1、空间开辟大小是固定的
2、数组在声明的时候,必须指定数组长度,数组空间一旦确定了大小不能调整
那么有人可能会说,将设置的空间大小设置的大些不就可以了吗?
其实这样也是不可取的!!内存空间的大小就那么多,每个人都申请空间,空间可能不够,并且较大的空间的确可以存储,但会造成空间浪费的问题~~
那么为了解决空间太小,无法存储数据,空间太大,空间浪费的问题
C语言引入了动态内存开辟——更灵活、让程序员自己动态申请空间
要动态申请空间就得学会malloc,free,realloc,calloc这四个函数的使用
注:在申请内存时,可能申请失败,所以这种情况也要判断~~
3、malloc和free
注:malloc和free都声明在stdlib.h头文件中
3-1 malloc
C语言提供了一个动态内存开辟的函数——malloc函数
函数原型:
功能:向内存申请一块连续可用的空间,并返回指向这块空间的指针
返回值:
如果开辟成功,就返回一个指向开辟好空间的指针
如果开辟失败,就返回一个NULL指针
返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,所以具体在使用的时候,使用者自己来决定
注:如果参数size为0,malloc的行为是标准未定义的,取决于编译器
3-1-1 malloc函数总结
函数:Malloc
功能:分配内存块
具体解释:分配一个大小字节的内存块,返回一个指向该块开头的指针。
参数:1、内存块的大小,以字节为单位
2、Size_t是一个无符号整型注:如果size为0,则返回值取决于特定的库实现(它可能是也可能不是空指针),但是返回的指针不能被解引用。
3-2 free
free函数是专门用来做动态内存的释放和回收的~
函数原型:
功能:用来释放动态开辟的内存
参数:如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的
如果参数ptr是NULL指针,那么函数什么事都不会做
释放空间内存,要将对应的指针置为NULL,避免出现野指针
其实malloc,realloc,calloc申请的空间,如果不想使用,都可以使用free函数释放
如果没有free函数来释放,当程序运行结束时,也会由操作系统来回收的!!
总结:
1、尽量做到谁(函数)申请的空间谁释放~
2、如果不能释放,要告知别人,记得释放~
最好:malloc和free成对出现~
例如:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
int num = 10;
int arr[10] = { 0 };
int* ptr = NULL;
ptr = (int*)malloc(num * sizeof(int));
if (NULL != ptr)// ptr
{
int i = 0;
for (i = 0; i < num; i++)
{
*(ptr + i) = 0;
}
}
free(ptr);
ptr = NULL;
return 0;
}
3-2-1 free函数总结
函数:Free
功能:释放内存块
具体解释:先前通过调用malloc、calloc或realloc分配的内存块被释放,使其再次可用以进行进一步分配。
如果ptr没有指向由上述函数分配的内存块,则会导致未定义的行为。
如果ptr是空指针,则该函数不执行任何操作。参数:指向先前用malloc、calloc或realloc分配的内存块的指针
4、realloc和calloc
4-1 realloc
realloc函数的出现让动态内存管理更加灵活~
有时会发现我们过去申请的空间太小,有时我们又会觉得申请的空间太大,那么为了合理的使用内存,我们一定要对内存的大小做灵活的调整,那么realloc函数就可以做到对动态开辟内存大小的调整。
函数原型:
功能:可以实现动态增容,通常以2/4倍增容
参数:ptr是要调整的内存地址
size调整之后新大小
返回值:返回值为调整之后的内存起始位置(起始地址)
原理:函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
注意:realloc函数在调整内存空间的时候,存在两种情况
1、原有空间之后有足够大的空间
要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化
2、原有空间之后没有足够大的空间
原有空间之后没有足够多的空间时,拓展空间的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址
由于上述情况,realloc函数的使用,就要注意一些!!
比如:
#include<stdio.h>
int main()
{
int* ptr = (int*)malloc(20);
if (NULL != ptr)// ptr
{
int i = 0;
for (i = 0; i < 5; i++)
{
*(ptr + i) = i;
}
}
else
{
return 1;
}
int* p = NULL;
p = realloc(ptr, 40);
if (p != NULL)
{
ptr = p;
}
free(ptr);
ptr = NULL;
return 0;
}
解析
1.如果p指向的空间之后有足够的空间可以追加,则直接追加,返回的是p原来的起始地址。
2.如果p指向的空间之后没有足够的空间可以追加,则realloc函数会重新找一个新的内存区域,重新开辟一块40个字节的动态内存空间,并且把原来内存空间的数据拷贝回来,释放旧的内存空间还给操作系统,最后返回新开辟的内存空间的起始地址。
3.我们需要用一个新的指针变量来接收realloc的返回值。
4.同时我们要考虑调整内存大小失败的情况,如果开辟失败,我们至少不能让原内存数据失效,我们也要释放原内存数据,并把指针置为空。
5.开辟成功,也不用担心原来的内存有没有浪费,因为realloc函数会把原来的内存空间拷贝回来,再将其内存释放。注意如果ptr!=NULL ;p=ptr;这个p指针已经被赋为ptr了(就不用考虑原内存空间的指针有没有被置为空指针),所以新空间不再使用时,要释放内存,并把指针置为空。
4-1-1 realloc函数总结
函数:Realloc
功能:重新分配内存块
具体解释:改变ptr所指向的内存块的大小。
函数可以将内存块移动到一个新的位置(其地址由函数返回)
参数:1、指向先前用malloc、calloc或realloc分配的内存块的指针。这也可以是一个空指针,在这种情况下,一个新的块被分配(好像malloc被调用)
2、内存块的新大小,以字节为单位
返回值:
指向重新分配的内存块的指针,它可以与ptr相同,也可以是一个新的位置。
空指针表示该函数分配存储失败,因此ptr所指向的块没有被修改。
4-2 calloc
calloc函数也是用来动态内存分配的
函数原型:
功能:为num个大小为size的元素开辟一块空间,并把空间的每个字节初始化为0
与malloc函数的区别在于calloc会在返回地址之前把申请的空间的每个字节初始化为全0
使用场景:可用于初始化内存~~
例如:
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (NULL != p)
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
}
free(p);
p = NULL;
return 0;
}
输出结果:
0 0 0 0 0 0 0 0 0 0
所以说如果对申请的内存空间的内容要求初始化,可以使用calloc函数来完成
强调:在进行动态内存管理时,可以将对应的变量理解为一个数组
4 -2-1 calloc函数总结
函数:Calloc
功能:分配和零初始化数组
具体解释:为包含num个元素的数组分配一块内存,每个元素的长度为字节大小,并将其所有位初始化为零。
参数:1、要分配的元素数量
2、每个元素的大小
返回值:
如果成功,则返回一个指向函数分配的内存块的指针。
如果函数分配请求的内存块失败,则返回空指针。
5、常见的动态内存错误
5-1 对NULL指针解引用操作
样例:
void test()
{
int* p = (int*)malloc(INT_MAX / 4);
*p = 20;// p如果是NULL就会出现问题
free(p);
}
分析:p可能是NULL指针,那么p如果是NULL就会出现问题,对NULL解引用,程序会出现问题
5-2 对动态开辟的空间进行越界访问
样例:
//对动态开辟的空间进行越界访问
void test()
{
int i = 0;
int* p = (int*)malloc(10 * sizeof(int));
if (NULL == p)
{
exit(EXIT_FAILURE);
}
for (i = 0; i <= 10; i++)
{
*(p + i) = i;//i是10的时候越界访问——类似数组越界访问!
}
free(p);
}
分析:test函数中开辟了10个整型大小的空间,那么可以理解为int arr[10],下标由0开始递增直至到9,那么循环中i是10的时候,访问了没有申请的空间,那么就是越界访问——类似数组越界访问!
5-3 对非动态开辟的内存使用free释放
样例:
//对非动态开辟的内存使用free释放
void test()
{
int a = 10;
int* p = &a;
free(p);//ok?--no
}
分析:动态开辟的空间才能使用free函数释放空间,否则不能使用free函数释放空间~~
5-4 使用free释放一块动态开辟内存的一部分
样例:
//使用free释放一块动态开辟内存的一部分
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不在指向动态内存的起始位置
}
分析:test函数中开辟了100个整型大小的空间,将这部分空间的地址给指针p,那么p就指向了这块空间,在进行p++操作之后,p已经不在指向动态内存的起始位置,那么释放p所指向的空间就会存在问题!!
5-5 对同一块内存多次释放
样例:
//对同一块内存多次释放
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放~
}
分析:切记对同一块空间只要释放一次就够了!!
5-6 动态开辟内存忘记释放(内存泄漏)
样例:
//动态开辟内存忘记释放(内存泄漏)
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while (1);
return 0;
}
分析:test函数中开辟了100个整型大小的空间,将这部分空间的地址给指针p,那么p就指向了这块空间,但test函数在使用完这块空间后,空间没有释放,p未置为NULL,那么就会造成内存泄漏!!
忘记释放不在使用的动态开辟的空间会造成内存泄漏!!
强调:动态开辟的空间一定要释放,并且正确释放!!
6、动态内存经典笔试题
6-1 试题1
//经典笔试题
//试题1
void GetMemory(char* p)
{
p = (char*)malloc(100);
//变量p只能在函数内部使用
//
//跳出函数,p就找不到开辟空间的地址,找不到相应的空间!!
//跳出函数p销毁,p的空间还给操作系统
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
//传的的是指针变量的本身-传值调用
strcpy(str, "hello world");
//对NULL指针的解引用
printf(str);
free(str);
str = NULL;
}
输出结果是什么呢?
这段代码存在问题,你能否试着分析与改进呢?
分析与改进
分析:从输出结果与代码可以看出存在内存泄漏与程序崩溃的问题,具体解析请看注释!!
图解:
6-1-1 改进1
//改进--1
void GetMemory(char** p)
{
*p = (char*)malloc(100);
//*p就是str
//把100个字节的地址放在str里了
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
//传址调用
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
输出结果:
hello world
图解:
6-1-2 改进2
// 改进--2
char* GetMemory()
{
char* p = (char*)malloc(100);
return p;
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
输出结果:
hello world
6-1-3 改进3
// 改进--3
char* GetMemory()
{
char* p = (char*)malloc(100);
return p;
}
void Test(void)
{
char* str = NULL;
str=GetMemory(str);
strcpy(str, "hello world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
输出结果:
hello world
6-2 试题2
char* GetMemory(void)
{
char p[] = "hello world";
return p;//类似数组越界
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
free(str);
str = NULL;
}
输出结果是什么呢?
分析:
1、等GetMemory函数返回后,使用str指针去访问p数组,就是非法访问,因为p数组的内存已经还给操作系统了
此时str就是一个野指针!!
图解:
这是实际上是栈空间地址的问题
强调:数组在栈区上开辟的空间,进入的它的作用域创建,出了的它的作用域销毁
比如:
int * test()
{
int n = 100;
return &n;
//可以返回值,不可以返回地址,因为出函数后地址所在的空间不在了~
}
int main()
{
int* p= test();
printf("wwwww\n");
printf("%d\n", *p);
return 0;
}
输出结果:
6-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);
}
输出结果是什么呢?
输出结果:
hello
分析:这段代码大体上不存在问题,但动态开辟的空间使用完后没有及时释放空间!!
完整的正确代码:
//3--与改进-2的代码代码类似
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//没有释放内存
free(str);
str = NULL;
}
6-4 试题4
//4
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);
//free释放空间后指针不会置为NULL!!
//空间还给操作系统,已经没有这块空间的使用权限,但这块空间还存在
//str还能找到这块空间,其中存放了这块空间的地址~
if (str != NULL)
{
strcpy(str, "world");//非法访问内存空间
printf(str);
}
}
输出结果是什么呢?
world
图解:
7、柔性数组(不可忽视)
7-1 柔性数组
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫柔性数组成员。
比如:
//柔性数组
struct st_type {
int i;
int arr[0];//柔性数组成员
};
在有些编译器上这种方式不允许,那么还有一种写法,那就是
//柔性数组
struct st_type {
int i;
int arr[];//柔性数组成员
};
7-2 柔性数组的特点
1、结构中的柔性数组成员,前面必须至少一个其他成员
2、sizeof返回的这种结构大小,不包括柔性数组的内存
3、包含柔性数组成员的结构,用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
比如试着计算一下这个结构体的大小,相信你一眼就能看出来~
struct st_type {
int i;//至少有一个成员,否则结构体大小是0~
int arr[];//数组大小是未指定的!!
//或者写成
//int arr[0];//柔性数组成员
}type_a;
int main()
{
printf("%zd\n", sizeof(type_a));
return 0;
}
输出结果
4
这段代码就说明了柔性数组的特点:至少有一个成员,否则结构体大小是0(无意义),并且数组大小是未指定的!!
值得注意:1、关注对象是结构体中最后一个成员
2、最后一个成员是数组,数组没有指定大小
这个数组才是柔性数组!!
7-3 柔性数组的使用
比如:
typedef struct st_type {
int n;
int arr[];
}type_a;
int main()
{
//在堆区上开辟空间,
type_a* ps = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));//malloc(结构体的大小+希望设置的数组的大小)
if (ps == NULL)
{
perror("malloc:");
exit(1);
}
ps->n = 100;
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;
}
//……
free(ps);
ps = NULL;
return 0;
}
程序图解:
这样柔性数组成员,相当于获得了5个整型元素的连续空间。
7-4 柔性数组的优势
方案1
//柔性数组的使用
// 方案1——柔性数组
// 一次malloc,一次free
// 不容易弄错
typedef struct st_type {
int n;
int arr[];
}type_a;
int main()
{
//在堆区上开辟空间,
type_a* ps = (type_a*)malloc(sizeof(type_a) + 5 * sizeof(int));//malloc(结构体的大小+希望设置的数组的大小)
if (ps == NULL)
{
perror("malloc:");
exit(1);
}
ps->n = 100;
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;
}
//调整空间-ps指向的空间
//可以通过realloc调整空间大小,即改变数组的空间大小,进而使得数组"柔性"(柔性变化)
type_a* pa = (type_a*)realloc(ps, sizeof(type_a) + 10 * sizeof(int));
if (pa != NULL)
{
ps = pa;
}
//……
free(ps);
ps = NULL;
return 0;
}
程序的图解:
方案2
//方案2——结构体里包含一个指针
//缺陷:
// 两次malloc,两次free,
//释放空间的顺序,不能交换
//malloc申请空间的次数越多,内存与内存之间的空隙越多(内存碎片越多)
//内存的利用率越低
typedef struct st_type {
int n;
int* arr;
}type_a;
int main()
{
type_a* ps = (type_a*)malloc(sizeof(type_a));//n空间的大小
if (ps == NULL)
{
perror("malloc:");
exit(1);
}
ps->arr = (type_a*)malloc(5 * sizeof(int));
ps->n = 100;
int i = 0;
for (i = 0; i < 5; i++)
{
ps->arr[i] = i;
}
//调整数组的大小
//用arr来维护这块空间
int* pa = (int*)realloc(ps->arr, 10 * sizeof(int));
if (pa != NULL)
{
ps->arr = pa;
}
//使用
// ……
free(ps->arr);//先释放
free(ps);//后释放
ps = NULL;
return 0;
}
程序的图解
这样做的原因是——我们想给一个结构体内的数据分配一个连续的内存!这样做的意义有两个好处:
第一个意义是:方便内存释放(主要的)。如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。(读到这里,你一定会觉得C++的封闭中的析构函数会让这事容易和干净很多)
第二个原因是:这样有利于访问速度。连续的内存有益于提高访问速度,也有益于减少内存碎片。
8、C/C++中程序内存区域划分
C/C++程序内存分配的几个区域:
栈区(stack) :在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时
这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,分配方式类似于链表。
静态区(数据段):(static)存放全局变量,静态数据。程序结束时由系统释放。
代码段:存放函数体(类成员函数和全局函数)的二进制代码。
9、动态内存管理的总结
当数组被声明时,必须在编译时知道它的长度。动态内存分配允许程序为一个长度在运行时才知道的数组分配内存空间。
malloc和calloc函数都用于动态分配一块内存,并返回一个指向该块内存的指针。malloc 的参数就是需要分配的内存的字节数。和它不同的是,calloc 的参数是你需要分配的元素个数和每个元素的长度。calloc 函数在返回前把内存初始化为零,而malloc函数返回时内存并未以任何方式进行初始化。调用 realloc 函数可以改变一块已经动态分配的内存的大小。增加内存块大小时有可能采取的方法是把原来内存块上的所有数据复制到一个新的、更大的内存块上。当一个动态分配的内存块不再使用时,应该调用free函数把它归还给可用内存池。内存被释放之后便不能再被访问。
如果请求的内存分配失败,malloc、calloc和realloc 函数返回的将是一个NULL指针。错误地访问分配内存之外的区域所引起的后果类似越界访问一个数组,但这个错误还可能破坏可用内存池,导致程序失败。如果一个指针不是从早先的malloc、calloc 或realloc函数返回的,它是不能作为参数传递给 free 函数的,你也不能只释放一块内存的一部分。
内存泄漏是指内存被动态分配以后,当它不再使用时未被释放。内存泄漏会增加程序的体积,有可能导致程序或系统的崩溃。
10、 警告的总结
1.不检查从malloc函数返回的指针是否为NULL。
2.访问动态分配的内存之外的区域。
3.向free函数传递一个并非由malloc函数返回的指针。
4.在动态内存被释放之后再访问它。
11、编程提示的总结
1.动态内存分配有助于消除程序内部存在的限制。
2.使用sizeof计算数据类型的长度,提高程序的可移植性。
制作不易,一键三联吧,老铁们!!!