当前位置: 首页 > news >正文

【进阶C语言】动态内存分配

本章大致内容介绍:

1.malloc函数和free函数

2.calloc函数

3.realloc函数

4.常见错误案例

5.笔试题详解

6.柔性数组

一、malloc和free

1.malloc函数

(1)函数原型

函数参数:根据用户的需求需要开辟多大的字节空间,为无符号的字节。

返回值:malloc函数成功开辟内存后,会返回该内存的起始地址,可以根据需要强制转换成任意的类型;若开辟空间失败,则会返回空指针(NULL)。

头文件:#include<stdlib.h>

(2)使用方法

1)申请空间:

#include<stdio.h>
#include<stdlib.h>
int  main()
{int* p = (int*)malloc(40);return 0;
}

目的:申请十个整形空间,所以参数传:4*10=40。

结果:用一个整形指针来接收其返回值

2)检查安全和使用

#include<stdio.h>
#include<stdlib.h>
int  main()
{int* p = (int*)malloc(40);if (p == NULL)//必须对指针安全性检查{printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i=0;i<10;i++){*(p+i) = i;}i = 0;for (i=0;i<10;i++){printf("%d\n",*(p+i));}return 0;
}

当使用结束之后,我们需要删除该动态生成的空间,则需要对空间进行释放,这就是我们接下来讲的free。

2.free函数

(1)函数原型

1.参数为动态开辟内存的首地址

2.无参数返回

3.头文件#include<stdlib.h>

(2)配合动态内存开辟的函数使用

前面malloc函数开辟的内存还没释放,接下来它们配合使用。

#include<stdio.h>
#include<stdlib.h>
int  main()
{int* p = (int*)malloc(40);if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i=0;i<10;i++){*(p+i) = i;}i = 0;for (i=0;i<10;i++){printf("%d\n",*(p+i));}free(p);p = NULL;//及时将指针置空return 0;
}

注意事项:

1.free只能释放由动态内存开辟的空间

2.free释放的是指针所指向的那块空间,释放后指针仍在,但是指向的空间不咋了,就会变成野指针,所以我们需要及时置空。

二、calloc

1.函数定义

函数参数:第一个参数是需要开辟的数据个数,第二个是该数据类型的内存大小。

返回值:calloc函数成功开辟内存后,会返回该内存的起始地址,可以根据需要强制转换成任意的类型;若开辟空间失败,则会返回空指针(NULL)。

头文件:#include<stdlib.h>

2.calloc的使用

目的:需要开辟10个整形空间

#include<stdio.h>
#include<stdlib.h>
int  main()
{int* p = (int*)calloc(10,sizeof(int));if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}i = 0;for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}free(p);p = NULL;//及时将指针置空return 0;
}

运行结果:

根据malloc和calloc函数使用的两段代码,好像除了名字和参数之外,其他没什么不同呀?其实他们还有一处区别。

3.calloc函数与malloc函数的区别

(1)区别

malloc函数开辟好空间之后,并不会对其初始化,但是calloc函数开辟好空间之后,会将数据的每一个字节都初始化成0。

(2)对照

1)malloc

2)calloc

除了以上三点不同之外,其他的都一样。所以我们需要根据内存需求,需不需要初始化内存而选择合适的开辟方式。

三、realloc

1.函数定义

realloc可以对已有的内存进行调整

函数参数:ptr是要调整的内存地址,size为内存调整之后的新大小,单位是内存总大小(字节)

返回值:内存调整后的起始地址,同样有申请内存成功和失败两种情况

头文件:#include<stdlib.h>

2.realloc申请空间成功的两种情况

(1)原空间后的空间足够大

开辟空间方式:直接原有内存之后直接追加空间,原来空间的数据不发生变化。

(2)原空间之后没有足够大的空间 

     原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小
的连续空间来使用。这样函数返回的是一个新的内存地址。而原有数据也会被拷贝到新内存中。

