CODESYS开发教程14-指针使用

在写完《长字符串处理》以后,好长时间也没想到写什么内容好,前几天发现好像没有介绍过指针,那么今天我们的教程重点是介绍CODESYS中指针的使用。指针可以说算是C语言的精髓之一,有很多的优点和方便之处,但是同时也是个超级大坑(优点和缺点从来都是一体两面的~~)。在绝大部分时候,指针属于那种完全可以不用的类型,但是了解一下总是好的(就是喜欢学一些奇奇怪怪的用不着的知识😊)。本次教程对指针的基本概念、类型及使用方式,还有CODESYS的绝对地址映射做一下简单的介绍。

一、什么是指针

指针就是存储对象的内存地址的变量。内存地址就是内存的编号。如果把内存空间比喻成一家酒店,那么内存地址就是房间号。数据是存储在内存中的,住酒店的客人是住在一个个房间里面一样。取数据的指针就是取数据的地址,也就是酒店的房间号。

在CODESYS里面是地址是可以用类似%MWx(x=0~n)来表示。例如:

%MW10 := 123;

数据123存储在内存地址%MW10处,10就是地址。如果定义一个指针指向10,那么就可以理解为这个指针的值就是10。W表示的WORD类型,就是指针指向的地址的空间大小,类似与酒店房间的不同类型,如标准间、大床房、总体套房等等。

指针和数组一样,也是一种变量类型,因此需要先定义后使用。在CODESYS中,指针的定义方式如下:

pa : POINTER TO INT;

a : INT := 1;

pa:=ADR(a);

变量pa 定义了一个指向 INT类型的指针。POINTER TO 是定义指针的关键字,INT是指向的数据类型。

二、指针的类型及定义

1. Pointer to

指针的声明格式如下:

<pointer name>: POINTER TO <data type | data unit type | function block>;

在CODESYS中,指针是一个DWORD类型的值(不区分32位还是64位平台)。

取值操作符^:获取指针指向对象的内容。如上图中,P1为变量D1的地址,而P1^为D1的值。

注意:由于I/O的具有访问限制,因此定义指向I/O的指针以后,在代码生成时编译器会提示"<pointer name >不是一个有效的赋值目标"。如果需要对I/O值进行操作,可以先将I/O值拷贝到自己定义的变量中,再进行指针操作。

2.指针的索引访问

CODESYS允许通过索引操作符[]来访问指针指向的变量类型,例如以前教程中提到的字符串STRING类型可以通过[]访问字符串中的单个字符。

指针所指向的变量类型决定了操作符[]的索引移动长度,例如

pInt : ARRAY[0..10] OF INT;

pInt[i]实际上是地址(pInt+ i * SIZEOF(INT))指向的值。

即索引i改变时,地址偏移的长度与数据类型INT长度相关。

对于STRING类型,由于单个字符的存储类型是BYTE,因此通过指针索引访问时返回的值是一个SINT类型,即字符的ASCII码。

str : STRING:='CODESYS';

str[1]的值为79,即字符'C'的ASCII码。

对于WSTRING类型,其单个字符类型为WORD,因此通过指针索引访问时返回的值是一个INT类型,即字符的UNICODE码。

3.取地址操作符ADR

ADR()用于获取变量的地址。

与CoDeSys V2.3不同,您可以将ADR运算符与函数名称、程序名称、函数块名称和方法名称一起使用。因此,ADR取代了INDEXOF运算符。

使用函数指针时,可以将函数指针传递给外部库,但不能从CODESYS中调用函数指针。若要启用系统调用(运行时系统),必须为函数对象设置相应的对象属性("构建"选项卡)。这句话的意思是函数指针是给CODESYS调用外部实现的函数功能的,在CODESYS内部不能使用函数指针调用内部函数。另外在使用系统调用功能调用外部命令时,需要预先设置调用对象的属性(我的理解大概是这样,忍不住要吐槽一下CODESYS的帮助文档,看不懂啊看不懂~~~☹)。

注意:

(1)不能用于给常量取地址。例如,ADR(9)会报C0131错误。

