在结构化文本(ST)编程中,变量作用域是决定程序可维护性、安全性与逻辑清晰度的核心机制。ST 是 IEC 61131-3 标准定义的高级文本语言,广泛用于 PLC 编程(如 Siemens TIA Portal、Codesys、Beckhoff TwinCAT)。理解 VAR、VAR_INPUT、VAR_OUTPUT 的作用域规则,不是语法细节问题,而是避免逻辑错乱、数据覆盖、调试困难的根本前提。
一、什么是作用域?为什么它在 ST 中特别关键?
作用域(Scope)指变量在程序中可见且可访问的代码区域。在 ST 中,变量一旦声明,其生命周期、初始化时机、内存归属、读写权限均由声明位置和关键字严格限定。与 C 或 Python 不同,ST 没有“局部函数作用域”或“动态作用域”,只有静态、显式、块级作用域——即变量从声明起就绑定到特定程序组织单元(POU),且不可跨层级隐式继承。
错误的作用域使用会导致三类典型故障:
- 值被意外覆盖:多个 POU 同名
VAR变量指向同一地址(若未正确隔离); - 读取未初始化值:
VAR_INPUT在调用时未传参,却直接读取,结果为默认值(如0或FALSE),掩盖逻辑缺陷; - 无法调试修改:全局变量被多处隐式修改,断点追踪失效。
因此,ST 中没有“默认作用域”——每个变量必须通过明确关键字声明其意图。
二、三大变量区的核心语义与作用域边界
ST 中所有变量声明必须位于 POU(Program、Function Block、Function)的声明区(Declaration Section),以 VAR 开头、END_VAR 结尾。声明区内部可并列多个变量块,每个块由不同关键字引导,含义截然不同:
-
VAR_INPUT:输入参数区- 作用域:仅限该 POU 内部可读,值由调用方显式传递;
- 生命周期:每次调用时重新载入,调用结束即释放(对 FB 还涉及背景数据块保留);
- 初始化:不支持默认值赋值(如
VAR_INPUT x : INT := 10;是语法错误); - 特殊规则:若为
VAR_INPUT REFERENCE TO ...,则传递的是地址而非值,需确保源变量生命周期覆盖调用周期。
-
VAR_OUTPUT:输出参数区- 作用域:仅限该 POU 内部可写,值在调用结束后返回给调用方;
- 生命周期:与
VAR_INPUT同步,在调用退出前完成赋值; - 初始化:声明时不初始化,首次执行时内容为数据类型默认值(
INT→0,BOOL→FALSE,STRING→''); - 关键限制:禁止在 POU 内部读取未写入的
VAR_OUTPUT(部分平台报编译警告,运行时行为未定义)。
-
VAR:局部变量区- 作用域:仅在本 POU 声明区以下的 ST 代码块内可见且可读写;
- 生命周期:FB 类型中随背景数据块持久化;Program 类型中随循环扫描周期重置(具体取决于 PLC 实现,TIA Portal 中 Program 的
VAR默认每周期清零); - 初始化:支持默认值(如
VAR count : INT := 0;),且该初始化仅在 POU 首次加载或复位时执行一次; - 本质:这是真正的“私有变量”,调用方完全不可见,也无法通过任何方式间接访问。
✅ 正确示例(Function Block
FB_MotorCtrl):FUNCTION_BLOCK FB_MotorCtrl VAR_INPUT start : BOOL; speed : REAL; timeout : TIME; END_VAR VAR_OUTPUT running : BOOL; error : WORD; END_VAR VAR timer : TON; state : INT := 0; // 初始值仅在首次实例化时生效 retry : INT := 3; END_VAR
❌ 典型错误:
VAR_INPUT flag : BOOL := TRUE; // 编译失败:VAR_INPUT 不允许初始化 END_VAR VAR_OUTPUT result : INT; // result := result + 1; // 危险:读取未写入的 VAR_OUTPUT,值不确定 END_VAR
三、作用域对比表:谁能在哪读/写?何时存在?
以下表格严格按 IEC 61131-3 标准定义,适用于所有合规 ST 环境(TIA Portal V18+、Codesys 3.5+、TwinCAT 3):
| 变量区 | 调用方能否访问 | POU 内能否读 | POU 内能否写 | 是否支持默认值 | 生命周期起点 | 生命周期终点 |
|---|---|---|---|---|---|---|
VAR_INPUT |
必须传参才能访问(调用方提供) | ✅ 可读 | ❌ 不可写 | ❌ 禁止 | 调用指令执行瞬间 | 本 POU 执行结束 |
VAR_OUTPUT |
调用后可通过变量名获取结果 | ⚠️ 不推荐读(未写入前值未定义) | ✅ 可写 | ❌ 禁止 | 调用指令执行瞬间 | 本 POU 执行结束(值传出) |
VAR |
完全不可见 | ✅ 可读 | ✅ 可写 | ✅ 允许 | POU 首次加载或复位时 | POU 销毁(FB 实例删除)或周期重置(Program) |
注意:VAR_IN_OUT 是第四类,但本质是 VAR_INPUT 与 VAR_OUTPUT 的组合(双向传递引用),不在本文范围,故不展开。
四、真实场景下的作用域陷阱与规避策略
场景 1:Function 中误用 VAR 替代 VAR_INPUT
问题:编写一个计算平均值的 Function F_Avg,开发者将采样数组声明为 VAR arr : ARRAY[0..9] OF REAL;,并在调用前手动填充 arr。
后果:arr 是纯局部变量,调用方对 arr 的任何赋值均无效;实际运行时 arr 始终为全 0。
✅ 正解:必须声明为 VAR_INPUT arr : ARRAY[0..9] OF REAL;,调用时传入实参。
场景 2:FB 的 VAR 被多实例共享(隐式全局化)
问题:在 TIA Portal 中创建 FB_PID,其 VAR 区含 integral : REAL;。当两个不同背景数据块(DB1、DB2)调用同一 FB 时,若 integral 声明在 VAR_GLOBAL(错误关键字)或未指定存储类别,可能导致两实例积分项互相干扰。
✅ 正解:确认 integral 位于 VAR 块(非 VAR_GLOBAL),且每个 FB 实例绑定独立 DB——此时 VAR 自动映射至对应 DB 偏移,天然隔离。
场景 3:VAR_OUTPUT 初始化伪需求
问题:需要 error 输出默认为 16#0000(无错误),但直接写 VAR_OUTPUT error : WORD := 16#0000; 报错。
✅ 正解:在代码首行强制赋初值:
IF NOT initialized THEN
error := 16#0000;
initialized := TRUE;
END_IF
其中 initialized 是 VAR 区布尔变量(默认 FALSE),确保只初始化一次。
五、作用域与编译器行为的底层关联
ST 编译器将不同变量区映射到 PLC 内存的不同物理段:
VAR_INPUT/VAR_OUTPUT→ 映射至调用栈帧(Stack Frame),地址由调用指令动态分配,生命周期严格绑定调用;VAR→ 映射至背景数据块(Instance DB)(FB)或循环组织块 OB1 的本地数据栈(Program),地址静态固定;- 若声明
VAR RETAIN,则VAR区变量内容在 PLC STOP→RUN 切换时保持(写入保持性存储区)。
这意味着:作用域不仅是语法约定,更是内存布局契约。越界访问(如在 Function 中读 VAR_OUTPUT)在编译阶段即被阻止,因为编译器无法生成合法内存寻址指令。
六、最佳实践清单(可直接嵌入团队编码规范)
- 输入必用
VAR_INPUT:任何需由调用方控制的数据,无论简单类型或数组,一律声明为VAR_INPUT; - 输出必用
VAR_OUTPUT:任何需反馈给调用方的结果,禁止用VAR+ 外部读取替代; - 状态变量归
VAR:计数器、定时器实例、中间计算结果、标志位,全部置于VAR区; - 禁用裸
VAR传参:绝不通过全局变量或VAR区变量“隐式通信”,所有交互必须显式通过INPUT/OUTPUT; - 初始化检查前置:对依赖初始值的
VAR,用IF NOT init_flag THEN ... END_IF封装,而非依赖声明赋值(因某些平台重启后声明赋值不触发); - 命名体现作用域:
in_Speed、out_Running、var_Counter,从名称即可判断归属,降低阅读成本。
七、调试验证方法:三步确认作用域生效
- 编译检查:尝试在
VAR_INPUT x : INT;下方写x := 10;,编译器必须报错(“Assignment to input variable not allowed”); - 在线监控:在 TIA Portal 中打开 FB 实例的背景 DB,在线查看
VAR区变量是否随周期变化、VAR_INPUT是否与调用端变量值实时同步; - 断点验证:在 FB 内设置断点,观察
VAR_OUTPUT y在首次进入时的值——应为类型默认值(如0),而非随机数或上周期残留值。
以上任一环节异常,即表明作用域机制未被正确遵循。
作用域不是约束,而是保护壳。VAR_INPUT 划定责任边界,VAR_OUTPUT 明确交付承诺,VAR 构建逻辑堡垒。把变量放进正确的 VAR_XXX 里,等于为每一行代码签发了内存主权证书。

暂无评论,快来抢沙发吧!