ST怎么写指针取值:Value := DEREF(pPointer);

发布于 2026-03-15 11:00:31 · 浏览 7 次 · 评论 0 条

在结构化文本(ST)编程中,DEREF 函数是访问指针所指向内存地址内容的核心操作。它不是语法糖,而是 IEC 61131-3 标准明确定义的唯一标准方式,用于安全、明确地执行“指针解引用”。许多初学者误以为 pPointer^pPointer[0] 可用,或直接写 Value := pPointer;,这些写法在 ST 中全部非法,会导致编译错误或运行时未定义行为。本文将手把手说明如何正确使用 DEREF 实现指针取值,覆盖从基础声明、地址获取、类型匹配、边界检查到典型故障排查的全流程。


一、理解指针本质:不是“变量”,而是“地址容器”

在 ST 中,指针不是普通变量,而是一个存储内存地址的特殊数据类型。它的值本身是某个变量在 PLC 内存中的物理/逻辑地址(如 #16#A00428),而非该地址处的数据。因此:

  • pPointer 的值 = 地址(例如 ADR(MyInt)
  • DEREF(pPointer) 的值 = 该地址中存放的实际数据(例如 MyInt 当前的整数值)

这与 C 语言中 int* p; *p 的语义完全一致,但语法强制要求显式调用 DEREF(),杜绝隐式解引用带来的歧义和安全隐患。


二、正确声明指针变量:必须指定目标类型

ST 中所有指针都必须带类型限定,格式为 POINTER TO <数据类型>。没有“通用指针”或“void*”概念。

  1. 声明一个指向 INT 的指针

    pToInt : POINTER TO INT;
  2. 声明一个指向结构体的指针

    TYPE MyStruct : STRUCT
        State : BOOL;
        Count : DINT;
    END_STRUCT
    VAR
        stInst : MyStruct;
        pToStruct : POINTER TO MyStruct;
    END_VAR
  3. 声明一个指向数组首元素的指针(常用)

    arrData : ARRAY[0..9] OF REAL;
    pToRealArray : POINTER TO REAL;  // 指向 REAL 类型,非 ARRAY 类型

⚠️ 错误示例(编译不通过):

pBad : POINTER;           // ❌ 缺少 TO 子句,语法错误  
pWrong : POINTER TO;     // ❌ TO 后无类型,语法错误  

三、获取有效地址:只能用 ADR(),禁用间接计算

指针必须指向一个已声明且生命周期有效的变量。获取地址的唯一合法方式是 ADR(变量名) 函数。

  1. 对普通变量取地址

    MyValue : INT := 123;
    pToInt := ADR(MyValue);  // ✅ 正确:pToInt 现在指向 MyValue 的内存位置
  2. 对数组元素取地址

    arr[0..4] OF BYTE;
    pToByte := ADR(arr[2]);  // ✅ 指向第3个元素(索引2)
  3. 对结构体成员取地址

    pToState := ADR(stInst.State);  // ✅ 指向结构体内布尔成员

❌ 绝对禁止的操作:

  • p := 16#A00428; —— 直接赋数值地址(PLC 地址空间受保护,且不可移植)
  • p := ADR(arr) + 2; —— 指针算术(ST 不支持 + / - 运算符作用于指针)
  • p := ADR(tempVar); —— tempVar 是临时变量(如 FB 内部未保留的 TEMP),地址在扫描周期结束即失效

✅ 替代方案(安全偏移):用 OFFSET 函数配合 ADR()

pToThird := OFFSET(ADR(arr[0]), SIZEOF(BYTE) * 2); // ✅ 等效于 ADR(arr[2])

四、执行取值:DEREF() 是唯一合法方式

语法:DEREF(<指针变量>)
返回值类型 = 指针声明时 TO 后的类型。

  1. 基本取值

    MyValue : INT := 42;
    pToInt : POINTER TO INT;
    Result : INT;
    
    pToInt := ADR(MyValue);
    Result := DEREF(pToInt);  // ✅ Result = 42
  2. 取结构体成员值

    stInst.State := TRUE;
    pToState := ADR(stInst.State);
    bFlag := DEREF(pToState);  // ✅ bFlag = TRUE
  3. 取数组元素值(通过指针偏移)

    arrData : ARRAY[0..4] OF REAL := [1.1, 2.2, 3.3, 4.4, 5.5];
    pToReal : POINTER TO REAL;
    
    pToReal := ADR(arrData[0]);
    val1 := DEREF(pToReal);            // ✅ = 1.1  
    pToReal := OFFSET(pToReal, SIZEOF(REAL));  // 移动到下一个 REAL
    val2 := DEREF(pToReal);            // ✅ = 2.2

⚠️ 常见错误写法(全部报错):
| 错误写法 | 原因 |
|----------|------|
| Value := pToInt^; | ^ 是 Pascal 风格,ST 不识别 |
| Value := pToInt[0]; | ST 中 [] 仅用于数组索引,指针不支持 |
| Value := pToInt; | 类型不匹配:POINTER TO INT ≠ INT |
| Value := DEREF(ADR(MyValue)); | ❌ ADR() 返回值不可直接传入 DEREF();必须先赋给指针变量 |


五、类型安全:DEREF 的类型检查规则

DEREF() 在编译期强制校验指针类型与目标变量类型是否一致。这是 ST 安全性的核心保障。

假设:

iVar : INT := 100;
rVar : REAL := 3.14;
pInt : POINTER TO INT;
pReal : POINTER TO REAL;
表达式 是否允许 原因
DEREF(pInt) 类型匹配
DEREF(pReal) 类型匹配
DEREF(ADR(iVar)) ❌ 编译错误 ADR(iVar)POINTER TO INT 类型,但未声明为变量,无法作为 DEREF 参数
pInt := ADR(rVar); ❌ 编译错误 ADR(rVar)POINTER TO REAL,不能赋给 POINTER TO INT
pInt := ADR(iVar); Value := DEREF(pInt); 全流程类型一致

💡 提示:若需跨类型访问(如把 REAL 当作 4 字节 INT 解析),必须用 UNIONBYTE 数组 + ADR() + DEREF() 组合,并确保硬件字节序一致。


六、空指针防护:运行时必须检查有效性

ST 中指针初始值为 0(空指针)。对空指针调用 DEREF() 会导致运行时错误(如 PLC 停机、诊断缓冲区报 Access violation)。

必须添加空指针检测:

IF pToInt <> 0 THEN
    Value := DEREF(pToInt);
ELSE
    Value := 0; // 或触发报警、设默认值
END_IF

更严谨写法(兼容所有指针类型):

IF NOT ISVALID(pToInt) THEN
    // 处理无效指针
ELSE
    Value := DEREF(pToInt);
END_IF

注:ISVALID() 是部分厂商扩展函数(如 CODESYS、TwinCAT),非 IEC 标准,但被广泛支持。若 PLC 不支持,用 pToInt <> 0 是通用替代。


七、典型应用场景与完整代码示例

场景 1:动态选择多个传感器数据源

VAR
    Sensor1, Sensor2, Sensor3 : REAL;
    pActiveSensor : POINTER TO REAL;
    ActiveIndex : INT; // 1, 2 or 3
    CurrentReading : REAL;
END_VAR

CASE ActiveIndex OF
    1: pActiveSensor := ADR(Sensor1);
    2: pActiveSensor := ADR(Sensor2);
    3: pActiveSensor := ADR(Sensor3);
    ELSE pActiveSensor := 0;
END_CASE

IF pActiveSensor <> 0 THEN
    CurrentReading := DEREF(pActiveSensor);
ELSE
    CurrentReading := 0.0;
    // 触发“无效传感器索引”报警
END_IF

场景 2:遍历字符串缓冲区(BYTE 数组)

VAR
    Buffer : ARRAY[0..255] OF BYTE;
    pBuf : POINTER TO BYTE;
    i : INT;
    Len : INT := 12;
END_VAR

pBuf := ADR(Buffer[0]);
FOR i := 0 TO Len - 1 DO
    IF pBuf <> 0 THEN
        // 将每个字节转为 ASCII 字符处理
        ProcessByte(DEREF(pBuf));
        pBuf := OFFSET(pBuf, SIZEOF(BYTE));
    END_IF
END_FOR

场景 3:FB 内部缓存指针(带初始化防护)

FUNCTION_BLOCK DataLogger
VAR
    pData : POINTER TO DINT;
    bInitialized : BOOL;
    LastValue : DINT;
END_VAR

METHOD Initialize : BOOL
VAR_INPUT
    pSource : POINTER TO DINT;
END_VAR
IF pSource <> 0 THEN
    pData := pSource;
    bInitialized := TRUE;
ELSE
    bInitialized := FALSE;
END_IF
Initialize := bInitialized;

METHOD ReadValue : DINT
IF bInitialized THEN
    ReadValue := DEREF(pData);
ELSE
    ReadValue := 0;
END_IF

调用方式:

logger : DataLogger;
myData : DINT := 999;

// 初始化时传入有效地址
logger.Initialize(ADR(myData));

// 后续任意时刻读取
current := logger.ReadValue(); // = 999

八、调试技巧与常见故障定位

现象 最可能原因 排查步骤
编译失败:“Expected ‘)’” 或 “Invalid operand type” DEREF() 参数不是指针变量,或类型不匹配 检查 DEREF() 内是否为已声明的 POINTER TO X 变量;确认 ADR() 赋值语句存在且无拼写错误
运行时 PLC 停机,诊断缓冲区显示 Deref on NULL pointer 指针为 0 时未检查直接 DEREF 在所有 DEREF 前加 IF pX <> 0 THEN;用在线监控查看指针变量实时值
DEREF() 返回值始终为 0 或随机值 指针指向了 TEMP 变量或已释放内存 确认 ADR() 作用对象是 VAR、VAR_GLOBAL 或 FB 的 VAR RETAIN 成员;避免对 FOR 循环内声明的临时变量取地址
值正确但后续修改不生效(如 DEREF(p) := 123 不起作用) 使用了只读地址(如常量、函数返回值)或硬件映射区写保护 DEREF(p) 左值赋值需目标地址可写;检查变量声明是否含 CONSTAT %Q* 等只写区

✅ 快速验证指针有效性方法:

  • 在线监控模式下,右键点击指针变量 → “Go to Address” → 查看该地址处实际存储的数值是否与预期变量值一致。
  • SIZEOF(DEREF(p)) 验证类型大小(如 SIZEOF(DEREF(pToInt)) 应返回 2)。

九、性能与资源注意事项

  • DEREF() 是单周期指令,无额外开销,性能等同直接访问变量。
  • 指针变量本身占用内存:32 位 PLC 占 4 字节,64 位占 8 字节。
  • 频繁使用 OFFSET() + DEREF() 遍历大数组时,比直接用 arr[i] 略慢(因多一次地址计算),但差异通常小于 0.1μs,可忽略。
  • 避免在每周期都重复调用 ADR()(如 DEREF(ADR(x)) 放在循环内)——应提前赋值给指针变量,复用地址。

Value := DEREF(pPointer); 这行代码背后,是 ST 对内存安全的刚性约束与工程可靠性的底层承诺。它拒绝一切隐式、猜测和侥幸——地址必须显式获取,类型必须严格匹配,空值必须主动防御。写好这一行,不是学会一个函数,而是真正开始以 PLC 工程师的思维控制物理世界。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文