(2)使用在线更改时,地址的内容可能会发生变化。因此,指针变量可能指向无效的内存区域。为了避免出现问题,应该确保指针的值在每个周期中都会更新。

(3)不要将函数和方法的指针变量返回给调用者,也不要将它们分配给全局变量。函数和方法内部的变量是不会保存的,每次调用是会初始化为0,调用完成后应该是不存在了,因此将其内部变量返回是没有意义的。

4.Runtime指针监视函数CheckPointer()

用于在运行模式下监视指针的内存访问。该函数实际上是给用户提供了一个指针的监视接口,用户可以根据自己的需求实现对所用指针的检查。函数的模板如下:

// 声明部分,请勿修改

FUNCTION CheckPointer : POINTER TO BYTE

VAR_INPUT

ptToTest : POINTER TO BYTE;

iSize : DINT;

iGran : DINT;

bWrite: BOOL;

END_VAR

// 实现部分,请用户自行添加代码

CheckPointer := ptToTest;

该函数的输入参数含义如下:

ptToTest:指针的目标地址。

iSize:引用变量的大小,INT型。iSize的数据类型必须覆盖变量的维度范围。

iGran:参考尺寸的粒度,INT型。这是引用变量中包含的最大非结构化数据类型??

bWrite:访问类型,BOOL型,TRUE为写入,FALSE为读取。

CheckPointer函数通常应该检查以下情况:

(1)检查返回的指针是否引用了有效的内存地址。

(2)监视引用内存范围是否与指针引用的变量类型匹配。

如果这两个条件都满足,则返回指针。否则需要在函数中进行错误处理。

注意:

(1)为了保证监视功能正常运行,不要修改声明部分,但用户可以添加局部变量。

(2)THIS和SUPER指针不会触发Checkpoint()函数调用。

(3)对于3.5.7.40及以上的编译器版本,对于REFERENCE变量也可以使用Checkpoint()函数进行检查。

说实话,这个功能我也没用过。主要是在CODESYS里面使用ST开发大部分时候不需要用指针。必须要用的时候,一定是确保用法是正确的,所以一般是不做这个检查的。必须要做这个检查的使用环境,目前还没遇到过,所以这里就不举例了(讲这么多,其实就是懒,没别的原因~~😊)。

5.引用Reference to

引用是对象的假名,实际上是指针,可以指向各种类型的数据(bit除外)、结构体、功能块、函数和程序。

定义方式如下:

<identifier> : REFERENCE TO <data type>;

引用赋值:

<identifier> REF = <variable>;

引用变量可以直接访问引用对象的值,不必使用^操作符。

对于引用类型,编译器会进行类型安全检查,即检查两个类型是否一致,不一致会报错。而指针这不进行这种检查,需要用户自己确保使用正确。在运行时,指针的内存访问可以通过隐式监视函数CheckPointer()进行检查。

引用作为函数或功能块参数时,不需要ADR()来进行参数传递。

注意:

(1)对于3.3.0.40及以上的编译器版本,引用变量会初始化为0。

(2)对输入设备定义引用时,由于没有写访问权限,编译器会显示警告。正确的方式与指针类似,定义一个中间变量与输入设备关联,然后对该中间变量定义引用。

(3)可以使用__ISVALIDREF()检查引用是否指向有效值(非0值为有效)。

(4)与指针一样,对常量定义引用是无效的。

三、指针的使用

1.结构体或数组指针

aa : ARRAY[0..9] OF INT:=[1,2,3,7(100)];

pa : POINTER TO INT;

pa := ADR(aa);

b:=pa[2];

c:=pa^;

//c:=(pa+1)^; //报错

pa:=pa+SIZEOF(INT);

d:=pa^;

pa:=pa+1;

e:=pa^;

指针pa实际上指向数组aa的首地址,pa^实际就是pa[0],数组序号默认是从0开始。需要注意的是,直接对指针进行加减操作时会导致结果不可预知。上图中c的值为1,即p[0]。将pa的值加上一个INT类型长度后,d的值变为2,即p[1]。但是在pa上加1以后,e的值变为768。另外(p+1)^这种操作是不合法的,编译器会报错。

