第4章 循环变换
4.1 适配体系结构特征的关键技术
由于高级语言隐藏了底层硬件体系结构的大量细节,如果不经过优化直接将高级程序设计语言编写的程序部署在底层硬件上,往往无法充分利用底层硬件体系结构的处理能力。
算子融合不仅可以提高计算密度,还可以避免相邻算子之间通过GPU设备内存通信引入的数据访问开销。
循环变换和不同循环变换之间的组合是实现面向底层硬件体系结构的重要优化手段。本章将基于多面体模型详细介绍优化编译器实现循环变换及不同循环变换之间组合的方式,并介绍各种循环变换的应用场景,以及一些循环变换之间的关系。
4.2 预处理
只要维持程序的循环携带依赖,那么循环变换总是有效的。在开始分析依赖关系之前,优化编译器应该提供一些能够对循环嵌套实现预处理的机制。一方面,预处理能够简化依赖测试或精确依赖分析的过程;另一方面,预处理也能够消除一些复杂的循环携带依赖,为实现更多的循环变换创造环境。
4.2.1 循环正规化
循环正规化使得循环嵌套中每个循环下界从0开始运行到某个确定的上界,并且循环的步长为1.同时,使用新的循环索引变量替换的仿射函数表达式区替换原循环变量在循环内的所有引用。即循环的下界为0,上界为(U-L)/r,步长为1,。原循环索引变量为i,之后为r*i + L。注意r应该是一个具体的常数值而不是符号常亮,否则难以通过静态分析来判定依赖是否存在。尽管可能通过引入谓词条件的方式将非仿射表达式转换为仿射表达式的形式,但这种向上近似的方法往往会导致冗余的循环跌打,程序的语义则通过引入的谓词条件来保证。
4.2.2 死代码删除
在实际应用中,可能会存在一些不会被其他有用的语句引用结果的语句,即程序的死代码。有用的语句是指用于输出程序结果的代码,或者那些再被优化的程序中仍然需要执行的代码。
死代码删除可以在计算出程序的数据流信息之后,利用向下暴露集与程序迭代空间以及依赖关系之间的基本操作来完成,其基本思想是先根据近似数据流分析方法计算出被优化的程序片段的向下暴露集,并依此计算出所有向下暴露集中语句实例的流依赖源点的集合,将二者集合求并,得到一个新的集合,向这些变量写入数据的语句实例都不会是死代码,重复该过程,直到求并之后的集合不再发生变化。由于死代码可以与其他语句产生数据流关系,因此将所有死代码语句导致的依赖从数据流关系和各种依赖关系中删除,减少编译器对程序实施循环变换的约束。
4.2.3 别名分析
别名分析阻碍优化编译器分析依赖关系最关键的因素之一,特别是对以多面体模型为基础的优化编译器而言,因为多面体没有办法判定不同内存地址单元之间的别名关系,也就无法确定语句实例之间是否有依赖。
大多数基于多面体模型的优化编译器只是简单地认为数组之间不再存在别名关系。
一种常用的方法是在程序设计语言级别引入辅助别名分析或者消除别名的语言特性。例如,C语言提供的restrict关键字。另一种方式是优化编译器在实施程序转换时,对可能产生别名关系的数组进行标记,并依靠运行时技术确定别名关系是否存在。
4.2.4 迭代空间分裂
为了减少二维stencil中空间维度之间的长距离依赖在每个维度上的距离向量分量,可以采用一种称为迭代空间分裂的方法。
对原迭代空间中非依赖方向进行切割,同一时间维度上的子空间可以通过折叠,将原来的依赖转换。迭代空间分裂为子空间的折叠创造了条件,但子空间的折叠却需要依靠多面体模型对循环迭代空间进行一维或多维反向的循环偏移或反转变换才能获得有利于循环变换的迭代空间。
4.3 多面体模型中的循环变换
与传统的并行编译器所采用的幺模矩阵相比,多面体模型具备以下几个特点:
1. 应用范围广。幺模矩阵只能处理完美嵌套循环,而多面体模型对输入程序的约束更少,不仅处理完美循环嵌套,而且对非完美循环也支持。
2. 表示能力强。幺模矩阵只能处理循环变换、切斜和反转等变换。而多面体还能处理循环分块、合并和分布等在内的几乎所有循环变换。
3. 优化空间大。多面体模型能处理更多的循环变换的组合。
除了面向通用体系结构的源到源优化工具Pluto和PPCG外,GCC、LLVM、Opn64和IBM XL编译器都集成了多面体优化相关的模块。也在深度神经网络和自动优化和部署受到了广泛的关注:MLIR, TensorComprehensions,Tiramisu, PlaidML/Stripe和Diesel等深度学习编译器中发挥了不可替代的作用。
4.3.1 循环变换分类
根据循环变换对程序特征的改变,分为以下几种:
1. 改变程序算法设计:是通过对算法的调整来降低时间或空间复杂度的。例如,在不考虑稳定性的前提下,可以使用快排代替冒泡排序。
2. 改变程序计算顺序:往往是为了提升程序的并行性或数据的局部性。例如循环合并就是为了缩短相邻循环嵌套访问数据之间的“生产-消费”距离、提升数据的时间局部性而实现的循环变换。
3. 仅改变循环结构、但不改变程序计算顺序:主要是为了生成对目标体系结构友好的代码而设计或实现的。例如循环展开就是一种为了充分利用细粒度指令流水并行而设计的循环变换。
4. 改变程序数据布局和被访问内存地址单元:充分利用目标体系结构上的存储层次结构。
编译器很难实现第一种循环变换,基于多面体的编译器能够自动实现后三种的循环变换及其组合。后三种循环变换几乎都需要考虑目标体系结构的特征,因此如何在多面体模型中实现对目标体系结构的抽象是实现循环变换的关键。
4.3.2 循环变换的复杂性
基于多面体模型的循环变化将循环的迭代空间表示成空间多面体,并通过多面体上的几何操作达到分析和优化程序的目的。多面体模型利用迭代空间、访存映射、依赖关系和调度表示程序及其语义。其中,调度表示语句实例与其对应的字典序之间的仿射函数,多面体模型实现循环变换的方法就是在满足依赖关系的前提下,将一种调度转换成另一种调度的过程,该过程也被称为调度变换。多面体模型首先将循环嵌套内的语句实例构成的集合表示为空间多面体的形式。从几何角度来看,多面体模型上的调度变换实质上就是多维空间几何的变基过程。
// CPU 一段可并行循环
for(t = 0; t < T; t+=1)for(i = 0; i < N; i+=1)A[t+1][i] = 0.25 * (A[t][i+1] -2*A[t][i] + A[t][i-1]);// GPU 二段可并行循环
for(t = 0; t < T; t+=1)for(it = 0; it < (N-2)/4; it+=1)for(ip = max(1, 4*ip); ip < min(N-1, 4*ip+4); ip+=1)A[t+1][ip] = 0.25 * (A[t][ip+1] -2*A[t][ip] + A[t][ip-1]);
只考虑并行性的前提下,循环变换的代码在硬件上的部署似乎看起来没有那么麻烦。然而,一个无法忽视的问题是现代体系结构上的存储层次结构。尽管一些底层的编译技术已经充分考虑到如寄存器分配、指令流水线并行等细粒度策略,但是面向高速缓存的数据局部性优化却是循环变换不得不考虑的问题。
在基于多面体的编译器中,代码生成阶段的输入是表示程序迭代空间的集合和表示程序调度的映射。其中,表示程序调度的映射必须是从语句实例集合到字典序之间的仿射函数。
对迭代空间进行粗暴地矩形划分是非法的,因为任意两个水平相邻的分块之间存在依赖环,而多面体模型的代码生成器限制循环只能延(t,i)坐标轴方向切割。细看之后发现,沿i轴获得循环分块是合法的。接下来解决t轴切割。t轴的切割方向与程序中的两个依赖,即左上和右上方向的依赖都相交。如果沿着两个依赖的其中一个方向进行切割并构造新的分块形状。不难发现,这两种分块形状都不会导致任意方向相邻两个分块之间的依赖环。
要构造上述分块,i轴保持不变,另外一个坐标轴的指向左上或右上的依赖方向平行,需要编译器自动实现对循环分块友好的循环变换及这些循环变换的不同组合,这对编译器很重要也很困难。
循环变换的目的有时候是互相冲突的,以程序并行性和数据局部性为例,上述分块没有实现最大化程序并行性的目的,尽管提高了数据局部性。
带有OpenMP编译指示的代码在运行时有同步开销,编译指示在循环嵌套的层数越靠近,同步的开销就越小。循环分块增大了同步开销,因此损失了程序并行性。
4.3.3 Pluto调度算法
Pluto算法以自动实现循环分块为目的,在实施循环分块之前试图寻找能够最大化循环分块可能性的循环变化组合。
Pluto调度算法的核心是兼顾程序并行性和数据局部性的前提下,利用一种定义良好的代价模型,自动确定有利于实现循环分块的顺序关系,通过计算原始程序的顺序关系和新的顺序关系之间的映射函数,自动实现循环分块友好的循环变换组合。
4.4 仿射循环变换
任何能用多维仿射变换表示的循环变换都成为仿射循环变换。Pluto调度算法先进行仿射变换,然后在循环分块。
4.4.1 循环交换interchange
向量化:使用向量寄存器;并行化:无依赖;
循环交换将完美嵌套循环的两个循环顺序进行交换,通过这种技术既可以将可向量化的循环移动到循环嵌套的最内层以提高程序的向量化效果,也可以将可并行化的循环移动到循环嵌套最外层以增加程序的并行粒度,减少同步开销。
Pluto调度算法的代价模型是面向多核架构设计的,所以Pluto算法在实现循环交换时,更倾向于将可并行化的循环移动到循环嵌套的外层,以提高并行的粒度,并降低同步开销。
4.4.2 循环反转reversal
循环反转是一种将循环嵌套某一层循环的迭代方向进行反转,并将循环步长设置为原始值的相反数。
for (int i = 1; i < M; i++)for (int j = 1; j < N; j++)for (int k = 0; k < K - 1; k++)A[i][j][k] = A[i][j-1][k+1] + A[i-1][j][k+1];// reverse loop
for (int k = K-2; k >= 0; k--)#pragema omp parallel forfor (int i = 1; i < M; i++)for (int j = 1; j < N; j++)A[i][j][-k] = A[i][j-1][-k+1] + A[i-1][j][-k+1];
4.4.3 循环延展scaling
循环延展通过延展循环索引变量的取值范围和循环步长的技术,目的在于将程序中不规则的依赖转换成灾迭代空间上全局一致的依赖,从而使程序迭代之间的依赖距离向量分量能够用整数表示,依此来为其他循环变换创造机会。
for (int i = 0; i < N; i++)f[i] = fin[i];
for (int i = 0; i < N/2; i++)g[i] = f[2*i+1] * f[2*i-1];
for (int i = 0; i < N; i++)h[i] = g[i/2+1] * g[i/2];
将g[i]循环以延展为2展开,所有循环迭代之间的依赖在整个迭代空间上全局一致,且3个循环还可以合并。
4.4.4 循环倾斜skewing
循环倾斜通过改变完美循环嵌套对应迭代空间形状的循环变换技术,但并不会改变迭代之间的执行顺序。例如(t, i) --> (t, 2t+i)。
for(t = 0; t < T; t+=1)for(i = 0; i < N; i+=1)A[t+1][i] = 0.25 * (A[t][i+1] -2*A[t][i] + A[t][i-1]);
4.4.5 循环合并fussion
循环合并是一种通过将多个循环嵌套融合在一起形成一个统一的迭代空间,从而便于编译器实现其他优化的循环变换技术。循环合并本身是一个非常复杂的过程,合并后的循环可能会失去并行性或无法实现循环分块,因为可能产生的依赖问题。循环合并通过循环的融合缩短程序数据之间的“生产--使用”距离,从而提高数据的局部性。
4.4.6 循环分裂fission
循环分裂是一种将一个循环嵌套在分裂成多个循环嵌套的循环变换技术。循环分裂通过将循环嵌套某层循环上的循环携带依赖转换为循环无关依赖,来提升循环的并行性。
4.5 近似仿射变换
在仿射循环变换中,用于循环变换的表达式只包含加减法,乘法以及这些操作的组合,并且表示循环变换的仿射变换中不会涉及存在量词。在一些循环变换中,除法、取模以及存在量词是不可避免的,所以需要借助近似仿射表达式来表示循环变换。
4.5.1 循环分块blocking
循环分块将循环嵌套空间划分成不同的分块并改变循环迭代执行顺序的循环变换技术。如果在未经循环倾斜的原始迭代空间上能够实现正确的循环分块,那么就称为矩形分块。在经过一些仿射循环变换的组合之后才能进行循环分块所得到形状可能是平行四边形后者其他形状。
4.5.2 循环分段strip-mining
循环分段将一个循环的迭代空间分成多个子集,并将每个子集作为一个调度单元进行执行的循环变换技术。在多核架构中,循环分段的实现往往是隐式地分配单个循环迭代空间的不同子集给不同线程。但是在多级并行硬件抽象的体系结构,例如GPU上的线程块和线程,循环分段必须显式执行。
由编译器显式实现的循环分段往往会带来额外的循环执行开销,因为引入了新的循环边界判断条件。除非能提升程序的并行性,否则并不鼓励在优化编译器中显示地实现循环分段。循环分段也可看做循环分块在单层循环上的特殊情况。
4.5.3 循环展开压紧unroll and jam
循环展开压紧将某两层的外层循环展开后再将内层循环进行合并的循环变换技术,压紧效果在于减少了向量寄存器取数操作的次数,和展开效果在于增加不同计算功能部件之间的指令流水线并行效率。
4.6 代码生成过程的循环变换
代码生成过程中的循环变换包括分块分离、循环展开和循环剥离。
4.6.1 分块分离tile isolation
在迭代空间边界上由于与迭代空间相交而被舍去部分区域的分块被称为半块,其他没有被舍去的分块为整块。分块分离式一种将半块和整块分离,以消除循环嵌套中的重叠边界条件限定条件(min、max)。
分开分离通过消除生成代码中循环边界的min和max等限定边界操作,使得生成的代码能够对一些领域特定的加速芯片友好。
4.6.2 循环展开unrolling
循环展开是一种通过展开循环迭代来降低循环条件分支开销的循环变换技术,提高指令间流水并行,并降低用于循环边界判定的控制开销,还为向量化创造条件,但注意寄存器溢出。
4.6.3 其他循环优化
循环剥离peeling将循环某些迭代从循环主体中剥离出来的循环变换技术。循环中有时只有部分迭代会产生依赖,当这些循环迭代是循环的前几个或后几个迭代的时候,优化编译器就可以通过循环剥离的方法,将原来循环转换为两部分。
循环判断外提unswitching将循环内与循环索引无关的条件谓词外提到循环外的技术,降低分支开销,减少代码量。
4.7 循环压紧
循环压紧coalescing将多层循环嵌套压缩成单层循环的技术。
相关文章:

