ST语言声明赋值与首扫描:PLC变量初始化深度解析

发布时间:2026/10/4 17:37:40
ST语言声明赋值与首扫描:PLC变量初始化深度解析 搞三菱MELSEC ST语言编程的兄弟八成纠结过一个问题给变量设初值到底用声明赋值bStart : BOOL : FALSE;好还是在程序开头搞一个首次扫描触发块统一赋值好表面上看两种写法在PLC启动后的第一次扫描都能让变量变成目标值可真移植到现场项目里往往就差着一层。这篇文章就掰开揉碎讲清楚声明赋值和首扫描的底层时机、复位行为、保持逻辑以及各种坑结合GX Works3里的对比工程示例让刚上手ST的新人和写了两三年但没细究过的老手都能少走弯路。1. 两种写法的直观对比与问题起源1.1 声明赋值写在变量声明区的“出厂设置”ST语言里最常见也最直观的初值写法就是在声明变量时直接赋值。三菱GX Works3中你会在ST程序的变量声明区看到类似这样的代码VAR_GLOBAL iMode : INT : 1; iCount : INT : 0; bCyl : BOOL : FALSE; bAlarm : BOOL : FALSE; END_VAR这种写法是IEC 61131-3标准里明确支持的语言特性声明区里的初值会在程序编译后作为变量的初始数据被固化下来。用通俗的话讲这相当于给变量设置了一个“出厂设置”。PLC的CPU从STOP切换到RUN的时候系统会在执行任何用户逻辑之前先把这些初值加载到变量对应的内存区。也就是说第一个扫描周期还没正式开跑变量就已经是声明区里写的那个值了。这个特性非常省事尤其在处理单个变量的固定默认值时。比如某个模式字、某个使能位、某个计时器预设值只要在声明区写一个常量就行程序体里什么都不用做。也正因为它简单很多初学ST的人会把所有初值都堆在声明区里这本身没问题但一旦变量多了、初始化逻辑复杂了就会暴露出它的局限性。1.2 首扫描赋值利用特殊继电器的“一次性闹钟”另一种做法是把初始化写在程序体里用一个“首次扫描标志”包住。在三菱MELSEC系列PLC里不同系列的首次扫描特殊继电器名称不同Q系列常用SM402FX系列常用M8002它们会在CPU由STOP切到RUN后的第一个扫描周期内保持ON之后就变成OFF。ST代码会长这样IF SM402 THEN iMode : 1; iCount : 0; bCyl : FALSE; bAlarm : FALSE; END_IF;这段逻辑本质上不是一个语言级功能而是一个应用级逻辑。它做的事是在程序启动后的第一个扫描周期里执行一段“初始化流程”。你可以把它理解成一个一次性闹钟——响过一次后面就不响了。因为它是普通ST语句里面可以写任意复杂的表达式、条件判断、循环甚至调用功能块所以它比声明赋值灵活得多。1.3 既然结果看起来一样为什么要纠结如果只看启动后的第一个扫描周期结束时的变量值两种写法得到的结果确实一样。但“第一次扫描结束时相同”不代表“整个生命周期里都相同”。它们最大的区别在于初始化的归属和生效时机声明赋值是系统在扫描逻辑开始前完成的首扫描赋值则是用户逻辑在扫描过程中“抽空”做的。这种区别带来的连锁反应很多。比如如果首扫描赋值的程序块在扫描顺序里排得靠后那么同一扫描周期里排在前面的程序块读到的可能还是“老值”又比如如果变量声明成保持型RETAIN首扫描赋值会无条件下次上电时强制覆盖而声明赋值一般不会覆盖恢复后的保持值再比如在线修改程序时两种写法都不会重新触发初始化但很多人会误以为改完程序后变量会恢复初值。这些问题如果不搞清楚现场迟早要背锅。2. 声明赋值与首扫描的底层生效机制2.1 声明赋值发生在“逻辑执行之前”要理解声明赋值和首扫描的差别先得理清PLC一个扫描周期的基本流程。以三菱MELSEC的中大型PLC为例CPU从STOP切到RUN时并不是直接就跳到第一条用户程序去执行。它要经过内部诊断、系统初始化、程序加载等步骤然后才进入周期性的扫描循环输入刷新、程序执行、输出刷新。ST变量声明区里的初值正是在“系统初始化”这个阶段写入变量内存的。也就是说在用户程序的第一条语句执行之前变量值已经被写成了声明初值。因为这个动作发生在逻辑执行之前所以不管这个ST程序块在任务里排第几个执行也不管程序块内部有没有代码所有程序块在第一个扫描周期里读到的变量都是已经初始化好的值。这里有一个实际工程里很多人忽略的细节如果你在RUN状态下用GX Works3在线修改程序CPU一般不会重新加载声明初值。声明初值的重载发生在CPU的STOP到RUN切换、断电重新上电后的运行启动或者你手动执行了“全清除/初始化”这类操作时。在线修改程序只改了代码逻辑没有重新初始化变量区所以那些声明初值并不会因为这次修改而重新生效。这个坑在后续章节我会详细展开。2.2 首扫描赋值跟着扫描顺序和任务执行走首扫描赋值就不一样了。虽然外面的“IF SM402”条件只在第一个扫描周期内为真但它终究还是程序逻辑。这就意味着它的执行时机完全取决于当前ST程序块在任务调度中的位置。举个例子一个工程里可能有10个程序块初始化专用的块被排在最后一个。那么CPU执行第一个扫描周期时会先把前面9个程序块都跑完才跑到初始化块。前面9个程序块如果引用了iMode或bAlarm它们看到的仍然是变量内存里原来的值。这个“原来的值”可能来自哪里可能是上一次运行残留的值可能是系统默认的0也可能就是声明区里写了但还没被覆盖的初值。总之在这个扫描周期的前半段变量并不是最终的初始化状态。这种“逻辑窗口”很讨厌因为它和扫描顺序强相关。你在调试的时候可能单步看变量值是好的但换成另一个任务配置、或者把初始化块挪了个位置问题就冒出来了。首扫描标志本身并没有做错什么错的是你把它当成了“在逻辑执行之前”的工具可它本质上只是“在程序扫描的中途某个点”的工具。2.3 冷启动、热启动与在线修改的不同命运讨论初值不能只看正常上电那一下还要看PLC运行的几种常见状态。第一种是冷启动也就是STOP到RUN、断电重新上电后启动。这是两种写法都能正常发挥作用的场景声明赋值由系统加载初值首扫描赋值在第一个扫描周期执行。表面上看起来殊途同归但注意如果变量是RETAIN保持型冷启动的行为就有变化了。保持型变量断电后如果由电池或超级电容维持重新上电时系统会从保持区恢复数据这时候声明初值不一定能覆盖回来而首扫描赋值只要SM402能ON就会老老实实把值再写一遍保持型变量的效果直接被打回原形。第二种是热启动指的是PLC在运行中由于某些原因暂停扫描又恢复或者某些CPU支持的部分重启模式。这个时候首次扫描标志可能触发也可能不触发具体要看CPU手册。声明赋值同样不一定重载。所以如果你依赖这两种方式去恢复初值在热启动场景下很可能失灵。第三种是RUN状态下的在线修改。GX Works3支持在线变更程序这时候CPU不会重新初始化变量区也不会重新触发首次扫描标志。大多数人改完程序发现变量值没有回到初值就是没搞明白这个机制。在线修改本质上是“不打扰CPU运行状态地更新逻辑内容”任何初始化动作都更像是额外福利而不是默认行为。想要变量在在线修改后重新初始化你得在程序里做一个手动“初始化请求”机制或者干脆停机重启。3. 五个关键差异维度的详细拆解3.1 差异速查表为了让大家一目了然我把两个方案的关键差异整理成一张表。这张表每个格都可以在工程现场作为检查清单用。对比维度声明赋值首扫描赋值生效时机CPU开始扫描用户逻辑前第一个扫描周期内、程序块被执行时生效范围所有程序块统一可见无顺序差异只对初始化块之后的逻辑可见在线修改程序后不重新加载初值不会再次触发首次扫描标志RETAIN保持变量上电恢复保持值时一般不会被覆盖只要执行就强制覆盖初始化逻辑复杂度只能写固定常量无法写复杂逻辑可写条件、运算、循环、调用FB多变量联动不能互相依赖可以按顺序逐步计算赋值可维护性集中声明简单直观需要单独POU或块来承载否则容易散落典型应用场景默认常量、单变量初值初始化序列、条件化初值、批量复位表格只是一个速览真正要理解的是背后为什么会有这些差异。下面挑几个最容易出事的维度展开说。3.2 维度一生效时机带来的“逻辑窗口”前面提到首扫描赋值会受扫描顺序影响。这个“逻辑窗口”在实战里造成的故障往往非常隐蔽。我用一个真实感很强的例子来说明。假设有一个全局变量g_bReady声明区里写死了FALSE。首扫描初始化块在程序末尾把它置成TRUE。程序里还有两个功能块需要用到这个变量功能块A在初始化块之前执行它判断g_bReady FALSE于是进入“待机分支”功能块B在初始化块之后执行它判断g_bReady TRUE于是进入“运行准备分支”。同一个扫描周期同一个变量两个功能块却做出了不同的判断。如果g_bReady还影响了输出那在第一个扫描周期里就很可能出现一次瞬间的错误输出。不要觉得一个扫描周期很快无所谓。在设备启动瞬间哪怕几十毫秒的错误输出都可能驱动气缸动一下、变频器抖一下、报警灯闪一下现场影响很大。而用声明赋值就不存在这个问题因为变量在扫描开始前就已经是最终值了。所以我一直强调如果某个变量会被多个程序块在同一扫描周期内读取并且读取结果会直接影响动作那就别用首扫描来做它的初值至少要把初始化块排在最前面。3.3 维度二保持变量冲突保持变量是MELSEC工程里很常用的一个特性。把变量声明为RETAIN之后断电再上电CPU会从保持区把值恢复回来。这本来是为了满足“设备断电后重新上电需要恢复上次运行状态”的需求。但这时候如果初始化块里写了一句iRecipe : DEFAULT_RECIPE;那每次上电首扫描标志一ON这句话就会把保持值覆盖掉。你辛辛苦苦设计的配方号、累计产量、模式选择瞬间被刷回默认值。这个故障和“保持丢失”的表现症状几乎一样排查起来很容易走弯路。反过来声明赋值在保持型变量上的行为就温和得多。如果保持区已经恢复了断电前的值系统一般不会再去写声明初值。也就是说RETAIN变量用声明赋值可以做到“首次下载时给一个初值运行后断电再上电则恢复保持值”。这通常是工程师想要的效果。但也要注意如果保持区里的数据因为电池耗尽等原因失效CPU会重新把声明初值加载进去这时候相当于“出厂重置”。这本身是合理的但必须心里有数。有一种折中做法可以兼顾“真正的首次上电初始化”和“后续上电保持恢复”用一个保持型标志变量bInitDone初始值为FALSE首扫描时判断它如果没初始化过才执行初始化逻辑并把标志置TRUE。这样只有第一次冷启动会初始化后续掉电再上电都会正常恢复保持值。这个方法看起来很完美但它有一个前提下载程序的时候要保持区里的bInitDone不是一直为TRUE否则初始化逻辑会被跳过。所以工程上还要专门做一个“恢复出厂设置”的按钮以及一套清除保持区的操作流程。3.4 维度三初始化逻辑的复杂度上限声明赋值能做的其实很有限它只支持在变量声明时给定一个常量初值。你没法写iB : iA 1;这种依赖关系的赋值也没法根据某个外部输入开关来决定初值是1还是2更没法在声明的过程中调用一个功能块去完成复杂运算。首扫描赋值就没有这个限制。它本质上是一段完整的ST逻辑可以写IF、CASE、FOR、WHILE可以调用功能块也可以按顺序给几百个变量赋值。因此当你的设备有一个复杂的“上电初始化序列”时首扫描是最自然的实现手段。不过灵活也意味着更容易失控。我见过一些ST程序初始化逻辑没放在独立POU里而是顺手塞在各个功能块里每人塞几行最后启动时到底先执行哪段初始化根本没人说得清。要是再赶上多个功能块里都用SM402那就更乱了。建议的做法是单独建一个“Init”POU或功能块里面只干初始化这一件事并且把它放到任务列表的最前面执行。3.5 维度四任务周期和中断程序的影响很多三菱中大型PLC支持多种任务类型扫描执行、固定周期执行、事件中断执行。ST程序不一定全都在扫描任务里跑。首扫描标志SM402是CPU扫描层面的一个特殊继电器它只在RUN后的第一个扫描周期内为ON。如果你把初始化逻辑放在一个固定周期任务里而这个固定周期任务的第一次执行点正好在CPU第一个扫描周期内那没问题但如果你放在一个中断任务里中断的触发时机是不可预测的可能第一个扫描周期根本没到初始化代码就已经在某个中断里执行了也可能第一个扫描周期结束时中断都没触发SM402已经变回OFF初始化代码永远没机会执行。所以我的经验是首扫描初始化逻辑千万不要放在中断任务里也不要放在一个触发条件本身需要外部事件的程序块里。老老实实放在主扫描任务的最前面最可靠。声明赋值则完全没有这个顾虑它和任务调度无关系统加载初值之后所有任务里读到的都是初始化好的值。4. GX Works3里的对比实验从代码到现象4.1 场景与变量规划光讲理论不够我把这个对比做成一个可以直接在GX Works3里复现的小实验。假设我们要为一个简单的工装设备写初始化逻辑需要设置四个变量iMode设备模式默认1表示自动模式iCount计数值默认0bCylinder气缸输出默认FALSEbAlarm报警标志默认FALSE实验目标非常明确对比两种写法在断电上电、STOP到RUN、在线修改、RETAIN保持四种情境下这四个变量的真实表现。4.2 方案A只用声明赋值在GX Works3里新建一个ST程序变量声明区写VAR_GLOBAL iMode : INT : 1; iCount : INT : 0; bCylinder : BOOL : FALSE; bAlarm : BOOL : FALSE; END_VAR程序体其实可以什么都不写或者只留一行注释。声明初值会在CPU启动时由系统加载程序体有没有代码并不影响。如果你愿意也可以在程序体里加一行RETURN;保证编译器不抱怨空代码。注意到这里用的是VAR_GLOBAL因为我们需要在监控窗口里直接观察全局变量。如果写在局部VAR里只有对应的功能块实例能看到监控起来要更麻烦一些。实际工程里初始化对象往往是全局变量或直接被程序块引用的标签。4.3 方案B首扫描赋值同样新建一个ST程序变量声明区不写初值程序体里写IF SM402 THEN iMode : 1; iCount : 0; bCylinder : FALSE; bAlarm : FALSE; END_IF;这里的SM402是三菱Q/R系列的首扫描特殊继电器。如果你用的是FX系列需要把SM402换成M8002。下面所有实验结果对这两种系列来说机制是一样的。实验时我会把初始化逻辑故意放在任务列表的最后一个程序块里用来演示首扫描赋值的顺序问题。后续再做一个“放在最前面”的对比。4.4 实测步骤与预期结果你可以在自己的工程里按下面步骤一步一步做每一步都记录变量值的变化。第一步把方案A的程序下载到CPU然后执行STOP到RUN。在线监控iMode、iCount、bCylinder、bAlarm。理论上四个变量都应该等于声明区里写的初值。这一步方案A和方案B没有明显差别。第二步手动修改变量值。比如在线把iCount改成5把bAlarm改成TRUE。然后把CPU切到STOP再切回RUN。此时方案A和方案B都恢复了初值看起来还是一样。第三步在RUN状态下做一次在线修改随便改一个不影响初值的逻辑比如加一行注释。修改完成后不要重启CPU观察变量值。你会发现不管是方案A还是方案B之前手动改的iCount5、bAlarmTRUE都还保持着不会自动回到0或FALSE。这就验证了前面说的在线修改程序不会重新加载变量初值也不会重新触发首扫描标志。第四步把iCount的声明改成RETAIN保持型。方案A里iCount : INT : 0;方案B里iCount : INT;。下载程序后把iCount改成100断电再上电。方案A中如果保持区正常工作iCount会恢复为100因为声明初值0不会覆盖保持值方案B中只要首扫描代码执行了iCount : 0它就会被强制写回0。这就是两种写法在保持变量上最核心的差异。第五步把初始化逻辑在任务里的顺序从第一个调到最后一个然后再次启动CPU。方案B中你需要在初始化块前面的程序块里加一个断点或监控去看看在初始化块执行之前变量值是什么。你会发现在第一个扫描周期的前半段变量可能还是上电后的默认值或者上上次的残留值直到执行到初始化块才变过来。方案A没有这个问题。4.5 声明初值与首扫描同时存在的后果还有一类工程代码声明区里也写了初值首扫描块里也做了赋值。这种做法不能说绝对错但很容易让人迷糊。比如VAR_GLOBAL iMode : INT : 2; END_VAR程序体里IF SM402 THEN iMode : 1; END_IF;启动后的最终值当然是1因为首扫描块在扫描周期内把声明初值2覆盖掉了。但如果你在线修改了程序SM402没有触发这时声明初值也不会重新加载那iMode到底是2还是1就取决于你之前是什么状态。如果你在某个中间时刻手动把iMode改成了5在线修改后它可能一直是5。这种代码会让调试的人抓狂因为你很难判断当前值到底属于哪种机制控制的。我的建议是同一组变量的初值尽量只用一个机制管理。要么全部走声明赋值要么走首扫描初始化。如果实在要用两者也要明确分工比如声明区只负责“非保持变量的默认值”首扫描块负责“具备复杂依赖关系的初始化序列”并且把分工写进注释里。5. 常见问题与排查思路实录5.1 问题1首扫描赋值没有执行遇到最多的情况就是“程序跑起来了但初始化代码根本不生效”。排查时先确认特殊继电器有没有用对。第一三菱不同系列的首扫描继电器名称不一样Q/R系列是SM402FX系列是M8002。有的人把SM402当成了常ON继电器结果程序里被周期性地反复赋值变量永远停留在最后一个值。也有的把SM400当成首扫描SM400是常ON当然不对。第二确认这个ST程序块确实被分配到了一个被执行的任务里。GX Works3里如果你新建了一个POU但没有放到任务里那它压根不会执行。首扫描赋值再合理代码不跑也白搭。第三确认这个程序块所在的任务确实在RUN后第一次扫描周期内执行了。如果放在了某个触发条件极其苛刻的中断任务里SM402信号可能根本轮不到它。第四看有没有其他代码把SM402对应的标志或变量提前改了。比如有人在更前面的程序块里写了SM402 : FALSE这样的逻辑虽然是特殊继电器理论上不能随意写但项目里如果通过中间变量做了转发就可能被干扰。排查顺序不重要重要的是脑子里先有一个概念首扫描赋值是应用逻辑它能不能执行取决于任务调度和程序块位置而不像声明赋值那样由系统保证。5.2 问题2在线修改程序后初值没恢复这是一个典型的认知偏差。很多人觉得“我改了程序变量就应该重新初始化”但PLC在线修改的机制不是这样。在线修改只更新用户程序区的内容不碰变量区也不触发启动流程。所以如果你需要在线修改后让某些变量回到初值最直接的办法是修改完后执行一次STOP到RUN切换。如果不能停机那就在程序里设计一个“初始化请求”变量比如bManualInitReq在程序里写IF bManualInitReq THEN iMode : 1; iCount : 0; bCylinder : FALSE; bAlarm : FALSE; bManualInitReq : FALSE; END_IF;手动在监控窗口里把bManualInitReq置TRUE就能触发一次初始化。这种做法对声明赋值也有效因为它本质上是应用逻辑不受系统加载初值限制。这也是为什么我建议在复杂的设备控制程序里除了启动初值之外一定要留一个人工初始化入口。5.3 问题3RETAIN保持变量被首扫描刷掉这个问题我在现场遇到过不止一次。最典型的场景是设备有一个“配方号”或“累计产量”变量声明成RETAIN起初是用声明赋值给了一个默认配方0。后来做设备改造时工程师觉得上电初始化逻辑太散于是把所有变量初值统一挪到首扫描块里加了一句iRecipe : 0;。结果客户反映每次设备断电再上电配方号都会跳回0工程师的第一反应是电池没电了或者保持区坏了换了一块新电池还是没有解决。排查到最后才发现是首扫描赋值把保持值覆盖了。RETAIN变量恢复的是断电前的值首扫描一旦执行iRecipe : 0恢复等于没恢复。解决的办法很简单把要保持在断电前状态的变量从首扫描初始化块里摘出来让它们只依赖保持区的恢复机制或者用前面讲的bInitDone标志只在真正的首次启动时才初始化RETAIN变量。这里要给一个额外提醒如果你用了bInitDone标志这种方案下载程序时如果不清除保持区bInitDone在上电后可能已经是TRUE初始化逻辑不会执行。所以工程上必须配套“保持区清除”或“出厂重置”操作否则新的初值逻辑无法生效。这不是代码bug而是保持变量的固有特性。5.4 问题4初始化块执行顺序靠后导致初值不稳定如果首扫描初始化块排在任务列表的最后而前面的程序块已经使用了那些变量常见的故障表现为设备每次上电某个输出会短暂地闪一下或者某个计数值会在第一个扫描周期被错误地加了一次等到初始化块执行完才恢复正常。这种问题和初值本身没有关系而是初始化时机太晚。解决办法很直接把初始化POU移到任务列表的第一个位置。在三菱GX Works3里任务属性中可以调整程序块的执行顺序把Init块拖到最顶上就行。如果工程里有多个任务还要确认这些任务之间是否有优先级关系初始化要放在所有可能读取该变量的任务之前。如果你实在动不了任务顺序还有一个备用方案在变量声明区使用声明初值然后在首扫描块里只处理“需要复杂计算才能得到”的那部分变量。这样至少保证基础变量从一开始就是确定的复杂变量虽然晚一点但不会把这个扫描周期搅得天翻地覆。5.5 问题5功能块内部使用首扫描标志导致初始化多次执行在ST中使用功能块FB时每个FB实例都有自己的局部变量区。如果你在FB内部写了IF SM402 THEN来初始化局部变量那么有多少个实例这段初始化逻辑就会执行多少次。如果这些实例分布在不同的程序块里有些在第一扫描周期先执行有些在中间执行没有一个统一的初始化时刻调试起来会非常混乱。更麻烦的是有些FB实例可能只在某个分支条件下被调用首扫描标志ON时该分支压根没走到FB的初始化就没执行等下次这个FB再被创建或调用SM402早就不是ON了。这个FB的局部变量就处于一个“没有初始化”的状态。所以功能块内部尽量不要依赖首扫描特殊继电器。如果确实需要给FB内部变量做初始化应该让上位逻辑通过输入参数传入一个初始化标志或者把FB的内部变量声明初值利用起来。声明初值在FB实例化时对每个实例都会生效这一点的确定性比首扫描强得多。6. 到底怎么选我的决策建议与实战体会6.1 一套能直接套用的选型原则综合前面的机制和坑我给自己定了一套选型原则写在这里供参考。如果变量初值是一个固定常量和外部状态、其他变量没有任何依赖关系而且变量不需要掉电保持直接用声明赋值最省事。一个冒号一个等号就搞定不用额外建初始化块代码评审时一眼就能看明白。如果初始化逻辑涉及多个变量而且它们之间有先后依赖或条件判断比如“只有急停没被按下时才允许把模式设为自动”那就必须用首扫描块。为了保证不出现“逻辑窗口”把初始化块放到主扫描任务的第一个位置最好单独建一个Init POU。如果变量声明为RETAIN保持型并且希望断电后恢复保持值绝对不要用首扫描块无条件强制覆盖。需要首次真正上电初始化时用bInitDone保持标志加条件判断并且配套出厂重置机制。如果只是在线调试时希望变量快速回到初值不要依赖声明赋值和首扫描直接在程序里做一个手动初始化请求变量或者停机再启动。如果是安全相关的初始状态比如气缸、夹具、危险动作输出无论用哪种初值写法都不能只靠软件初始化来保证安全还要有硬件回路和设备侧的保护逻辑。这一点无论如何强调都不过分。6.2 我在实际项目里踩过的那一脚最后分享一个具体的改造经历。之前给一台设备做程序优化把原来散落各处的一堆声明区初值统一改成了一个首扫描初始化块。看着确实整洁了所有初始化逻辑集中在一个地方还能跑条件判断心里美滋滋。结果客户做验收的时候发现设备断电重启后操作员在触摸屏上保存的“快速循环模式”每次都会变成默认的“手动模式”客户怀疑程序丢数据了。我查了很久才发现那个模式变量是RETAIN类型旧程序里声明赋初值并不会在恢复保持值时覆盖它问题不大。而我改成了首扫描统一初始化之后每次上电都执行了一次强制赋值相当于把保持值给“洗”掉了。后来我把bInitDone机制加上去只在第一次真正上电时初始化后续断电重启恢复保持值这个故障才彻底消失。也是从那次之后我养成了一个习惯每次听到“初值”两个字都会多问一句“这个变量要不要保持”然后才决定用声明赋值还是首扫描块。初值这种东西看起来只是启动瞬间转眼即逝的一小步但在设备上电那一刻它决定了机器的“第一口动作”尤其涉及到气缸、电机和危险输出时一点都马虎不得。希望这篇文章能帮你把这一步走稳。