学校网站的建设论文,做网站前的准备工作,wordpress建自己的网站吗,泗洪县建设局网站怎么查不到【C语言】——动态内存管理 一、动态内存管理概述1.1、动态内存的概念1.2、动态内存的必要性 二、 m a l l o c malloc malloc 函数2.1、函数介绍2.2、应用举例 三、 c a l l o c calloc calloc 函数四、 f r e e free free 函数4.1、函数介绍4.2、应用举例 五、 r e a l l o … 【C语言】——动态内存管理 一、动态内存管理概述1.1、动态内存的概念1.2、动态内存的必要性 二、 m a l l o c malloc malloc 函数2.1、函数介绍2.2、应用举例 三、 c a l l o c calloc calloc 函数四、 f r e e free free 函数4.1、函数介绍4.2、应用举例 五、 r e a l l o c realloc realloc 函数5.1、函数介绍5.2、应用举例 六、常见的动态内存错误6.1、对NULL指针进行解引用6.2、对动态开辟空间的越界访问6.3、对非动态开辟的内存使用 f r e e free free 释放6.4、使用 f r e e free free 释放一块动态开辟内存的一部分6.5、对同一块动态内存多重释放6.6、动态开辟内存忘记释放内存泄漏 七、动态内存经典笔试题分析7.1、题一7.2、题二7.3、题三7.4、题四 八、柔性数组8.1、什么是柔性数组8.2、柔性数组的特点8.2、柔性数组的使用8.3、柔性数组的优势 九、C/C中内存区域划分 一、动态内存管理概述
1.1、动态内存的概念 在了解为什么要有动态内存管理之前我们得先知道动态内存的定义。 动态内存是指动态的内存空间意思就是能动态开辟的内存空间动态就是申请了这块空间后可动态的修改这块空间的大小根据需要动态地释放和分配内存空间。
1.2、动态内存的必要性 为什么要有动态内存呢 既然有动态内存那与之相对的就是静态内存 什么是静态内存呢其实静态内存我们天天都在用只是不知道它是静态内存而已 下面两种内存开辟方式就是静态内存
int val 20;//在栈空间上开辟四个字节
char arr[10] { 0 };//在栈空间上开辟10个字节的连续空间但是静态内存的开辟有两个缺点 空间开辟的大小是固定的数组在申明的时候必须指定数组的长度数组空间一旦确定了大小不能调整 但是在实际需求中我们往往只有在程序运行时才能知道所需的空间大小用数组开辟空间往往容易造成内存溢出空间开小或者内存浪费空间开大无法满足实际的需求 因此C语言引入了动态内存开辟让程序员自己可以申请和释放空间并根据需要自己调整开辟后空间的大小。这样不仅提高了内存的利用率也极大地增强了程序的灵活性与扩展性。
二、 m a l l o c malloc malloc 函数
2.1、函数介绍 C语言中提供了一个动态内存分配的函数 m a l l o c malloc malloc 功能向内存申请一块连续可用的空间可当成数组并返回指向这块空间的指针 参数 s i z e size size_ t t t s i z e size size 分配的内存的大小以字节为单位。即开辟 s i z e size size 字节大小的空间如果参数为 0 m a l l o c malloc malloc 的行为是标准未定义的取决于编译器 返回值 v o i d void void * 返回指向开辟空间的指针因为 m a l l o c malloc malloc 函数 事先并不知道使用者开辟空间存放什么类型的数据因此指针为 v o i d void void* 类型以便能接受所有类型。使用者可根据自己的需要将其强制类型转换成自己所需要的类型以便能进行解引用操作。如果开辟失败则返回 空指针因此 m a l l o c malloc malloc 的返回值一定要做检查 2.2、应用举例
#includestdlib.hint main()
{int* p NULL;p (int*)malloc(10 * sizeof(int));if (NULL p){perror(malloc fail);return 1;}return 0;
}上述代码是使用 m a l l o c malloc malloc 函数开辟 10 个整型变量的空间也即 40 个字节的空间
首先因为 m a l l o c malloc malloc 函数的返回值是指针我们需用指针变量 p p p 接收其返回值创建 p p p 时并不知道其指向的空间所以先初始化为 NULL。接着使用 m a l l o c malloc malloc 函数开辟空间因为我们要存放的是整型变量而 m a l l o c malloc malloc 的返回值类型为 v o i d void void* 我们通过强制类型转换将其返回类型转换为 i n t int int* 并用 p p p 来接收因为 m a l l o c malloc malloc 函数有可能开辟失败1只有当返回值不为空的情况我们才使用它因此需判断 p p p 指针是否为空。若为空则用 p e r r o r perror perror 函数2打印出错误信息并返回 13。若开辟成功我们就可以愉快地使用这块空间啦 需要注意的是 m a l l o c malloc malloc 开辟的空间并不会将其初始化
三、 c a l l o c calloc calloc 函数 开辟动态内存空间C语言还提供了一个函数叫 c a l l o c calloc calloc 原型如下 函数的功能是为 n u m num num 个大小为 s i z e size size 的元素开辟一块空间并将这块空间初始化为 0与函数 m a l l o c malloc malloc 的区别只在于 c a l l o c calloc calloc 会返回地址之前把申请的空间的每个字节初始化为全 0
举个例子
#includestdio.h
#includestdlib.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));}printf(\n);}return 0;
}运行结果 四、 f r e e free free 函数
4.1、函数介绍 上述 m a l l o c malloc malloc 函数、 c a l l o c calloc calloc 函数以及后面讲的 r e a l l o c realloc realloc 函数所申请的空间并不满足作用域的规则。只有当程序退出时用他们开辟的动态空间才会归还给操作系统换言之程序不退出就不会主动归还空间。这时我们就需要 f r e e free free函数 来对其主动释放 f r e e free free 函数是专门用于动态开辟的内存空间的释放和回收声明如下 f r e e free free 函数用来释放动态开辟的内存
如果参数 p t r ptr ptr 指向的空间不是动态开辟的那 f r e e free free 函数的行为是未定义的如果参数是 p t r ptr ptr 是NULL指针则函数什么事都不做需要注意的是 f r e e free free 函数不会改变指针所指向的值释放后它依然指向相同的内存空间。因此我们要手动将释放后的 p t r ptr ptr 置空避免出现野指针。 m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free 函数的声明都在 s t d l i b . h stdlib.h stdlib.h 中
4.2、应用举例 我们来看个例子
#includestdio.h
#includestdlib.hint main()
{int* ptr NULL;ptr (int*)malloc(10 * sizeof(int));if (NULL ! ptr){int i 0;for (i 0; i 10; i){*(ptr i) 0;}}free(ptr);ptr NULL;return 0;
}那我们来看看下面这种情况行不行呢
int main()
{int* ptr NULL;ptr (int*)malloc(5 * sizeof(int));if (NULL ! ptr){int i 0;for (i 0; i 5; i){*ptr 0;ptr;}}free(ptr);ptr NULL;return 0;
}当然是不行的为什么呢 因为传递给 f r e e free free 函数的是要释放空间的 起始地址 上面函数的 p t r ptr ptr 以及不再指向要释放空间的起始地址了当然是不行的。
五、 r e a l l o c realloc realloc 函数
5.1、函数介绍 可能有小伙伴问前面你说动态内存可根据需要动态调整所开辟空间的大小但前面介绍 m a l l o c malloc malloc、 c a l l o c calloc calloc 以及 f r e e free free函数 都只是在将动态空间的开辟和释放如何调整空间的大小呢别急我们接下来要讲的 r e l l o c relloc relloc函数 就是完成调整开辟空间的大小的任务的 r e a l l o c realloc realloc 函数的出现让动态内存管理更加灵活 有时我们会发现之前申请的空间太小了有时我们又会觉得申请的空间太大了那为了合理的使用内存我们一定会对内存的大小做出灵活的调整。那 r e a l l o c realloc realloc函数 就可以做到对动态开辟内存大小的调整
先来看看 r e l l o c relloc relloc函数 的声明 p t r ptr ptr 是要调整的内存地址 s i z e size size 是调整之后的大小可以变大也可变小返回值为调整之后的内存起始位置 r e a l l o c realloc realloc 调整内存大小分为三种情况 原有空间之后有足够大的空间 如上图 r e l l o c relloc relloc 已经开辟 20 个字节的空间现在我想扩容到 40 字节同时原有空间后方空间足够扩展新空间 此时 r e a l l o c realloc realloc 函数直接在后方追加空间原来空间的数据不发生变化 2. 原有空间之后没有足够大的空间 还是上面那个图现在我想将他扩容到 400 字节很明显已开辟空间后方没有足够的空间总不能把别人踢开自己霸占吧 这时 r e a l l o c realloc realloc 函数就会在堆空间 另外找一个 合适大小的空间。 具体流程如下 r e a l l o c realloc realloc 函数先在堆空间上找一块新的空间并且满足大小要求后将旧空间的数据拷贝到新空间中接着释放旧空间最后返回新空间的起始地址 3. 空间调整失败 r e a l l o c realloc realloc 可能出现空间调整失败的情况此时返回的是空指针 r e a l l o c realloc realloc 不仅仅能将空间的变大还能将空间变小只需要第二个参数的值小于原空间的大小就好了因为缩小空间比较简单这里就不再过多介绍但需要注意的是缩小空间可能会造成数据丢失因此需小心使用 同时 r e a l l o c realloc realloc函数 不仅能调整空间大小还能完成 m a l l o c malloc malloc函数 的功能当第一个参数 p t r ptr ptr 传递的是 空指针 时 r e a l l o c realloc realloc 函数就不再是调整空间大小了你都没空间我还怎么调。此时 r e a l l o c realloc realloc 函数会 新开辟 s i z e size size 字节大小的空间。
5.2、应用举例 看了上面三种情况大家想一想应该怎样接收 r e a l l o c realloc realloc 调整之后的返回值呢 可以直接用原来的指针 p p p 接收吗 显然是不行如果 r e a l l o c realloc realloc 调整成功那确实没问题但如果失败了呢此时返回的是空指针。本来 p p p 还维护着原来的空间现在直接变空指针那原来的空间再也找不到了这就造成了内存泄漏。 正确的方法是创建新的指针 p t r ptr ptr 来接收当 p t r ptr ptr 不为 NULL再将 p t r ptr ptr 的值传给 p p p 如下
#includestdlib.hint main()
{int* p (int*)malloc(5 * sizeof(int));if (NULL p){perror(malloc fail);return 1;}//1 2 3 4 5for (int i 0; i 5; i){*(p i) i 1;}//希望将空间调整为40个字节int* ptr NULL;ptr (int*)realloc(p, 40);if (NULL ! ptr){p ptr;}else{perror(realloc fail);}//调整成功使用40个字节调整失败继续使用20个字节/**************业务处理**************/free(p);p NULL;return 0;
}六、常见的动态内存错误
6.1、对NULL指针进行解引用 #includestdlib.hint main(
{int* p (int*)malloc(INT_MAX);*p 20;//如果p的值是空指针就会有问题free(p);p NULL;return 0;
}动态开辟的空间应该先对返回值进行判断确保空间开辟成功 上述代码所要开辟的空间太大开辟失败返回的是空指针而下面一句代码对空指针进行解引用是错误的
#includestdlib.hint main()
{int* p (int*)malloc(10 * INT_MAX);if (NULL p){perror(malloc fail);return 1;}*p 20;free(p);p NULL;return 0;
}6.2、对动态开辟空间的越界访问 #includestdlib.hint mian()
{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;}free(p);p NULL;return 0;
}可以看到当 i i i 10 时就是对 m a l l o c malloc malloc 开辟的空间越界访问了。 动态内存的空间与数组是非常相似的要注意不能对其越界访问。
6.3、对非动态开辟的内存使用 f r e e free free 释放
void test()
{int a 10;int* p a;free(p);//ok?
}变量 a a a 并不是动态开辟的变量用 f r e e free free 释放是错误的
6.4、使用 f r e e free free 释放一块动态开辟内存的一部分 这种情况即是传给 f r e e free free 的指针并不是动态开辟内存的起始地址指针跑后面去了。
void test()
{int* p (int*)malloc(100);p;free(p);//p不再指向动态内存的起始位置
}注意动态内存一定是 一同申请一同释放。无法做到只释放一部分空间
6.5、对同一块动态内存多重释放 void test()
{int* p (int*)malloc(100);//···free(p);free(p);//重复释放
}这种释放有办法可以避免释放完后及时把 p p p 置为空指针这样即使再次释放传的是空指针 f r e e free free 什么都不会做不会造成什么影响
6.6、动态开辟内存忘记释放内存泄漏
#includestdlib.h
void test()
{int* p (int*)malloc(100);if (NULL ! p){*p 20;}
}int main()
{test();while (1);return 0;
}上述代码你会发现一旦出了函数就再也找不到开辟的那 100 个字节的空间(这代码写的比较极端一直死循环程序一直不结束) 。找不到开辟的空间更别提将其释放空间就一直在那占着就造成了内存泄漏。 想一想如果我们一直向内存申请空间但从来不释放。要知道内存的总大小是有限的这样就会把内存耗干机器就挂了。 总结 用 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc 申请的空间尽量做到
谁可能是函数申请的就谁释放即 m a l l o c malloc malloc 和 f r e e free free 成对出现如果不能释放要告诉使用的人记得释放 七、动态内存经典笔试题分析
7.1、题一
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;
}请问运行 T e s t Test Test 函数会有什么样的结果
运行 T e s t Test Test 函数程序会崩溃 为什么呢我们来分析一下 先来看这段代码想要做什么 首先它定义了一个 G e t M e m o r y GetMemory GetMemory函数很明显这个函数是完成动态开辟空间任务的接着 T e s t Test Test函数 中创建了指针 s t r str str将变量传给 G e t M e m o r y GetMemory GetMemory即希望指针 s t r str str 管理动态开辟的空间最后往空间中存入 h e l l o w o r l d hello world helloworld并打印 代码的逻辑没问题那就是代码本身出问题咯 通过调试我们发现 G e t M e m o r y GetMemory GetMemory函数 并没有改变 s t r str str 的值它依然是个空指针。 为什么呢因为 G e t M e m o r y GetMemory GetMemory是 传值传参而不是传址传参传值传参无法改变主调函数中的值出了函数 s t r str str 依然是空指针而后面打印 s t r str str 指向的内容是要对其解引用的对空指针解引用自然会出问题。 同时函数中 m a l l o c malloc malloc 是实打实开辟了空间的只有程序结束才销毁而函数中的变量出了函数作用域就销毁这样函数中所开辟的 100 个字节空间出了 G e t M e m o r y GetMemory GetMemory函数 后也无法找到造成内存泄漏 可能有小伙伴会问 G e t M e m o r y GetMemory GetMemory函数 的参数类型就是 c h a r char char* 啊为什么还是传值传参呢这里我们要指针传址传参的本质传递的是变量的地址因为主调函数中要传的值本身就是指针 c h a r char char* 类型要改变指针变量就要传递指针变量的指针即二级指针。这里可不敢看到 G e t M e m o r y GetMemory GetMemory函数 参数中是 c h a r char char* 就认为他是传址传参 正确写法应该是这样
void GetMemory(char** p)
{*p (char*)malloc(100);
}void Test(void)
{char* str NULL;//传str的地址GetMemory(str);strcpy(str, hello world);printf(str);//释放动态空间free(str);str NULL;
}
当然 G e t M e m o r y GetMemory GetMemory函数 我们也可以直接让他返回 p p p以实现目的
char* GetMemory()
{char* p (char*)malloc(100);return p;
}void Test(void)
{char* str GetMemory();strcpy(str, hello world);printf(str);free(str);str NULL;
}7.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;
}运行结果 为什么会这样呢 问题还是出在 G e t M e m o r y GetMemory GetMemory函数 G e t M e m o r y GetMemory GetMemory函数 中创建的 p p p 数组在出了函数作用域后就销毁了因此函数返回 p p p用 s t r str str 接收而实际上 s t r str str 接收的地址是指向一块已经归还的空间此时的 s t r str str 是野指针。再去访问 s t r str str 所指向的空间是非法访问打印出的值是随机值。
7.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;
}可能有小伙伴会觉得这段代码咋一看好像没什么问题啊 确实大家有没有发现代码与我们第一题修改后的代码非常像但大家仔细想想它还缺少什么 这段代码唯一的问题是没有 f r e e free free动态申请内存空间后他并没有还回去。 虽然这里没有 f r e e free free 程序也没有问题因此程序结束后会自动释放空间但以后遇到复杂的情况就不好说了因此我们要 养成主动释放内存空间的习惯
7.4、题四
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;
}这题的问题相信大家都能看得出来 T e s t Test Test函数 上来先开辟 100 字节动态空间并创建 s t r str str 变量维护它再向空间中放 h e l l o hello hello 但紧接着释放 s t r str str却没将 s t r str str 置空此时的 s t r str str是野指针。将空间释放即将其还给操作系统我们是没有使用权限了但是这块空间本身还在 下面的 i f if if 语句判断为真往 s t r str str 指向的空间放入 w o r l d world world此时 s t r str str 指向的空间我们已经没有使用权限了但依然进行修改为非法内存访问。
八、柔性数组
8.1、什么是柔性数组 也许有些小伙伴从来没有听过柔性数组这个概念但是它确实是存在的 C99 中结构体的最后一个元素允许是未知大小的数组这就叫做柔性数组成员。
typedef struct st_type
{int i;int a[0];
}type_a;有些编译器会报错无法编译可以改成
typedef struct st_type
{int i;int a[];
}type_a;8.2、柔性数组的特点
结构体中的柔性数组成员前面必须至少一个其他成员 s i z e o f sizeof sizeof 返回的这种结构体大小不包括柔性数组的内存包含柔性数组成员的结构体一般用 m a l l o c malloc malloc函数 进行内存的动态分配并且分配的内存应该大于结构体的大小以适应柔性数组的预期大小
例如
typedef struct st_type
{int i;int a[0];
}type_a;
int main()
{printf(%d\n, sizeof(type_a));return 0;
}运行结果 8.2、柔性数组的使用
#includestdio.h
#includestdlib.htypedef struct st_type
{int i;int a[0];
}type_a;int main()
{int i 0;type_a* p (type_a*)malloc(sizeof(type_a) 10 * sizeof(int));if (NULL p){perror(malloc fail);return 1;}//业务处理p-i 10;for (i 0; i 10; i){p-a[i] i;}//调整空间type_a* ptr (type_a*)realloc(p, sizeof(type_a) 20 * sizeof(int));if (ptr ! NULL){p ptr;}free(p);p NULL;return 0;
}柔性数组的结构 既然这块空间是 m a l l o c malloc malloc 出来的也就是说他可以通过 r e a l l o c realloc realloc 来调整大小所以这个数组可变长变短不就是柔性吗
8.3、柔性数组的优势 上述 t y p e type type_ a a a 结构也可以设计为下面的结构也能完成同样的效果
#includestdio.h
#includestdlib.htypedef struct st_type
{int i;int* p_a;
}st_type;
int main()
{st_type* p (st_type*)malloc(sizeof(st_type));p-i 100;p-p_a (int*)malloc(p-i * sizeof(int));///业务处理for (i 0; i 100; i){p-p_a[i] i;}//释放空间free(p-p_a);p-p_a NULL;free(p);p NULL;return 0;
}图示 上述两个方法都可以达到类似的效果 但是使用柔性数组有两个好处
方便内存释放 如果我们的代码是在一个给别人用的函数中你在里面做了二次内存分配并把结构体返回给用户。用户调用 f r e e free free 可以释放结构体但是用户并不知道结构体内的成员也需要 f r e e free free所以你不能指望用户来发现这个事。 所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次 f r e e free free 就可以把所有的内存释放掉 有利于访问速度 连续的内存有益于提高访问速度也有益于减少内存碎片4 九、C/C中内存区域划分 C/C程序内存分配的几个区域
内核空间操作系统核心内核运行的地方在这个区域操作系统可以直接访问硬件并执行特权指令。我们用户是无权访问这块空间的栈区在执行函数时函数内局部变量的存储单元都是在栈上创建函数执行结束时这些存储单元自动释放。栈内存分配运算内置于处理器的指令中效率很高但是分配的内存容量有限。栈区只要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。堆区堆区一般是用来存储程序运行期间动态分配内存的地方。堆区的内存分配是在程序运行时动态进行的程序员可以通过调用标准库函数如 m a l l o c malloc malloc、 c a l l o c calloc calloc、 r e a l l o c realloc realloc等来在堆区中分配内存并在不需要时手动释放这些内存使用 f r e e free free函数。使用堆区需要注意内存泄漏 m e m o r y l e a k memory leak memoryleak的问题即程序在不再需要某块内存时没有释放它导致程序占用的内存越来越多。数据段数据段也叫做静态区主要用来存放全局变量、静态数据、全局变量。程序结束后由系统释放代码段存放函数体类成员函数和全局函数的二进制代码、只读常量字符串常量 内存开辟失败动态内存开辟失败的原因一般都是所空间太大没有足够的空间 ↩︎ p e r r o r perror perror函数有关该函数的具体介绍请看《【C语言】——字符串函数的使用与模拟实现下》 ↩︎ 返回值为 1 m a i n main main 函数程序正常退出返回值为 0异常退出返回值为 1 ↩︎ 我们开辟内存时不会紧接着上一块内存开辟而会留下一点空隙开辟次数越多留下的空隙也就也多这些空隙称为内存碎片。 ↩︎