第4章 循环变换
4.1 适配体系结构特征的关键技术 由于高级语言隐藏了底层硬件体系结构的大量细节,如果不经过优化直接将高级程序设计语言编写的程序部署在底层硬件上,往往无法充分利用底层硬件体系结构的处理能力。 算子融合不仅可以提…...

spring cloud使用git作为配置中心,git开启了双因子认证,如何写本地配置文件
问题 spring cloud使用git作为配置中心,git开启了双因子认证,死活认证不成功!!!!! 报错关键字 org.eclipse.jgit.api.errors.TransportException: https://git.qualink.com/zhaoxin15/sc-confi…...

JVM内存管理、内存分区:堆、方法区、虚拟机栈、本地方法栈、程序计数器
内存管理 内存分区 线程共享 堆 存放实例,字符串常量(直接引用),静态变量,线程分配缓冲区(TLAB线程私有)。垃圾收集器管理的区域 方法区 非堆,和堆相对的概念。存储已被虚拟机加载的…...

L1-047 装睡(Python实现) 测试点全过
题目 你永远叫不醒一个装睡的人 —— 但是通过分析一个人的呼吸频率和脉搏,你可以发现谁在装睡!医生告诉我们,正常人睡眠时的呼吸频率是每分钟15-20次,脉搏是每分钟50-70次。下面给定一系列人的呼吸频率与脉搏,请你找…...

