<>Dynamic Memory Allocation(动态内存分配)



<>1.为什么存在动态内存分配

<>2.动态内存函数的介绍

<>malloc

<>free

<>calloc

<>realloc



<>3.常见的动态内存错误

<>4.几个经典的笔试题

<>5.柔型数组





<>在开始之前,我们需要回顾一下 我们当前所掌握的 内存使用方法

<>当前我们知道的内存使用方法

<>1. 创建一个 变量

<>int a =10;// (假设)局部变量 - 栈区

<>int g_a =10; // (假设)全局变量 - 静态区




<>2. 创建 一个数组

<>局部变量 - 栈区

<>全局变量 - 静态区


程序一:
#include<stdio.h> struct s // 班级成员信息 { char name[20]; int age; }; int main() {
struct s arr[50];// 50 个 struct s 类型的数据 // 但是如果班级没50人,那么就意味着要浪费空间 //
反之班级人数超过50个,空间就不够用 // 如果 在前面加上 int n =0; scanf("%d",&n); n 取代 50 的位数行不行呢? //
答案不行 因为 n 是一个变量, 而数组的元素个数应该给一个常量 return 0; }
<>特点:

<>1. 空间开辟大小是固定的

<>2,数组 在申明 的时候,必须制定数组的长度,它所需要的的内存 在 编译时 分配



<>C语言是可以创建变长数组 - c99 中增加了(vs不支持,gcc支持 c99标准 -> gcc test.c - std = c99)




<>下面正式进入正文:

<>为什么存在动态内存分配?

<>对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才知道

<>那数组的编译时开辟空间的方式就不能满足了,这时候就只能试试动态开辟了(在堆上申请空间)






<>动态内存函数的介绍

<>malloc 和 free

<>malloc :void* malloc(size_t size); // size 开辟的空间大小,单位字节; void* 是一个指针,
指向的是开辟的空间的起始地址

<>free:void free(void* memblock); // memblock - 内存块


程序二:
#include<stdio.h> #include<stdlib.h>// malloc 所在头文件 #include<errno.h>//
errno,h 错误码库 int main() // 与 函数 strerror(错误信息报告) 搭配使用,将错误码转化为错误信息,并返回它的地址 { //
我想向内存申请 10个 整形的 空间 int* p = (int*)malloc(/*INT_MAX*/10 * (sizeof(int)));// 因为
malloc 函数 返回的是 万能*(无类型)指针,所以需要转化类型 // malloc 函数 也有 开辟空间失败的时候, // 比如 内存 只有 4 个 g
,我要开辟 8 g 空间,这肯定是会失败的,返回 空指针 NULL if (p == NULL) { // 找出打印错误原因的方式 printf("%s\n",
strerror(errno));// malloc(INT_MAX) //输出 Not enough space } else { // 正常使用 空间
int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i;// 赋值 0 1 2 3 4 5 6 7 8 9 }
for (i = 0; i < 10; i++) { printf("%d ", *(p + i));// 输出 0 1 2 3 4 5 6 7 8 9 }
printf("\n"); } // 当动态申请的空间 不再使用的时候 // 就会把空间 还给 操作系统 // 这时候 就会用到 free 函数 return
0; }



<>说到 free 函数, free 函数 是专门用来做 动态内存 的 释放 和 回收 的


程序三:
#include<stdio.h> #include<stdlib.h>// malloc,free 所在头文件 #include<errno.h> int
main() { // 我想向内存申请 10个 整形的 空间 int* p = (int*)malloc(10 * (sizeof(int))); if (p
== NULL) { // 找出打印错误原因的方式 printf("%s\n", strerror(errno));// malloc(INT_MAX)
//输出 Not enough space } else { // 正常使用 空间 int i = 0; for (i = 0; i < 10; i++) {
*(p + i) = i;// 0 1 2 3 4 5 6 7 8 9 } for (i = 0; i < 10; i++) { printf("%d ", *
(p + i));// 0 1 2 3 4 5 6 7 8 9 } printf("\n"); } // 当动态申请的空间 不再使用的时候 // 就会把空间
还给 操作系统 // 这时候 就会用到 free 函数 free(p);// 用完了再释放,也就是说
在打印完之后(调用完之后),再释放(p的值没有改变,还可以通过它找到该空间) // 虽然当前空间 不属于当前程序,但是依然 可以通过 p 找到
该空间,很有可能会破坏这个空间 // 所以先在这个 指针 依然很危险 p = NULL; // free函数 释放完空间后,不会改 p 的值,所以我们自己主动改
return 0; } // 最后请注意:free 是用来 释放 动态开辟的内存 // free 函数 的 特殊情况: //1. 如果参数 ptr
指向的空间不是动态开辟的,那 free 函数的行为是未定义的 //2. 如果参数 ptr 是 NULL 指针,则函数 什么 都不做。
<>为了方便你们进一步了解 free 函数