2.二级指针

二级指针是指向指针变量的地址。

index : INT:=10;

p : POINTER TO INT;

pt : POINTER TO POINTER TO INT;

p:=ADR(index); //p^值为10

pt:=ADR(p); ///p^^值为10

四、动态内存分配

要使用动态内存分配功能需要在Application里面开启。如下图:

相关操作函数

__NEW:为功能块或数组分配内存,成功后会返回指向相应对象的指针,失败则返回0。使用的时候应避免在两个任务中同时调用,要么只在一个任务中调用该操作,也可以使用信号量SysSemEnter()来避免__NEW的同时调用(可能会导致任务周期出现大的抖动)。

__DELETE:释放__NEW分配的内存。该操作没有返回值,内存释放后对应的指针被设为0。对于指向功能块的指针,CODESYS会在指针设为0之前调用功能块的FB_EXIT方法。

五、地址映射

在使用内存动态分配或指针的时候,地址本身的内容在实时运行时可能会改变。CODESYS提供了一套用于定义内存绝对地址的方式。形式如下:

%<memory range prefix><size prefix><number|.number|.number....>

主要用于表示从指定的地址开始取,具体使用多少个字节,由定义的数据类型决定。

(1)内存区域memory range prefix

I:输入内存,指输入设备或传感器,常用的如数字输入。

Q:输出内存,指输出设备或执行器,常用的如数字输出

M:控制器内存。??

(2)数据类型size prefix

X:位

NONE:位

B:BYTE,8位

W:WORD,16位

D:DWORD,32位

(3)示例

%QX7.5 //输出内存位置7.5,类型为BIT

%IW215 //输入内存位置215,类型为WORD

%MD48 //内存位置48,类型DWORD

iWr AT %IW0: WORD; //为变量指定地址

例如给%MD0赋值为10:

%MD0:=10;

现在有指针P指向%MD0,那么P的值就是%MD0。然后取指针P的值赋给%MD1,就是把指针P 指向的%MD0的值取出来,赋给%MD1,此时%MD1的值也为10。

地址的类型和范围受当前控制器的配置和设置影响。寻址位置与设备类型无关,只与所用数据类型有关,即字5总是用%IW5寻址,字节5总是用%IB5寻址。采用不同数据类型的寻址实际上是在操作同一片内存,只是计数的长度不同。不同数据类型的对应关系如下表所示:

从上表可以看到,D0包含B0-B3,W0包含B0和B1。修改%QW0的值会影响到QX0.0~QX0.7的值,因此使用时应避免出现重叠。

五、总结

指针虽然方便,但是CODESYS因为自身的原因,对指针的支持有限。像内存的动态分配,早期版本是不支持的,现在的版本虽然支持但也是有限制的,所以真正需要用到的时候又不太方便。大家要是问我对于指针有什么经验,那我只能说对于小白来说,能不用就别用。实在是要用,要高度警惕类型匹配和指针越界问题,因为实在是太容易出错了,而且错了也不容易察觉(高手当然是随便怎么玩都好~~~)。


原创不易,感兴趣的多支持

相关推荐
代码小鑫2 分钟前
A031-基于SpringBoot的健身房管理系统设计与实现
java·开发语言·数据库·spring boot·后端
五味香10 分钟前
Linux学习,ip 命令
linux·服务器·c语言·开发语言·git·学习·tcp/ip
欧阳枫落16 分钟前
python 2小时学会八股文-数据结构
开发语言·数据结构·python
何曾参静谧23 分钟前
「QT」文件类 之 QTextStream 文本流类
开发语言·qt
monkey_meng27 分钟前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss35 分钟前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
2401_853275731 小时前
ArrayList 源码分析
java·开发语言
zyx没烦恼1 小时前
【STL】set,multiset,map,multimap的介绍以及使用
开发语言·c++
lb36363636361 小时前
整数储存形式(c基础)
c语言·开发语言
feifeikon1 小时前
Python Day5 进阶语法(列表表达式/三元/断言/with-as/异常捕获/字符串方法/lambda函数
开发语言·python