为什么要在codesys V3中使用指针和引用?
Why use pointers and reference in codesys V3?
我的问题是:使用指针和引用有什么好处?
我是 codesys 的新手,在我以前的工作中,我在 TIA 门户(西门子)和 Sysmac Studio(欧姆龙)中编程,从未遇到过指针或类似的东西。我想我了解它们的工作原理,但不确定何时应该自己使用它们。
例如,我刚收到供应商的一个功能块:
他们为什么不只用一个数组来输入和输出?
首先,如果您曾经使用过 VAR_IN_OUT
声明,那么您已经使用过引用,因为这相当于 VAR
和 REFERENCE TO
.
关于用途,目前我能想到的主要有4种:
Type Punning,您也可以使用 UNION
来实现,但您可能不希望必须为代码中的每个重新解释转换都创建联合。
TL; DR:为了节省内存和复制执行时间。 每当你将一些数据传递给 function/function 块时,它就会被复制。如果您的 PLC 有足够的 CPU 功率和内存,这不是一个大问题,但是如果您在低端 PLC 上处理特别大的数据,那么您可能会超出实时执行限制,或者 运行 内存不足。但是,当您传递 pointer/reference 时,无论数据有多大,只会复制并传递 pointer/reference ,在 32 位系统中为 4 个字节,在 64 位系统中为 8 个字节。
在 C 风格的语言中,当您想要一个函数 return 多个值而无需每次都创建自定义结构的麻烦时,您会使用 pointers/references。你可以在这里做同样的事情,但是在 CODESYS 函数中 can 有多个输出,例如:
VAR_OUPUT
out1 : INT; (*1st output variable *)
out2 : INT; (*2nd output variable *)
//...
END_VAR
- 最后,就像我一开始提到的,当你想传递一些需要在函数本身修改的数据时,换句话说,你可以使用
VAR_IN_OUT
的地方也可以使用pointers/references。您必须使用指针的一种特殊情况是,如果您有一个 Function Block
,它在 FB_Init
(initialization/construction) 函数中接收一些数据并将其存储在本地。在这种情况下,您将有一个指针作为功能块中的局部变量,并在 FB_Init
函数中获取变量的地址。如果您的结构需要引用另一个结构或某些数据,同样适用。
PS。可能还有一些我错过的其他用途。其他语言的主要用途之一是用于动态内存分配,但在 CODESYS 中,这在默认情况下是禁用的,并非所有 PLC 都支持它,几乎没有人(据我所知)使用它。
编辑:虽然这已被接受,但我想举一个我们使用指针的真实例子:
假设我们想要一个功能块来计算给定数字系列的 moving average。一个简单的方法是这样的:
FUNCTION_BLOCK MyMovingAvg
VAR_INPUT
nextNum: INT;
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
window: ARRAY [0..50] OF INT;
currentIndex: UINT;
END_VAR
但是,这有一个问题,即移动 window 大小是静态的并且是预定义的。如果我们想获得不同 window 大小的平均值,我们要么必须为不同的 window 大小创建多个函数块,要么执行如下操作:
FUNCTION_BLOCK MyMovingAvg
VAR CONSTANT
maxWindowSize: UINT := 100;
END_VAR
VAR_INPUT
nextNum: INT;
windowSize: UINT (0..maxWindowSize);
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
window: ARRAY [0..maxWindowSize] OF INT;
currentIndex: UINT;
END_VAR
我们将只使用从 0 到 windowSize
的数组元素,其余的将被忽略。然而,这也有我们不能使用超过 maxWindowSize
的 window 大小的问题,并且如果 maxWindowSize
设置得很高,可能会浪费很多内存。
有两种方法可以获得真正通用的解决方案:
- 使用动态分配。然而,正如我之前提到的,并非所有 PLC 都支持它,默认情况下禁用它,有缺点(您必须将内存分成两块),几乎不使用并且不太像 CODESYS。
- 让用户定义他们想要的任意大小的数组,并将数组传递给我们的功能块:
FUNCTION_BLOCK MyMovingAvg
VAR_INPUT
nextNum: INT;
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
windowPtr: POINTER TO INT;
windowSize: DINT;
currentIndex: UINT;
END_VAR
METHOD FB_Init: BOOL
VAR_INPUT
bInitRetains: BOOL;
bInCopyCode: BOOL;
END_VAR
VAR_IN_OUT // basically REFERENCE TO
window_buffer: ARRAY [*] OF INT; // array of any size
END_VAR
THIS^.windowPtr := ADR(window_buffer);
THIS^.windowSize := UPPER_BOUND(window_buffer, 1) - LOWER_BOUND(window_buffer, 1) + 1;
// usage:
PROGRAM Main
VAR
avgWindow: ARRAY [0..123] OF INT; // whatever size you want!
movAvg: MyMovingAvg(window_buffer := avgWindow);
END_VAR
movAvg(nextNum := 5);
movAvg.avg;
同样的原理可以应用到任何对数组进行操作的功能块上(例如,我们也用它来进行排序)。此外,类似地,您可能希望拥有一个适用于任何 integer/floating 数字的函数。为此,您可以使用 ANY 类型之一,它基本上是一个结构,其中包含指向数据第一个字节的指针、数据大小(以字节为单位)和枚举类型。
我的问题是:使用指针和引用有什么好处?
我是 codesys 的新手,在我以前的工作中,我在 TIA 门户(西门子)和 Sysmac Studio(欧姆龙)中编程,从未遇到过指针或类似的东西。我想我了解它们的工作原理,但不确定何时应该自己使用它们。
例如,我刚收到供应商的一个功能块:
他们为什么不只用一个数组来输入和输出?
首先,如果您曾经使用过 VAR_IN_OUT
声明,那么您已经使用过引用,因为这相当于 VAR
和 REFERENCE TO
.
关于用途,目前我能想到的主要有4种:
Type Punning,您也可以使用
UNION
来实现,但您可能不希望必须为代码中的每个重新解释转换都创建联合。TL; DR:为了节省内存和复制执行时间。 每当你将一些数据传递给 function/function 块时,它就会被复制。如果您的 PLC 有足够的 CPU 功率和内存,这不是一个大问题,但是如果您在低端 PLC 上处理特别大的数据,那么您可能会超出实时执行限制,或者 运行 内存不足。但是,当您传递 pointer/reference 时,无论数据有多大,只会复制并传递 pointer/reference ,在 32 位系统中为 4 个字节,在 64 位系统中为 8 个字节。
在 C 风格的语言中,当您想要一个函数 return 多个值而无需每次都创建自定义结构的麻烦时,您会使用 pointers/references。你可以在这里做同样的事情,但是在 CODESYS 函数中 can 有多个输出,例如:
VAR_OUPUT
out1 : INT; (*1st output variable *)
out2 : INT; (*2nd output variable *)
//...
END_VAR
- 最后,就像我一开始提到的,当你想传递一些需要在函数本身修改的数据时,换句话说,你可以使用
VAR_IN_OUT
的地方也可以使用pointers/references。您必须使用指针的一种特殊情况是,如果您有一个Function Block
,它在FB_Init
(initialization/construction) 函数中接收一些数据并将其存储在本地。在这种情况下,您将有一个指针作为功能块中的局部变量,并在FB_Init
函数中获取变量的地址。如果您的结构需要引用另一个结构或某些数据,同样适用。
PS。可能还有一些我错过的其他用途。其他语言的主要用途之一是用于动态内存分配,但在 CODESYS 中,这在默认情况下是禁用的,并非所有 PLC 都支持它,几乎没有人(据我所知)使用它。
编辑:虽然这已被接受,但我想举一个我们使用指针的真实例子:
假设我们想要一个功能块来计算给定数字系列的 moving average。一个简单的方法是这样的:
FUNCTION_BLOCK MyMovingAvg
VAR_INPUT
nextNum: INT;
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
window: ARRAY [0..50] OF INT;
currentIndex: UINT;
END_VAR
但是,这有一个问题,即移动 window 大小是静态的并且是预定义的。如果我们想获得不同 window 大小的平均值,我们要么必须为不同的 window 大小创建多个函数块,要么执行如下操作:
FUNCTION_BLOCK MyMovingAvg
VAR CONSTANT
maxWindowSize: UINT := 100;
END_VAR
VAR_INPUT
nextNum: INT;
windowSize: UINT (0..maxWindowSize);
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
window: ARRAY [0..maxWindowSize] OF INT;
currentIndex: UINT;
END_VAR
我们将只使用从 0 到 windowSize
的数组元素,其余的将被忽略。然而,这也有我们不能使用超过 maxWindowSize
的 window 大小的问题,并且如果 maxWindowSize
设置得很高,可能会浪费很多内存。
有两种方法可以获得真正通用的解决方案:
- 使用动态分配。然而,正如我之前提到的,并非所有 PLC 都支持它,默认情况下禁用它,有缺点(您必须将内存分成两块),几乎不使用并且不太像 CODESYS。
- 让用户定义他们想要的任意大小的数组,并将数组传递给我们的功能块:
FUNCTION_BLOCK MyMovingAvg
VAR_INPUT
nextNum: INT;
END_VAR
VAR_OUTPUT
avg: REAL;
END_VAR
VAR
windowPtr: POINTER TO INT;
windowSize: DINT;
currentIndex: UINT;
END_VAR
METHOD FB_Init: BOOL
VAR_INPUT
bInitRetains: BOOL;
bInCopyCode: BOOL;
END_VAR
VAR_IN_OUT // basically REFERENCE TO
window_buffer: ARRAY [*] OF INT; // array of any size
END_VAR
THIS^.windowPtr := ADR(window_buffer);
THIS^.windowSize := UPPER_BOUND(window_buffer, 1) - LOWER_BOUND(window_buffer, 1) + 1;
// usage:
PROGRAM Main
VAR
avgWindow: ARRAY [0..123] OF INT; // whatever size you want!
movAvg: MyMovingAvg(window_buffer := avgWindow);
END_VAR
movAvg(nextNum := 5);
movAvg.avg;
同样的原理可以应用到任何对数组进行操作的功能块上(例如,我们也用它来进行排序)。此外,类似地,您可能希望拥有一个适用于任何 integer/floating 数字的函数。为此,您可以使用 ANY 类型之一,它基本上是一个结构,其中包含指向数据第一个字节的指针、数据大小(以字节为单位)和枚举类型。