<>举个 special 例子:

<>你呢,跟你 girlfriend 前期 相处非常好,你把女友电话记得牢牢的,有助于交流。

<>但是,有一天 你女朋友看不上你了,就 free(p) [ 跟你分手了 ]

<>
但是你(p)还死皮赖脸的记住你前女友的号码(动态开辟空间的地址),随时可能打电话骚扰她,并且找到她,影响她的生活,说明你很危险(访问该空间内容,并修改,该指针很危险)

<>这时候,你前女友为了你能断开念想 (其实保护自己 == 维护程序安全)

<>你前女友 给你当头一棒(爆头),让你失忆了。(p = NULL)

<>我只能说 杀人诛心!!!(还好我单身,暂时不用担心被爆头的危险。)






<>calloc 函数 :void* calloc (size_t num,size_t size);

<>num 元素个数,size每个元素的长度【大小】(字节)

 

<>1. 函数的功能是:开辟一块空间 num*size ( num 个 大小为 size 的 元素) ,并且把空间的每个字节初始化为 0

<>2.与函数 malloc 的区别 只在于 calloc 会在返回 地址之前 把申请的空间的 每个字节初始化为全0


程序四:
用法跟 malloc 函数差不多 #include<stdio.h> #include<errno.h> #include<string.h> int
main() { // 开一块空间,空间大小为 10 * int == 40 字节 int* p = (int*)calloc(10, sizeof(int))
;// 比 malloc 函数 多一个元素个数 参数 if (p == NULL) { printf("%s\n", strerror(errno)); }
else { int i = 0; for (i = 0; i < 10; i++)// 因为 p 为整形指针变量,指向一个 int 类型 元素,所以有
10个 int 元素 { printf("%d ", *(p + i));// 0 0 0 0 0 0 0 0 0 0 } //calloc
在开辟好空间后,会把空间的所有字节内容全部初始化为 零 } free(p); p = NULL;// 狗头拿来!! return 0; }



<>realloc 函数 : void* realloc (void* memblock,size_t size);

<>memblock - 内存块 (指针 指向 之前已经开辟了的内存块)

<>size : 新的大小

 

<>1. realloc 函数 的 出现 让动态内存管理更加灵活

<>2. 有时我们会发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了, 为了合理的使用内存,我们一定会对 内存的大小 做灵活的调整,
realloc 函数就可以做到 对 动态开辟内存 大小的调整。

<>3. memblock 是要调整的内存块的地址

<>4.size 调整之后 新空间的大小

<>5.返回值为 调整之后的内存起始位置

<>6.这个函数调整 原内存空间大小 的基础上,还会将原来内存中的数据移动到 新 的空间

<>7.realloc 在 调整内存空间 时,存在两种情况:


程序五:
#include<stdio.h> #include<stdlib.h> #include<errno.h> int main() { int* p = (
int*)malloc(20);// 开辟 20 byte 动态空间 if (p == NULL) // 防止malloc 开辟动态空间失败 { printf(
"%s\n", strerror(errno));// 打印错误信息 } else { // 正常使用空间 int i = 0;// 20 字节 == 5*
(sizeof(int)) for (i = 0; i < 5; i++) { *(p + i) = i;// 此时 就是在使用 malloc
函数开辟的空间(20byte) } } // 假设 malloc 函数 开辟的 20 byte 的空间,不满足我们的需求(不够) // 我们希望能够有 40
个 byte 的空间 //这里 就可以使用 realloc 函数 来调整动态开辟的内存 int* p2 = (int*)realloc(p, 40);//
10 * int == 40 byte /* p */ // 记住 只要开辟动态空间的时候,最好判断一下,无论是 malloc
,calloc还是realloc 函数 都有可能开辟空间失败 // 在下面的解决方案中,已得到解决 // 你会发现 原本 整个空间是 由 p 来维护的 //
但是 现在你会发现 变成了 p2 来维护 了 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p2
/* p */ + i));// 0 1 2 3 4 随机值1 随机值2 随机值3 随机值4 随机值5 } // 随机值 是因为 malloc 不会像
calloc 函数 一样开辟完动态空间之后,初始化为 0 printf("\n"); for (i = 5; i < 10; i++) // 使用 增大后的空间
{ *(p2/* p */ + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p2/* p */ +
i));// 0 1 2 3 4 5 6 7 8 9 } // 如果保持统一 ,p2 换成 p,会出现问题(p已将找不到了) // 因为 realloc
函数,是重新创建一个(40 byte)空间 // 把 p 的内容拷贝下来,把拷贝下来的数据 放进 这新创建的空间里 // 然后把 p 释放(p = NULL)
// 最后 返回新空间的地址 // 输出这个新创建的空间 的结果 让你 感觉 空间确实变大了 // 实际上 已经 不是 原先的空间 了 return 0; }