Mysql优化原理分析
一、存储引擎 1.1 MyISAM 一张表生成三个文件 xxx.frm:存储表结构xxx.MYD:存储表数据xxx.MYI:存储表索引 索引文件和数据文件是分离的(非聚集) select * from t where t.col1 30; 先去t.MYI文件查找30对应的索引…...

软考高级系统架构设计师系列案例考点专题一:软件架构设计
软考高级系统架构设计师系列案例考点专题一:软件架构设计 一、考点梳理及精讲1.质量属性判断与质量属性效用树2.必备概念3.架构风格对比4.MVC架构5.J2EE架构6.面向服务的架构SOA7.企业服务总线ESB一、考点梳理及精讲 系统架构设计师方面的知识在案例分析中每年必考1~2题,并且…...

css实现垂直上下布局的两种常用方法
例子:将两个<span>元素在<div>内垂直居中放置. 方法一:使用 Flexbox 来实现。 在下面的示例中,我将为 <div> 元素添加样式,使其成为一个 Flex 容器,并使用 Flexbox 属性将其中的两个 <span>…...

【Jetpack】Navigation 导航组件 ⑤ ( NavigationUI 类使用 )
文章目录 一、NavigationUI 类简介二、NavigationUI 类使用流程1、创建 Fragment2、创建 NavigationGraph3、Activity 导入 NavHostFragment4、创建菜单5、Activity 界面开发 NavigationUI 的主要逻辑 ( 重点 )a、添加 Fragment 布局b、处理 Navigation 导航逻辑 ( 重点 )c、启…...

