ST指针基础:ADDRESS_OF与解引用在间接寻址中的应用
在结构化文本(Structured Text,ST)编程中,指针是实现高效、灵活数据访问的核心机制。尤其在电气自动化系统(如基于IEC 61131-3标准的PLC程序)中,当需要动态操作数组、设备寄存器、或跨功能块共享变量时,直接寻址往往力不从心;而间接寻址借助指针,让程序具备“用变量控制地址”的能力——这正是 ADDRESS_OF 和解引用(^)协同工作的价值所在。
以下内容不依赖特定品牌PLC(如西门子S7-1500、罗克韦尔ControlLogix或倍福TwinCAT),所有示例均严格遵循IEC 61131-3第3版语法,可直接移植验证。
一、为什么需要间接寻址?从一个典型场景切入
假设你正在编写一条包装线控制逻辑:
- 有8台伺服电机,每台对应一组参数:
Motor1_Params,Motor2_Params, …,Motor8_Params; - 运行时需根据HMI选中的电机编号
n(值为1~8),实时读取并修改其参数;
若用直接寻址,代码将被迫写成冗长的分支:
IF n = 1 THEN
CurrentParam := Motor1_Params;
ELSIF n = 2 THEN
CurrentParam := Motor2_Params;
// …重复7次
END_IF;
这种写法违反“单一职责”原则:一旦新增电机,必须修改全部分支;且无法对参数组做统一循环处理(如批量备份、校验)。
间接寻址的解法:定义一个指针变量 pMotorParam : POINTER TO MotorParamStruct;,再通过 n 动态计算出对应参数块的内存地址,并让 pMotorParam 指向它——后续所有读写都通过 pMotorParam^ 完成。
这一过程分两步:
- 获取地址:用
ADR()或ADDRESS_OF获取变量的内存起始地址; - 使用地址:用
^运算符访问该地址所存的数据(即“解引用”)。
二者缺一不可,且必须类型匹配。
二、ADDRESS_OF:安全获取变量地址的唯一方式
IEC 61131-3 明确禁止使用 ADR(Variable) 在所有上下文中获取地址——它仅在局部变量、输入/输出参数、临时变量上有效,且不能用于常量、字面量或表达式结果。
正确做法是使用 ADDRESS_OF 运算符(IEC 61131-3:2013新增,推荐优先使用):
ADDRESS_OF(Variable)返回POINTER TO TypeOfVariable类型;- 编译器强制检查:目标变量必须具有确定的存储位置(即非优化掉的临时量);
- 若变量被声明为
RETAIN或位于VAR_GLOBAL区,ADDRESS_OF同样有效。
示例:声明与初始化指针
TYPE MotorParamStruct :
STRUCT
MaxSpeed : REAL;
AccTime : TIME;
Enable : BOOL;
END_STRUCT
END_TYPE
PROGRAM Main
VAR
Motor1_Params, Motor2_Params : MotorParamStruct;
pTarget : POINTER TO MotorParamStruct;
selectedIdx : INT := 1;
END_VAR
// ✅ 正确:ADDRESS_OF 获取已声明变量地址
IF selectedIdx = 1 THEN
pTarget := ADDRESS_OF(Motor1_Params);
ELSIF selectedIdx = 2 THEN
pTarget := ADDRESS_OF(Motor2_Params);
END_IF;
⚠️ 注意事项:
ADDRESS_OF(Motor1_Params.MaxSpeed)是合法的,返回POINTER TO REAL;ADDRESS_OF(3.14)❌ 编译错误:字面量无固定地址;ADDRESS_OF(a + b)❌ 错误:表达式结果是临时值,无地址;ADDRESS_OF(MyArray[0])✅ 合法(数组元素有地址),但ADDRESS_OF(MyArray)也合法——它返回数组首元素地址(即POINTER TO ARRAY[0..9] OF INT的基地址)。
三、解引用:用 ^ 访问指针指向的数据
指针变量本身只存一个地址(如 16#A000_1234),要读写其中的数据,必须显式解引用。语法为:PointerVariable^。
关键规则:
pTarget^的类型严格等于POINTER TO T中的T;- 对
pTarget^的读写,等价于直接操作Motor1_Params(当pTarget指向它时); - 解引用前必须确保指针非空(
pTarget <> 0),否则行为未定义(多数PLC会触发运行时错误)。
完整工作流示例:
// 假设 pTarget 已通过 ADDRESS_OF 正确赋值
IF pTarget <> 0 THEN
// ✅ 解引用:读取当前电机最大速度
currentSpeed := pTarget^.MaxSpeed;
// ✅ 解引用:修改加速度时间
pTarget^.AccTime := T#2.5S;
// ✅ 解引用:启用电机
pTarget^.Enable := TRUE;
END_IF;
对比直接寻址:
- 直接写
Motor1_Params.AccTime := T#2.5S;→ 硬编码,无法切换目标; - 间接写
pTarget^.AccTime := T#2.5S;→ 只需改pTarget指向,逻辑完全复用。
四、ADDRESS_OF 与数组/结构体的深层配合
4.1 数组元素动态定位
VAR
TempValues : ARRAY[0..99] OF REAL;
pElem : POINTER TO REAL;
idx : INT := 42;
END_VAR
// ✅ 让 pElem 指向 TempValues[42]
pElem := ADDRESS_OF(TempValues[idx]);
// ✅ 后续所有操作都作用于 TempValues[42]
pElem^ := 123.45;
value := pElem^;
注意:ADDRESS_OF(TempValues[idx]) 在每次执行时重新计算地址,因此 idx 可动态变化。编译器自动完成地址偏移(BaseAddr + idx * sizeof(REAL))。
4.2 结构体成员地址跳转
VAR
cfg : MotorParamStruct;
pSpeed : POINTER TO REAL;
END_VAR
// ✅ 获取结构体内嵌字段地址(无需先取结构体地址)
pSpeed := ADDRESS_OF(cfg.MaxSpeed);
// ✅ 等效于:pSpeed := ADDRESS_OF(cfg) + SIZEOF(REAL)*0;
// (因 MaxSpeed 是结构体首字段,偏移为0)
此特性极大简化了对复杂数据布局的操作——不必手动计算字节偏移,ADDRESS_OF 自动完成。
五、常见陷阱与规避方案
| 问题现象 | 根本原因 | 安全写法 |
|---|---|---|
p := ADR(MyVar); 在函数块中编译失败 |
ADR 不支持在 FB 内部对输入参数取地址(因参数可能为临时值) |
改用 ADDRESS_OF(MyVar),或确保参数声明为 VAR_IN_OUT(传引用) |
p^ := 100; 运行时报“访问违例” |
p 未初始化(值为 0),解引用空指针 |
始终前置判空:<br>IF p <> 0 THEN p^ := 100; END_IF; |
p := ADDRESS_OF(MyArray); 后 p^ 类型不符 |
ADDRESS_OF(MyArray) 返回 POINTER TO ARRAY[0..9] OF INT,而非 POINTER TO INT |
显式转换:<br>pInt := POINTER TO INT(ADDRESS_OF(MyArray[0])); |
特别强调空指针防护:
PLC 程序不允许崩溃,因此工业级代码必须将判空作为解引用的强制前置条件:
p := ADDRESS_OF(SomeVar);
IF p <> 0 THEN
// ✅ 安全解引用
value := p^;
ELSE
// ⚠️ 处理异常:报警、默认值、跳过
AlarmFlag := TRUE;
END_IF;
六、实战案例:通用参数配置表管理
目标:用一张 ConfigTable : ARRAY[0..15] OF ConfigEntry 统一管理16类设备参数,通过索引 tableIdx 快速定位并修改任意条目。
TYPE ConfigEntry :
STRUCT
ID : UINT;
Name : STRING[16];
Value : REAL;
IsLocked : BOOL;
END_STRUCT
END_TYPE
PROGRAM ConfigManager
VAR
ConfigTable : ARRAY[0..15] OF ConfigEntry;
pCurrent : POINTER TO ConfigEntry;
tableIdx : UINT := 5;
newValue : REAL := 99.9;
END_VAR
// 步骤1:根据索引计算地址
IF tableIdx <= 15 THEN
pCurrent := ADDRESS_OF(ConfigTable[tableIdx]);
ELSE
pCurrent := 0;
END_IF;
// 步骤2:安全更新
IF pCurrent <> 0 THEN
pCurrent^.Value := newValue;
pCurrent^.IsLocked := FALSE;
END_IF;
此模式可扩展至:
- HMI参数下载(HMI传入
idx和value,PLC更新对应项); - 配方管理(不同产品对应不同
tableIdx); - 故障日志动态写入(用环形缓冲区索引控制
pCurrent指向)。
七、性能与内存注意事项
ADDRESS_OF是编译期计算(若操作数为常量索引)或运行期简单加法(若含变量索引),开销可忽略;- 指针解引用
p^等价于一次内存读/写,速度与直接寻址一致; - 避免指针链式解引用(如
p1^ := p2^^),易引发难以追踪的错误; - 所有指针变量建议初始化为
0:p : POINTER TO INT := 0;—— 防止未赋值时随机值导致解引用崩溃。
八、总结:掌握 ADDRESS_OF 与 ^ 的核心要点
- ADDRESS_OF 是唯一安全的地址获取入口:它替代
ADR,提供更强类型检查和更广适用范围; - 解引用
^是指针生效的开关:无^,指针只是个数字;有^,才真正访问数据; - 判空是铁律:
p <> 0必须出现在每次p^使用之前; - 类型必须严格匹配:
POINTER TO T只能解引用为T,不可隐式转换; - 间接寻址不是炫技,而是解耦刚需:当“操作对象”在运行时才能确定,指针就是最轻量、最标准的解决方案。

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