<>解决方案:

程序六:
#include<stdio.h> #include<stdlib.h> #include<errno.h> int main() { int* p = (
int*)malloc(20); if (p == NULL) { printf("%s\n", strerror(errno)); } else { int
i= 0;// 20 字节 == 5* (sizeof(int)) for (i = 0; i < 5; i++) { *(p + i) = i;// 此时
就是在使用 malloc 函数开辟的空间(20byte) } } int* ptr = (int*)realloc(p, 40);// 拿一个新的指针变量
ptr 先来接收 realloc 的返回地址 if (ptr != NULL) // 判断 realloc 开辟动态空间是否 成功 { p = ptr;//
防止开辟失败,把 p 改掉了 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", *(p + i));//
0 1 2 3 4 随机值1 随机值2 随机值3 随机值4 随机值5 } printf("\n"); for (i = 5; i < 10; i++) { *(
p+ i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i));// 0 1 2 3 4 5
6 7 8 9 } } // 释放内存 free(p);// 如果 realloc 开辟动态空间失败,返回 NULL,而 free(NULL),free
函数什么都不会做 p = NULL; // 一定 要释放内存,并置为空指针,永绝后患! return 0; }
<>realloc 如果 p 指向的空间之后 有 足够的内存空间可以追加,则追加上,返回原先的旧地址(p)

<>如果 p 指向的空间之后 没有 足够的内存空间可以追加,则重新找一个新的内存区域,开辟一块新的空间, 来满足你对空间需求,同时把 原来内存 的 数据
拷贝下来,把拷贝下来的数据放到新空间里,且把原来的空间释放,最后返回新空间的地址




<>另外再补充一点

<>realloc 函数 可以实现与 malloc 函数 一样的功能

程序七:
#include<stdio.h> int main() { int* p = (int*)realloc(NULL, 40);// 此时它的功能 与
malloc 函数一样 // == (int*)malloc(40); }





<>常见的动态内存错误

<>1. 对 NULL 指针的解引用 操作

程序八:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40);
// 万一 malloc 失败,p 被赋值为 NULL for (int i = 0; i < 10; i++) { *(p + i) = i;//
非法地址,对 NULL 指针 进行解引用 } free(p); p = NULL; return 0; //解决方案 :在使用开辟的动态空间之前,用
assert(p),记得加上头文件 assert.h; 或 加上 if(p != NULL) 判断语句,对 p 进行相关的判断 }




<>2,对动态开辟的内存 的 越界访问

程序九:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(20);//
开辟了 5个 int 元素的空间 if (p == NULL)// 判断 malloc 函数 开辟动态空间 是否成功 { return 0; // 失败 }
else// 成功 { int i = 0; for (i = 0; i < 10; i++) // 我只有 5个 int元素,而现在 要访问 第 6~10
个 int 元素,形成了越界访问,导致程序崩溃 { // 改成 5 就没问题了 *(p + i) = i; } } free(p); p = NULL; }




<>3, 对 非动态 开辟内存使用 free(释放)函数

程序十:
#include<stdio.h> int main() { int a = 10;// 在 栈上 开辟空间 int* p = &a; *p = 20;
free(p); // free 对 非动态 开辟内存 使用,会造成程序崩溃 p = NULL; return 0; }




<>4.使用 free 释放动态开辟内存的一部分

程序十一:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); if
(p == NULL) { return 0; } else { int i = 0; for (i = 0; i < 10; i++) { *p++ = i;
// 解决方法 *(p+i)= i; 这样避免了 p 的地址被改变 } // 回收空间 free(p);// 如果这样直接这样释放空间,会导致程序崩溃 //
因为 p 已经不再指向该 动态开辟空间 的 起始位置,它释放第 10 个元素之后的空间(释放一部分动态开辟的空间) // free 只能释放 动态开辟空间
的起始地址(要释放就全释放) } p = NULL; return 0; }