3.realloc的使用

#include<stdio.h>
#include<stdlib.h>
int  main()
{int* p = (int*)calloc(10,sizeof(int));if (p == NULL){printf("申请空间失败\n");return;}//申请成功就开始用int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;}i = 0;for (i = 0; i < 10; i++){printf("%d\n", *(p + i));}//要求加大空间内存int* ptr = realloc(p,40*sizeof(int));if (ptr == NULL){printf("内存开辟失败\n");return;}p = ptr;//将新开辟好的内存赋值原地址free(p);p = NULL;//及时将指针置空return 0;
}

realloc的使用一般在原有空间的情况下,同样也需要对指针进行判空操作和free。

我们也可以看到,free和这些函数是紧紧联系在一起的。

四、常见错误解析

这些错误都是动态内存开辟前后的问题,与指针也有很大的联系

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

错误写法:

int main()
{int* p = (int*)malloc(4);*p = 20;printf("%d\n",*p);return 0;
}

malloc有可能开辟动态内存失败,则会返回NULL,这个时候对NULL指针解引用操作就是非法的。

正确写法:

int main()
{int* p = (int*)malloc(4);*p = 20;if (p == NULL)//对指针安全性限制{perror(malloc);return;}printf("%d\n",*p);
//后续需要对内存释放return 0;
}

 知识点1:在每次动态内存开辟完成之后,都要先对其指针进行判空操作;若非空,才能对其进行后续的操作。

2.对动态开辟空间的越界访问

错误写法:

int main()
{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);
}

错误的后果提示:

知识点2:在对指针解引用操作时,要注意指针所指向的个数

3.对非动态开辟内存使用free释放

错误写法:

test()
{int a = 100;int* p = &a;free(p);//错误
}

知识点3:free函数只能释放动态开辟的内存,否则会非法。

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

错误写法:

int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置

当p++之后,p指向的起始位置就变了,当free(p)之后,会释放不完整,也会造成内存泄漏。

知识点4:使用指针,尽量不要改变指针指向的起始地址。可以再重新使用新指针进行++或--操作;或者+1/-1操作。

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);
}

这是忘记对动态内存的释放的,也是不可取的。

五、关于动态内存开辟的笔试题

分析下面四道代码题存在什么问题

运行Test函数会有什么样的后果

1.对NULL解引用操作

void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}

1.str指针为空

2.malloc开辟的空间只是被p指向,没有被str指向(相当于形参的改变不影响实参)

3.所以strcpy函数就会对NULL指针进行解引用操作

4.没有free操作,还会操作内存泄漏

图解:

正确写法:

void GetMemory(char** p)
{*p = (char*)malloc(100);
}
void Test(void)
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str=NULL;
}

2.

问题代码:

char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}

1.字符数组p为栈空间的局部变量,函数返回后会被销毁

2.数组被销毁,返回的p就是野指针(所指向的空间已不属于自己)

 类型代码情况:

int* test()
{int a = 10;return &a;
}
int main()
{int* p = test();printf("%d\n",*p);return 0;
}

这种运行的结果仍然可以得到10,虽然空间依然属于p,但是值仍在,没有被其他的数据覆盖。但是下面这种情况则不行。

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);
}

动态开辟的内存,最后没有被free释放

4.

问题代码:

void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}

1.str所指向的空间已被销毁

2.str变成野指针,对其解引用操作为非法

六、柔性数组

1.柔性数组的定义

标准定义:C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

也就是说,柔性数组不是指简单的数组,而是在结构体中的数组。

有两种写法:

第一种:

typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;

 a数组就称为柔性数组。但是这种定义方式容易报错,所以我们还有第二种。

第二种:

typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;

就是不需要指定数组的大小,数组的大小是未知的。

2.柔性数组的特点

(1)sizeof 返回的这种结构大小不包括柔性数组的内存。

(2)结构中的柔性数组成员前面必须至少一个其他成员。