基于NAudio实现简单的音乐播放器
《测试.net开源音频库NAudio》介绍了使用NAudio实现音乐播放和录音的基本用法,本文基于NAudio的音乐播放功能实现简单的mp3音乐播放器程序,主要实现以下功能: 1)导入文件夹中的mp3音乐文件,直接导入多个mp3音乐文件…...

C++之“00000001“和“\x00\x00\x00\x01“用法区别(一百八十六)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 人生格言: 人生…...

Java“魂牵”京东店铺所有商品数据接口,京东店铺所有商品API接口,京东API接口申请指南
要通过京东的API获取店铺所有商品数据,您可以使用京东开放平台提供的接口来实现。以下是一种使用Java编程语言实现的示例,展示如何通过京东开放平台API获取整店商品数据: 首先,确保您已注册成为京东开放平台的开发者,…...

vuex详细用法
Vuex是一个专门为Vue.js应用程序开发的状态管理模式。它可以帮助我们在Vue组件之间共享和管理数据,以及实现更好的代码组织和调试。 在Vue.js中,组件之间的数据通信可以通过props和事件来实现。然而,随着应用程序规模的增长,组件…...

微前端-monorepo-无界
文章目录 前言一、微前端二 、monorepo三 、pnpm硬链接软链接(符号链接)幽灵依赖依赖安装耗时长monorepo项目搭建子模块复用 四、无界接入无界无界预加载无界传参 总结 前言 本文主要记录微前端框架 无界 的使用与理解以及monorepo代码管理方式。 一、微…...