<>5. 对同一块 动态内存 的多次释放

<>free 释放 一块动态开辟空间后,那个 空间 的地址并没有改变,此时再 free 就可能出现问题

程序十二:
#include<stdio.h> #include<stdlib.h> int main() { int* p = (int*)malloc(40); if
(p = NULL) { return 0; } // 使用 // 释放 free(p); free(p);// free 释放 一块动态开辟空间后,那个
空间 的地址并没有改变,此时再 free 就可能出现问题 return 0; } // 谁申请 谁回收 // 在 free(); 后面 手动 把 p =
NULL; 后面就算再接上一个 free,也不会有问题 // 因为 根据C语言标准:free(NULL); free 什么都不做




<>6. 对动态开辟内存 忘记释放(内存泄露)

<>这种错误的出现,会导致计算机内存被耗干,导致卡死,毕竟计算机内存有限

程序十三:
#include<stdio.h> #include<windows.h> int main() { while (1) { malloc(1); //
通过 三点(Ctrl、Alt 点 .)一线(同时按),选择任务管理器 -》 性能 // 通过观察 内存 占用 显示发现,内存正在被大量消耗, // 原因是
程序一直在申请空间,不回收,导致大量内存空间被占用(这些你不用,别人也用不了,因为你没还),这就是 内存泄露 } }




<>接下来我们来看几道面试题,来加深我对 动态内存分配 函数的 理解



<>题目 1

程序十四:
#include<stdio.h> #include<string.h> #include<stdlib.h> void GetMemory(char* p)
// p 是 str 的一份临时拷贝,即 p= NULL; { // malloc 在堆上开辟了一块空间,把该动态开辟空间的起始地址赋给 p p = (char
*)malloc(100);// 此时 p 指向 malloc 开辟的动态空间 // 动态开辟空间,调用完了,没有释放空间,会造成 内存泄露 // 而且
这函数调用完后之后,这块函数空间会被回收 // 到时我们再也找不到 这动态开辟的空间的起始地址 // 也就是说 出了这个函数 想解决 内存泄露
这个问题,都解决不了(p 是该函数的形参,只在该函数内部有效) 再举个例子,
假设有一个卧底(malloc开辟的动态空间),他的信息(空间的地址)在警察系统中都删掉了,只有 p 知道, 后来,有一天 p 死掉了(出了函数,销毁了
局部变量 p).他也没有记本本告诉别人(没有返回这块空间的地址), 那么这个卧底再也证明不了自己的身份(丢失了开辟动态空间的地址),就像无间道里一样。
没有人可以证明 他 是一个警察了(没有人知道 这块的空间的地址)} void test(void) { char* str = NULL;// dtr 的
空间 放了一个 空指针 GetMemory(str);// 传值:传的是 指针变量 str 本身(内容),不是地址 strcpy(str, "hello
world");// str 还是 空指针,因为 GetMemory(str) 没有 返回值,也没有通过地址 改变str的值 // 该表达式 意思是把
"hello world" 这个字符串 赋给 str 这个空指针 所指向的空间(无效的空间) // "hello world"
是不能赋过去的,这样会导致程序崩溃,因为访问内存已经失败了 // str并没有 指向 有效空间里,你非要把 "hello world" ,那我们就
解引用空指针,对空指针进行相印的遍历 // 空指针的值 == 0, 0 作为地址,找一个空间。在 strcpy 肯定是要 str++ ,往后面找空间。(0
作为地址,对指向的空间赋值,然后++,再进行赋值,以此类推) // 这样做 必然 会 造成 非法访问内存,从而导致 程序崩溃。 //
因为程序已经崩溃了,也就更谈不上后面的打印,虽然 打印的格式没有错误,但已经打印不出东西了 printf(str); // 通过下面一个程序,可以说明该
打印方式 没有问题 // == printf("hello world"); } int main() { test(); return 0; }
<>附加 printf 程序 : printf("%s\n", str) == printf(str)