因为柔性数组是不计入sizeof的计算的,只有柔性数组成员sizeof就会出错。

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

在计算包含柔性数组的结构体时,柔性数组是不计入内存的计算的。大于结构体内存大小的部分就会分配给柔性数组。

 (4)代码验证

struct S
{int a;int arr[];
};
int main()
{struct S s;printf("%zd\n",sizeof(s));//计算该结构体的内存大小return 0;
}

运行的结果:

 柔性数组确实是不会参加sizeof对结构体的计算

3.柔性数组的使用

(1)开辟空间

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}return 0;
}

(2)增容(realloc函数)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}struct S* str = (struct S*)realloc(ps,sizeof(struct S)+40);if (str != NULL){ps = str;}else{perror(realloc);return;}return 0;
}

 用malloc开辟空间之后,再用reallo增容(减容)。增容之后的空间都会加在柔性数组上,这个时候数组的大小就可以根据realloc变化,因此称为柔性数组。

 

4.柔性数组的优势

(1)方便内存释放

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
struct S
{int a;int arr[];//定义一个柔性数组
};
int main()
{struct S* ps=(struct S*)malloc(sizeof(struct S)+16);if (ps == NULL){perror(malloc);return;}struct S* str = (struct S*)realloc(ps,sizeof(struct S)+40);if (str != NULL){ps = str;}else{perror(realloc);return;}free(ps);ps = NULL;return 0;
}

因为开辟的空间都是连续的,在一块内存中,所以只需要free一次即可。

我们再对比一下另一种写法就更加明显了。

 结构体中有指针的写法:

struct S
{char c;int i;int* data;//定义一个指针
};
int main()
{struct S* ps = (struct S*)malloc(sizeof(struct S));if (ps == NULL){perror("malloc1");return 1;}ps->c = 'w';ps->i = 100;ps->data = (int*)malloc(20);if (ps->data == NULL){perror("malloc2");return 1;}int i = 0;for (i = 0; i < 5; i++){ps->data[i] = i;}for (i = 0; i < 5; i++){printf("%d ", ps->data[i]);}//空间不够了,增容int* ptr = (int*)realloc(ps->data, 40);if (ptr == NULL){perror("realloc");return 1;}else{ps->data = ptr;}//增容成功就使用//...//释放free(ps->data);//第一次ps->data = NULL;free(ps);//第二次ps = NULL;return 0;
}

1.两次开辟空间的原因是使得他们的数据都开辟在堆区上

2.使得跟第一种一样的写法,突然第一种的优势

3.这种写法开辟的空间是不连续的,容易造成空间零碎空间,导致空间浪费。

(2)有利于访问速度和节约内存

连续的内存有益于提高访问速度,也有益于减少内存碎片,更大程度的利用内存空间。

相关文章:

【进阶C语言】动态内存分配

本章大致内容介绍&#xff1a; 1.malloc函数和free函数 2.calloc函数 3.realloc函数 4.常见错误案例 5.笔试题详解 6.柔性数组 一、malloc和free 1.malloc函数 &#xff08;1&#xff09;函数原型 函数参数&#xff1a;根据用户的需求需要开辟多大的字节空间&#xff…...

手机上记录的备忘录内容怎么分享到电脑上查看?

手机已经成为了我们生活中不可或缺的一部分&#xff0c;我们用它来处理琐碎事务&#xff0c;记录生活点滴&#xff0c;手机备忘录就是我们常用的工具之一。但随着工作的需要&#xff0c;我们往往会遇到一个问题&#xff1a;手机上记录的备忘录内容&#xff0c;如何方便地分享到…...

LeetCode 2251. 花期内花的数目:排序 + 二分

【LetMeFly】2251.花期内花的数目&#xff1a;排序 二分 力扣题目链接&#xff1a;https://leetcode.cn/problems/number-of-flowers-in-full-bloom/ 给你一个下标从 0 开始的二维整数数组 flowers &#xff0c;其中 flowers[i] [starti, endi] 表示第 i 朵花的 花期 从 st…...