阿里云矢量图标透明背景转换/展示时变为黑色解决方法
下载了一个矢量图标,背景是透明的 上传到minio然后在前端展示,发现透明(白色)的地方变成黑色了 处理方法:去除透明的底色。使用window的画图程序打开保存一遍,将透明色转为白色 OK...

Linux之Shell(二)
Linux之Shell 函数系统函数basenamedirname 自定义函数 正则表达式入门常规匹配常用特殊字符 文本处理工具cutawk 综合应用案例归档文件发送消息 函数 系统函数 basename 基本语法 basename [string / pathname] [suffix] 功能描述:basename 命令会删掉所有的前缀…...

以太网POE供电浪涌静电防护推荐TVS二极管
POE是一种传输技术,可在以太网电缆上传输电力和数据。1000M千兆以太网POE供电端口广泛用于安防、视频监控以及智能电网等工业系统,以实现系统内的数据、视频传输、流量控制、以及通过总线实现供电。由于工业以太网工作环境非常严酷苛刻,对于以…...

如何在 JavaScript 中查看结构体数组?
调试 JavaScript 代码的最简单方法是使用许多开发人员使用的 console.log()。有时,我们需要了解数组的结构和存储的值以进行调试。以下介绍如何查看结构数组。 JavaScript 的各种方法允许我们检查数组的结构。例如,我们可以知道数组是否包含对象、嵌套数…...

【SpringBoot学习笔记】02.静态资源与首页订制
静态资源 Spring Boot 通过 MVC 的自动配置类 WebMvcAutoConfiguration 为这些 WebJars 前端资源提供了默认映射规则,部分源码如下。 jar包: JAR 文件就是 Java Archive File,顾名思意,它的应用是与 Java 息息相关的,…...