程序十五:
#include<stdio.h> int main() { char* str = "abcdef"; printf("%s\n", str);//
abcdef printf(str);// abcdef return 0; }



<>现在 我们开始 帮助 这个卧底 证明他的身份了(修改程序)

<>改正1

程序十六:
#include<stdio.h> #include<stdlib.h> #include<string.h> void GetMemory(char** p
)// str 是一个一级指针,要二级指针来接收它地址 { // *p == str (通过地址 找到了 str) *p = (char*)malloc(100
);// 把 malloc 开辟的动态空间的起始地址赋给 *p,也就是 赋给 str // 也就说 p 在死之前,把 卧底的身份写在小本本上, 送给 str
这个人,所以 str 知道卧底的真实身份 } void test(void) { char* str = NULL; //传址 GetMemory( &str
);// GetMemory - 获取一块内存块; //把这块内存 存入 str中,方便下方字符串 拷贝 strcpy(str, "hello world");
// 此时 str 不再是 NULL, 因为 GetMemory 赋给 它 一个 有效的 动态空间 的地址 // strcpy 可以 吧 "hello
world" 赋给 str 指向的空间(malloc 函数 开辟的动态空间) printf(str); // hello world // str 跟这个卧底
对了一波 暗号 == //但是别忘了 释放动态开辟的空间,我 们通过的 指针的方式(*p == str), 把地址带回来了 free(str); str =
NULL; // 并 向卧底保证,目前并不会揭穿他的身份,因为卧底不想暴露身份(其实我怀疑他上瘾了。) } int main() { test();
return 0; }



<>改正2

程序十七:
#include<stdio.h> #include<stdlib.h> #include<string.h> char* GetMemory(char* p
)// str存的值是一个地址,要一个指针来接收它地址 { p = (char*)malloc(100); //可以这么理解, 卧底(malloc
开辟的动态空间),通过 p 死之前,留下的信息,去找 str 这个知情人。 return p; } void test(void) { char* str =
NULL; str = GetMemory(str);//传值 // 卧底找到了 str 这个知情人 strcpy(str, "hello world");
// 保险起见,对了波暗号 printf(str); // hello world free(str); // str
确认了其身份,并保证不泄露他的身份,成为他证人 str = NULL;// 不曾想,卧底趁str不注意,宰了 str 这个人。(看来他是真的上瘾了) } int
main() { test(); return 0; }




<>题目 2

程序十八:
#include<stdio.h> char* GetMemory(void) { char p[] = "hello world";// 局部数组( 栈上
开辟 !!!!),出了函数 就会被销毁(还给操作系统) return p;// 这里返回的是一个野指针 } void test(void) { char*
str= NULL; str = GetMemory(); printf(str);// 访问野指针,属于非法访问,而且再次访问 不一定就是 hello
world,很可能是乱码 } int main() { test(); return 0; }




<>题目 3

程序十九:
#include<stdio.h> #include<stdlib.h> #include<string.h> void GetMemory(char* *p
, int num) { *p = (char*)malloc(num); // 通过地址 改变了 str 的值 } void test(void) {
char* str = NULL; GetMemory(&str, 100); // if(str != NULL) 或者 assert(str),判断
malloc 开辟动态空间 是否成功, // 万一开闭失败,str 为 NULL, 导致拷贝失败,程序崩溃,这也是一个隐藏问题 strcpy(str,
"hello world"); printf(str);// 打印 hello world ,但存在内存泄露,未释放动态开辟的空间 // free(str);
// str = NULL; } int main() { test(); return 0; }




<>题目 4

程序二十:
#include<stdio.h> #include<stdlib.h> #include<string.h> void test(void) { char*
str= (char*)malloc(100); if (str)// 判断 malloc 函数 是否开辟空间成功 { strcpy(str, "hello"
); free(str);// 释放了内存,但没有 将其 置为 NULL //加上 str = NULL; 没有问题 if (str != NULL)//
条件为真 { strcpy(str, "world");// 非法访问 内存,str 所指向的空间已不属于它 printf(str);// 可能输出
world ,也可以是乱码,因为这块空间已经被释放,你再对它进行访问操作 // 什么情况都可能出现 } } } int main() { test();
return 0; }





<>柔型数组

<>在 c99 标准中,结构中的最后一个元素允许是未知大小的数组,这叫做 柔型数组 成员




