1 地址描述
在内存中,每个字节都有一个编号,通过编号找到存储区(类似去1-407室 取快递,编号就是1-407,快递就是对应存储区),这个编号在这里我们取个名字叫做地址;当定义一个变量并为其赋值的时候,计算机会开辟一块空间用来存取变量值,在程序中一般是通过变量名来引用变量的值,实际上程序在编译的时候已经将变量名字转为变量的地址,即通过变量名找存储单元的地址,从而读取或修改存储单元的变量值;
2 绝对地址
(1)描述
在设备中比如PLC,已经提前定义好一部分地址的用途(比如地址(16#0080000~~16#008FFFF)只用于外部输入地址),
在Codesys中 I 、Q 、 M 表示设备的绝对地址区域,分别是输入地址区、输出地址区、中间地址区;
(2) 地址表示
格式:%<内存区域>(<大小>)<内存位置>
- 内存区域 : I 、Q、M
- 大小 : X(位)、B(字节) 、W(字)、D(双字)
- 内存位置:数字
例如 下面的关系对照表
举例 | Value |
---|---|
%IB1 | 输入区域B1的地址; |
%IX1.2 | 输入区域B1中位2的位地址; |
%QD2 | 输出区域D2的地址; |
![]() |
(3) 地址映射
%<内存区域>(<大小>)<内存位置> :只是表示从哪个地址开始取,具体取多少个字节,是由我们定义的数据类型决定;
dwTest AT %IB1 : DWORD;
表示 从输出区域字节B1开始取 4个字节(DWORD占四个字节)中的内容映射到dwTest变量中;
3 指针
(1)描述
指针就是地址,它可以是绝对地址,也可以是用户自定义变量的地址;
当定义指针的时候,必须定义指针的数据类型,因为指针指向的变量类型不同,而类型所占字节大小不同,所有如果仅仅知道地址不知道类型时,指针就不知道从当前地址读取多少个字节;
(2)关键字:“Pointer to” 、“adr” 、“^”
- “Pointer to” : 用来定义指针类型变量;
piTest: pointer to dword;(*指向dword类型的指针*)
pbTest: pointer to bool;(*指向bool类型的指针*)
prTest: pointer to real;(*指向real类型的指针*)
paiTest: pointer to array[1..10] of int ;(*指向int类型数组的指针*)
apiTest: array[1..10] of pointer to int;(*int类型指针数组:存储10个指针,每个指针指向Int类型*)
- “ADR” 用来获取变量的地址
- “^” 用来读取指针变量中地址对应的数值
VAR
piTest : POINTER TO INT;
iTest : INT := 1;
iTest2 : INT := 0;
END_VAR
piTest := ADR(iTest);(*获取变量piTest的地址,保存到piTest指针变量中*)
iTest2 := piTest^;(*取地址对应的存储单元数据,赋值给iTest2*)
(3)指针变量中值的含义
- 每个地址对应一个byte存储单元,即每个byte都有一个独立的地址
- WORD\INT 等16位数据类型包含2个byte,其地址用第一个byte的地址表示;
- dowrd\dint等32位数据类型包含4个byte,其地址用第一个byte的地址表示,以此类推;
- 指针变量中的值代表某一个byte地址,指针的类型决定了是指向以这个byte开始,连续多少个字节;
- 图中1000指的是第一个byte的地址,1001指的是第二个byte的地址,以此类推;
(1) 每个地址对应的空间是一个byte,byte中的每一位bit不具备单独的地址,如下图所示,取输出地址区第1个字节中每一位的地址,运行查看都是同一个值,这个值就是这个字节的地址;
VAR
bBool_0 AT %QX0.0 : BOOL;
bBool_1 AT %QX0.1 : BOOL;
bBool_2 AT %QX0.2 : BOOL;
bBool_3 AT %QX0.3 : BOOL;
bBool_4 AT %QX0.4 : BOOL;
bBool_5 AT %QX0.5 : BOOL;
bBool_6 AT %QX0.6 : BOOL;
apBool : ARRAY[0..7] OF POINTER TO BOOL;//指针数组
END_VAR
apBool[0] := ADR(bBool_0);
apBool[1] := ADR(bBool_1);
apBool[2] := ADR(bBool_2);
apBool[3] := ADR(bBool_3);
apBool[4] := ADR(bBool_4);
apBool[5] := ADR(bBool_5);
apBool[6] := ADR(bBool_6);
- bBool AT %QW0 :BOOL;
对于布尔变量,如果未指定单个位地址,则会在内部分配一个字节。bBool的值更改会影响从QW0.0到QW0.7的范围。- bBool AT %QW0.1 :BOOL ;
布尔变量声明,其中明确指定了单个位地址。bBool只影响QW0.1的值
(4)指针类型大小
指针类型大小为8个字节,和指向的数据类型无关,如下图所示;所以指针类型变量可以表示(2^64-1) 中不同的数,每一个数代表一个byte地址,也就是可以表示(2^64-1) 个byte内存空间大小;
VAR
piTest: POINTER TO DWORD;(*指向dword类型的指针*)
pbTest: pointer to bool;(*指向bool类型的指针*)
prTest: pointer to real;(*指向real类型的指针*)
paiTest: POINTER TO ARRAY[1..10] OF INT ; (*指向int类型数组的指针*)
iSize :ARRAY[0..3] OF UINT;
END_VAR
iSize[0] := SIZEOF(piTest);
iSize[1] := SIZEOF(pbTest);
iSize[2] := SIZEOF(prTest);
iSize[3] := SIZEOF(paiTest);
sizeof 返回变量所占空间的字节数
(5)指针类型的地址
指针类型的变量也是变量,每个变量都有自己的地址;
如下图所示:
- piTest中的值是dwTest的地址
- ppTest中的值是piTest的地址;
- ppTest^ : 获得的是dwTest的地址
- ppTest^^ : 获得的是dwTest变量的值,等价于 piTest^
VAR
ppTest: POINTER TO POINTER TO DWORD;
piTest: POINTER TO DWORD;
dwTest : DWORD := 100;
END_VAR
piTest := ADR(dwTest);
ppTest := ADR(piTest);
(6) 地址映射
- 指针的值(变量的地址)可以知道从那块区域取数据
- 指针的类型可以知道取多少个字节数据;
- 取的数据要和指针的类型一一映射
首先定义一个结构体
TYPE DUT :
STRUCT
bTest1 : BOOL;(*1个字节*)
bTest2 : BOOL;(*1个字节*)
iTest : INT;(*2个字节*)
diTest : DINT;(*4个字节*)
END_STRUCT
END_TYPE
进行测试
VAR
abyTest :ARRAY[0..7] OF BYTE := [1,0,2,0,7,0,0,0];
pdutTest: POINTER TO DUT;
END_VAR
pdutTest := ADR(abyTest[0]);
其原理如图,变量pdutTest的值是 abyTest[0]的地址,然后就从abyTest[0]开始,取pdutTest指向DUT数据类型大小的数据,DUT为8个字节;也就是取abyTest[0]到abyTest[7]中的内容(因为数组的物理地址是连续的)映射到结构体DUT中;
(7) 指针保护
- 指针没有保护,如果指针指向的位置是不可知的,因为指针变量的值是别的变量的地址,通过指针访问修改了一个不确定含义的地址,结果是不可知的;
VAR
pdutTest: POINTER TO INT;
END_VAR
pdutTest :=16#002341234;(*随便赋值,16#002341234地址不知是哪个变量地址*)
- 指针默认值是为0,意味着空指针
VAR
pdutTest: POINTER TO INT;
END_VAR
- 指针没有类型保护
例如用一个INT类型指针,指向BOOL变量地址不会报警。
一个BOOL变量会占用一个字节空间,其变量值取决于字节最低位的值;如果修改其他位(比如第二位)的值为1,BOOL变量会怎么样呢 ?
代码验证
VAR
bTest : BOOL:= TRUE;
pbyTest: POINTER TO INT;
END_VAR
pbyTest := ADR(bTest);(INT型指针 指向BOOL类型地址)
pbyTest^ := 2;(*通过指针,把BOOL变量第二位置为1*)
(8) 指针偏移:下标([])和加减操作符,可以控控制指针的偏移
(1)加减操作符方式
VAR
piTest : POINTER TO INT;
aiTest : ARRAY[1..10] OF INT :=[1,2,3,4,5,6,7,8,9,10];
iTest : INT;
END_VAR
(*获得数组 aiTest[0]的地址,等价于piTest := ADR(aiTest[0])*)
piTest := ADR(aiTest);
(*获得数组 aiTest[1]的地址,需要当前地址偏移2个Byte地址,因为一个INT占2个byte*)
piTest := piTest + 2;
iTest := piTest^;
(1)通过下标的方式:它进行了2部分工作,第一步是进行地址偏移,第二步取偏移后地址对应存储区的变量值;
VAR
piTest : POINTER TO INT;
aiTest : ARRAY[1..10] OF INT :=[1,2,3,4,5,6,7,8,9,10];
iTest : INT;
END_VAR
(*获得数组 aiTest[0]的地址,等价于piTest := ADR(aiTest[0])*)
piTest := ADR(aiTest);
(*1 地址偏移:地址偏移量的计算方法是 下标中的数字n * sizeof(piTest所指向的数据类型),在这里是 1 * sizeof(int) = 2*)
(*2 取新地址对应存取区的变量值*)
iTest := piTest[1];
(9) 应用举例
写一个求可变数组求和的函数(数组是可变,最大数组长度位10000,最小为1)
- 写法1:
FUNCTION POU : LINT
VAR_INPUT
aiTest : ARRAY[1..10000] OF INT ;(*传入外部数组所有数据*)
iNum : INT; (*外部数组长度*)
END_VAR
VAR
i :INT;
END_VAR
(*求和计算*)
FOR i := 1 TO iNum BY 1 DO
POU := POU + aiTest[i];(*累加求和*)
END_FOR;
- 写法2:–指针
FUNCTION POU : LINT
VAR_INPUT
aiTest : POINTER TO INT ;(*传入外部数组首地址*)
iNum : INT; (*外部数组长度*)
END_VAR
VAR
i :INT;
END_VAR
(*求和计算方式1*)
FOR i := 1 TO iNum BY 1 DO
POU := POU + aiTest[i]; (*取地址对应中的数据,不断累积求和*)
END_FOR;
(*求和计算方式2*)
FOR i := 1 TO iNum BY 1 DO
POU := POU + aiTest^; (*取地址对应中的数据,不断累积求和*)
aiTest := aiTest + SIZEOF(INT);(*指向数组中下一个INT型变量地址*)
END_FOR;
- 比较说明
写法2明显会开辟的空间小,指针类型变量占用8个字节,数组变量(ARRAY[1…10000] OF INT)占用10000*2=20000个字节,如果数组长度为百万,千万,其效果更明显;