kotlin 转 Java
今天突然想研究下有些kotlin文件转为Java到底长什么样,好方便优化kotlin代码,搞了半天发现一个非常简单的Android Studio或者Intellij idea官方插件Kotlin,Kotlin是插件的名字,真是醉了; 这里以AS为例,使用…...

【Harmony】在Harmony上面可以使用的Android常用的开源库
序言 Harmony开发中,由于不像Android开发经过这么多年的发展,各种类库都是比较完善的,这就导致在Harmony开发中很多Android类库是不能使用的,但是也有一些是可以使用的,下面是我在Harmony开发中实际开发中可以使用的部…...

数学建模:灰色关联分析
🔆 文章首发于我的个人博客:欢迎大佬们来逛逛 灰色关联分析法 算法流程 建立一个m行 n列的矩阵 X X X ,其中 m 表示评价对象, n表示评价指标首先进行矩阵的归一化,得到归一化后的矩阵 d a t a data data获取参考向…...

nodepad++ 插件的安装
nodepad 插件的安装 一、插件安装二、安装插件:Json Viewer nodepad 有 插件管理功能,其中有格式化json以及可以将json作为树查看的插件: Json Viewer 一、插件安装 1、首先下载最新的notepad 64位【https://notepad-plus.en.softonic.com…...

学习分享:Ubuntu 下使用 Qt 打开串口报错 Permission denied
Ubuntu 下使用 Qt 打开串口报错 Permission denied 错误描述 提前声明一下,开发环境是 Ubuntu18.04,用户是非 root 用户。 因项目需求,需要使用到 Qt 收发串口数据,曾经写过一个串口调试助手的程序 通俗易懂玩QT:串口…...

Javaweb入门
Spring Spring发展到今天已经形成一种开发生态圈,Spring提供若干个子项目,每个项目用于完成特定的功能。 Spring Boot可以帮助我们非常快速的构建应用程序、简化开发、提高效率 SpringBootWeb入门 需求:使用Spring Boot开发一个web应用&a…...

后端开发基础概念
后端开发基础概念 目前处于项目上手阶段,在学习项目过程中,有一些一知半解或者不明白含义的专业名词或者缩写,在此汇总。里面的内容很多都是基于个人理解,水平有限如果有出错的地方还请各位大佬批评指正。 2023年8月31日00:34:22…...

ELK原理和介绍
为什么用到ELK: 一般我们需要进行日志分析场景:直接在日志文件中 grep、awk 就可以获得自己想要的信息。但在规模较大的场景中,此方法效率低下,面临问题包括日志量太大如何归档、文本搜索太慢怎么办、如何多维度查询。需要集中化…...

FBX SDK 开发环境配置 visual studio 2022
FBX | Adaptable File Formats for 3D Animation Software | Autodesk. 下载windows的sdk并安装. 创建一个c console 工程 设置include目录 添加预处理宏 FBX_SHARED1 添加fbx sdk lib 目录 添加依赖lib : libfbxsdk-md.lib libxml2-md.lib zlib-md.lib 配置完毕....

vue面试题_vue2和vue3的区别
1、数据绑定原理不同 vue2:vue2的数据绑定是利用ES5的一个API:Object.definePropert() 对数据进行劫持,结合发布订阅模式的方式来实现的。 vue3:vue3中使用了ES6的Proxy API对数据代理。相比vue2.x,使用proxy的优势如…...

Shiro整合SpringBoot,实战下的应用场景
文章目录 前言一、springBootshiro环境准备1.数据库2.ssmp环境搭建3.实体类4.三层搭建5.初始化测试数据 二、Shiro过滤器1.Shiro认证过滤器2.Shiro授权过滤器 三、springBootshiro身份认证1.创建Realm,重写认证方法doGetAuthenticationInfo2.创建shiro配置类3.Postman测试 四、…...

C语言——全局变量和局部变量重名了会怎么样
前言 (1)今天在交流群里面看到这样一个问题: 为什么这个程序中下面我定义的void型函数smart在全局变量前声明了,但是在man函数中调用了smart函数,m的值打印出来还是0。 #include<stdio.h>int m; void smart(void);int main(…...