【3】贪心算法-最优装载问题-加勒比海盗

算法背景 在北美洲东南部&#xff0c;有一片神秘的海域&#xff0c;那里碧海蓝天、阳光 明媚&#xff0c;这正是传说中海盗最活跃的加勒比海&#xff08;Caribbean Sea&#xff09;。 有一天&#xff0c;海盗们截获了一艘装满各种各样古董的货船&#xff0c;每一 件古董都价值连…...

JavaScript 的 for 循环应该如何学习?

JS for 循环语法 JS for 循环适合在已知循环次数时使用&#xff0c;语法格式如下&#xff1a; for(initialization; condition; increment) {// 要执行的代码 }for 循环中包含三个可选的表达式 initialization、condition 和 increment&#xff0c;其中&#xff1a; initial…...

C++核心编程--对象篇

4.2、对象 4.2.1、对象的初始化和清理 用于对对象进行初始化设置&#xff0c;以及对象销毁前的清理数据的设置。 构造函数和析构函数 防止对象初始化和清理也是非常重要的安全问题 一个对象或变量没有初始化状态&#xff0c;对其使用后果是未知的同样使用完一个对象或变量&…...

安装php扩展XLSXWriter,解决php导入excel表格时获取日期变成浮点数的方法

安装php扩展XLSXWriter 1、下载安装包 PECL :: Package :: xlswriter #例如选择下载1.3.6版本 2、解压下载包 tar -zxvf xlswriter-1.3.6.tgz 3、进入文件夹,编译 cd xlswriter-1.3.6 phpize ./configure --with-php-config=/usr/local/php7.1/bin/php-config make&am…...

Vue+element开发Simple Admin后端管理系统页面

最近看到各种admin&#xff0c;头大&#xff0c;内容太多&#xff0c;根本不知道怎么改。所以制作了这个项目&#xff0c;只包含框架、和开发中最常用的表格和表单&#xff0c;不用自己从头搭建架构&#xff0c;同时也容易上手二次开发。可以轻松从其他开源项目整合到本项目。项…...

源码编译安装pkg-config

安装环境&#xff1a;银河麒麟 1 到这个网址下载pkg-config源码&#xff1a; Index of /releases (pkg-config.freedesktop.org) 2 解压 3 进入解压后的目录。输入 ./configure 但是报错。 4 根据报错信息&#xff0c;将configure改为&#xff1a; ./configure --with-i…...

游览器找不到服务器上PHP文件的一种原因

最近在练习搭建网站&#xff0c;遇到游览器找不到服务器上的php文件的问题。后来查找发现&#xff0c;apache文档根目录跟apache虚拟主机文档根目录不同&#xff0c;服务器开启了虚拟主机功能。这导致游览器找不到php文件。使用的环境LAMP 里操作系统和软件版本如下&#xff1a…...

C++之std::function的介绍

C之std::function的介绍 std::function和函数指针的区别介绍std::function 的常见用法包括用法举例 std::function和函数指针的区别介绍 std::function 和函数指针在 C 中都可以用来存储和调用函数&#xff0c;但它们的使用方式和功能有所不同。 函数指针是一种指向函数的指针…...

卷积神经网络学习(一)

CNN应用对象是图像&#xff0c;CNN可被应用于的任务&#xff1a; 1、分类&#xff08;classification&#xff09;&#xff1a;对图像按其中的物体进行分类&#xff0c;如图像中有人与猫&#xff0c;则图像可分为两类。 2、目标检测&#xff08;object detection&#xff09;&a…...

使用KEIL自带的仿真器仿真遇到问题解决

*** error 65: access violation at 0x40021000 : no read permission 修改debug选项设置为下方内容。...

4700 万美元损失,Xn00d 合约漏洞攻击事件分析