<>柔性数组的 特点:

<>1.结构体中的柔型数组成员前面必须至少一个其他成员

<>2.sizeof 返回的这种结构大小不包括柔型数组的内存

<>3.包含柔型数组成员的结构 用 malloc 函数 进行内存的动态分配,并且分配的内存 应该大于 结构的大小,以适应 柔型数组 的 预期大小





<>程序二十一:
#include<stdio.h> struct s { int n; // 第一种写法 int arr[];// 最后一个成员,可以是未知大小的 //
第二种写法 // int arr2[0]; // 以上 这种数组 的 大小是未知的 ,被称为 柔性数组 成员 // 柔性 : 这个 数组 的 大小 是 可调整的
}; int main() { return 0; }
<>因为编译器的原因,可能有一种写法不支持,这时可以写另一种写法






<>柔性数组的使用

程序 二十二:

#include<stdio.h> #include<stdlib.h> struct s // 该结构体大小为 4 { int n; int arr[];
}; int main() { //struct s s; //sizeof 返回的这种结构大小不包括柔型数组的内存 //printf("%d\n",
sizeof(s));// 4, 也就说没有包含 这个 柔性数组 的大小 struct s* ps = (struct s*)malloc(sizeof(
struct s) + 5 * sizeof(int)); // 开辟 24 byte 的空间 // 口口口口 口口口口口口口口口口口口口口口口口口口口 //
n arr // 然后把这块空间的地址赋给 结构体指针变量 ps // 对于 ps 来说 这块空间是 一个结构体的空间,前四个字节 是 成员 n 的空间, 后
20 个字节 是 成员 arr 的空间 int i = 0; if (ps)// 判断 malloc 开辟动态空间是否成功 如果 ps = NULL ==
0,因为 NULL 的值 为 0,条件为假跳过if语句 { ps->n = 100; for (i = 0; i < 5; i++) { ps->arr[i]
= i;// 0 1 2 3 4 printf("%d ", ps->arr[i]); // 0 1 2 3 4 } printf("\n"); // 假设
前面 的 5 个int, 不够怎么办,要10个int , 这时就要用 realloc 函数 来扩展 struct s* ptr = (struct s *)
realloc(ps, 44);// 4 byte 结构体大小 + 柔性数组的大小 40 byte if (ptr)// 判断 realloc 创建 动态
是否成功 { ps = ptr; for (i = 5; i < 10; i++) { ps->arr[i] = i;// 5 6 7 8 9 } for (i
= 0; i < 10; i++) { printf("%d ", ps->arr[i]); // 0 1 2 3 4 5 6 7 8 9 } free(ps)
;// 用完 动态空间,一定要记得释放 ps = NULL; } } return 0; }




<>程序 二十三 (替代柔型数组的方法):

#include<stdio.h> #include<stdlib.h> struct s { int n; int* arr;// arr 整形指针 };
int main() { struct s* ps = (struct s*) malloc(sizeof(struct s));// n 和 指针变量
arr 的 空间 int i = 0; if (ps) { ps->arr = malloc(20);// 开辟一块 20 byte 的动态空间,将其地址
赋给 指针变量 arr if (ps->arr) { for (i = 0; i < 5; i++)// 赋值 { ps->arr[i] = i; printf
("%d ", ps->arr[i]);// 0 1 2 3 4 } // 图方便 少写一个 打印 for 循环 printf("\n"); } //调整大小
int* ptr = (int*)realloc(ps->arr,40);// 开辟一块 40 byte 的动态空间,将其地址 赋予 指针变量 arr if (
ptr) // 判断 realloc 开辟动态空间是否成功 { ps ->arr = ptr; for (i = 5; i < 10; i++) { ps->
arr[i] = i; } for (i = 0; i < 10; i++) { printf("%d ", ps->arr[i]);// 0 1 2 3 4
5 6 7 8 9 } } } free(ps->arr);// 要先释放arr,如果先释放 ps 会找不到指针变量 arr free(ps); ps->arr
= NULL; ps = NULL; return 0; }




<>那么 柔型数组 和 替代方法 有何区别




<>我们先来对比一下:

<>柔性数组方法里(在 c99 标准中),在计算 结构体大小时,是没有包含 arr 数组的大小,而且开辟空间之前,我们主动算一个大小,把 n 和
数组arr 的大小(空间),一次性开辟成功,再交由 ps(接收开辟动态空间的地址) 来维护,柔性数组 其实是 把 n 和 数组arr 开辟到一块连续的空间里)



<>而 第二种方法: 先 malloc 创建这个结构体 空间(包含 n 和 arr),然后 再由 malloc 开辟出一个动态空间,将其地址嫁给 指针变量
arr,( malloc 开辟了二次动态空间)



<>区别:

<>柔性一次性开辟好空间, 替换的方法 需要 二次

<>意味着释放(free)动态空间的时候,柔性 一次释放,替换的方式需要 二次

<>那么就是说 在替换方法中 malloc 开辟的动态空间越多, free也就越多,就很有可能会出现 忘记释放动态空间 而造成 内存泄漏 的问题,或者说
释放 错误对象 的 空间

<>而 在柔性数组中,只需要 malloc 一次,free也只需要一次,这样出错概率小





<>注意 malloc 函数 在开辟空间的时候,它觉得那个空间是空的,就在那里开辟空间

<>这样开辟开辟的方式,必然会导致 内存 里面 会有一些 空间 不大不小,导致无法使用,而造成空间的浪费(内存碎片)

<>内存碎片 越多,空间利用率 越差。(malloc 用的越多,出现内存碎片概率就越高,空间的利用率越低)

<>所以 使用 柔性数组,出现的内存碎片的概率很低,空间利用率高。

<>替换柔性数组的方法: 根据 malloc 函数的 特性:它觉得那个空间是空的,就在那里开辟空间

<>它所创建 空间 不是连续的(就是 a 这里 我创建一块,b哪里创建一块空间,在内存上 两者之间并不相连[连续],)

<>柔性数组 : n 和 arr 都在一块连续的内存块 上存储 (在一块连续的内存块上,访问内存 的 效率更高)






<>在这里我们先来了解一些东西

<>存储器 - 金字塔层次结构:越靠近CPU速度越快,容量越小,价格越贵

<>金字塔层次结构

<>当 CPU 处理数据的时候 ,从寄存器里拿,因为它更快。

<>这时候我们就会把 内存的数据 , 放到 高速缓存 里,高速缓存的数据 放到 寄存器 里 。CPU 在往 寄存器里拿,数据最终都是来自于内存



<>局部性原理:

<>时间局部性(temporal locality):如果一个数据被访问了,那么它在短时间内还会被再次访问。如 LRU
缓存机制,将频繁访问的数据保存在内存中。

<>空间局部性(spatial locality):如果一个数据被访问了,那么和它相邻的数据也很快会被访问。如果数组的 CPU 预读功能。




<>在这里我们只需了解一下,不必太过深入

<>当我们访问一个内存(空间)数据的时候,接下来 80% 的可能性,你访问是它周边的 数据。

<>内存中,如果我们存放数据的时候,是连续存放的话,那么接下来,我们就把这些连续的数据 放到 寄存器
里,这样我们的命中率就提升了,这时候计算机的访问效率更高些。

<>也就是说 替换柔型数组的方法 ,开辟 的 两个 空间 并不连续,所在访问是可能 第一次访问不到,因为 太分散了(不连续)

<>意味着 cpu 往寄存器里拿,很有很有可能拿不到我们想要的,它就往 高速缓存 里面拿,高速缓存没有,就往内存里拿.

<>这样我们命中率大大下降,计算机的访问的效率更低。




<>柔型数组的优点:

<>1.开辟一块连续的空间,命中率就提升,计算机的访问效率更高。

<>2.出现 内存碎片 的 概率很低,空间利用率高。

<>3.只需要 malloc 一次,free也只需要一次,这样出错概率小(忘记释放动态内存,或者释放错误的空间对象)

<>所以综合来说 来说:

<>柔型数组的优点就是:方便内存释放,访问速度更快。




<>以上就是 动态内存分配(Dynamic Memory allocation)的全部内容。

技术
©2019-2020 Toolsou All rights reserved,
[数据结构]八大排序算法(C语言)总结G1垃圾收集器面试题Android中使用微信H5支付时支付结果刷新问题32-jdbc工具类大学里要参加竞赛吗?都有哪些竞赛可以参加?震惊!!C++居然可以发出声音!C语言之链表入门(超详解)Java实现一个疫情人数管理系统如何用python实现斐波那契数列的前100个MyBatis循环Map(高级用法)