4700 万美元损失&#xff0c;Xn00d 合约漏洞攻击事件分析 基础知识 ERC777 ERC777 是 ERC20 标准的高级代币标准&#xff0c;要提供了一些新的功能&#xff1a;运营商及钩子。 运营商功能。通过此功能能够允许第三方账户代表某一合约或者地址 进行代币的发送交易钩子功能。…...

第5讲:v-if与v-show的使用方法及区别

v-if条件判断 v-if是条件渲染指令&#xff0c;它根据表达式的真假来删除和插入元素&#xff0c;它的基本语法如下&#xff1a; v-if “expression” expression是一个返回bool值的表达式&#xff0c;表达式可以是一个bool属性&#xff0c;也可以是一个返回bool的运算式 &#…...

C理解(一):内存与位操作

本文主要探讨C语言的内存和为操作操作相关知识。 冯诺依曼结构和哈佛结构 冯诺依曼结构&#xff1a;数据和代码放在一起,便于读取和修改,安全性低 哈佛结构是&#xff1a;数据和代码分开存放,安全性高,读取和修麻烦 内存 内存是用来存储全局变量、局…...

ESP8266使用记录(四)

放上最终效果 ESP8266&Unity游戏 整合放进了坏玩具车遥控器里 最终只使用了mpu6050的yaw数据&#xff0c;因为roll值漂移…… 使用了https://github.com/ElectronicCats/mpu6050 整个流程 ESP8266取MPU6050数据&#xff0c;处理后通过udp发送给Unity显示出来 MPU6050_Z…...

云原生Kubernetes:K8S安全机制

目录 一、理论 1.K8S安全机制 2.Authentication认证 3.Authorization授权 4.Admission Control准入控制 5.User访问案例 6.ServiceAccount访问案例 二、实验 1.Admission Control准入控制 2.User访问案例 3.ServiceAccount访问案例 三、问题 1.生成资源报错 2.镜…...

【数据结构】归并排序、基数排序算法的学习知识点总结

目录 1、归并排序 1.1 算法思想 1.2 代码实现 1.3 例题分析 2、基数排序 2.1 算法思想 2.2 代码实现 2.3 例题分析 1、归并排序 1.1 算法思想 归并排序是一种采用分治思想的经典排序算法&#xff0c;通过将待排序数组分成若干个子序列&#xff0c;将每个子序列排序&#xff…...

【C++】C++模板进阶 —— 非类型模板参数、模板的特化以及模板的分离编译

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C多…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

k8s从入门到放弃之Ingress七层负载

k8s从入门到放弃之Ingress七层负载 在Kubernetes&#xff08;简称K8s&#xff09;中&#xff0c;Ingress是一个API对象&#xff0c;它允许你定义如何从集群外部访问集群内部的服务。Ingress可以提供负载均衡、SSL终结和基于名称的虚拟主机等功能。通过Ingress&#xff0c;你可…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试

作者&#xff1a;Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位&#xff1a;中南大学地球科学与信息物理学院论文标题&#xff1a;BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接&#xff1a;https://arxiv.…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

Cesium1.95中高性能加载1500个点

一、基本方式&#xff1a; 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

【项目实战】通过多模态+LangGraph实现PPT生成助手

PPT自动生成系统 基于LangGraph的PPT自动生成系统&#xff0c;可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析&#xff1a;自动解析Markdown文档结构PPT模板分析&#xff1a;分析PPT模板的布局和风格智能布局决策&#xff1a;匹配内容与合适的PPT布局自动…...

Map相关知识

数据结构 二叉树 二叉树&#xff0c;顾名思义&#xff0c;每个节点最多有两个“叉”&#xff0c;也就是两个子节点&#xff0c;分别是左子 节点和右子节点。不过&#xff0c;二叉树并不要求每个节点都有两个子节点&#xff0c;有的节点只 有左子节点&#xff0c;有的节点只有…...

【Oracle】分区表

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...