Why should we use it?
A pointer or reference is stored in as many bytes as it takes to hold an address on the computer. This often makes them much smaller than the things they point to. We take advantage of this small size when storing data and when passing parameters to functions. It is much faster and more memory-efficient to copy a pointer or a reference than to copy many of the things they are likely to point to.
Naked facts about pointers
- A pointer is a variable that holds the memory location of another variable.
- The pointer variable returns the value located at the address stored in the pointer variable which is preceded by the pointer sign ‚^‘.
- Operators in TwinCAT:
^
,ADR()
- The pointer variable can refer to NULL.
- An uninitialized pointer can be created.
- The pointer variable can be initialized at any point of time in the program.
- The pointer variable can be reinitialized as many times as needed.
- The result of the difference between two pointers is of type DWORD, even on 64-bit platforms if the pointers are 64-bit pointers.
UNCTION CheckPointer
Implementation example of FUNCTION CheckPointer
: https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2530405259.html&id=
Naked facts about references
- The reference variable is used to refer to the variable which is assigned to that reference variable.
- The reference variable returns the address of the variable.
- Operators in TwinCAT:
REF=
or:=
:=
is used instead of the allocation operator REF=
.- The reference variable can never refer to NULL.
- An uninitialized reference can never be created.
- The reference variable can only be initialized at the time of its creation.
- The reference variable can never be reinitialized again in the program.
- Check functions: use operator
__ISVALIDREF
Implementation example of operator __ISVALIDREF
: https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529458827.html&id=
How to use pointers in TwinCAT 3
In TwinCAT3, pointers are declared using the „POINTER TO“ keyword, followed by the data type of the variable that the pointer points to. For example, to create a pointer to an integer variable, you would use the following syntax:
VAR
myPointer : POINTER TO INT;
END_VAR
To assign the memory address of a variable to a pointer, use the „ADR“ operator. For example, to assign the memory address of the variable „myVar“ to the pointer „myPointer“, you would use the following code:
myPointer := ADR(myVar);
To access the value of the variable that a pointer points to, you use the „^“ operator. For example, to access the value of the variable „myVar“ using the pointer „myPointer“, you would use the following code:
myPointer^ := 5;
How to use references in TwinCAT 3
References, on the other hand, are declared using the „REFERENCE TO“ keyword, followed by the data type of the variable that the reference refers to. For example, to create a reference to an integer variable, you would use the following syntax:
VAR
myReference : REFERENCE TO INT;
END_VAR
For assignments in the code body (not calls) use the “REF=” assignment. For example, to assign the variable „myVar“ to the reference „myReference“, you would use the following code:
myReference REF= myVar;
To access the value of the variable that a reference refers to, you use the variable name without an additional operator. For example, to access the value of the variable „myVar“ using the reference „myReference“, you would use the following code:
myReference := 5;
Use cases and code examples
Demonstration of the type independence of pointers compared to references
VAR
x : INT;
y : INT;
z : REAL;
int_ptr : POINTER TO INT;
int_ref : REFERENCE TO INT;
real_ref : REFERENCE TO REAL;
ptrValue : INT;
intRefValue : INT;
realRefValue : REAL;
END_VAR
x := 5;
y := 10;
z := 3.14;
(* Pointers *)
int_ptr := ADR(x); // Pointer to an INT
int_ptr^ := y; // Dereference the pointer and change the value of x to y
ptrValue := int_ptr^; // Output: x = 10
int_ptr := ADR(z); // Assign a REAL address to an INT pointer
ptrValue := int_ptr^; // Output: z = undefined
(* This is allowed, but could lead to undefined behavior *)
(* References *)
int_ref REF= x; // Reference to an INT
int_ref REF= y; // Change the value of x to y
intRefValue := int_ref; // Output: x = 10
real_ref REF= z; // Reference to a REAL
real_ref := 2.718;
realRefValue := real_ref; // Output: z = 2.718
Pointers used for dynamic arrays
VAR
dynamicArray : POINTER TO INT;
elementOutput : INT;
i : INT;
END_VAR
(* Allocate memory for the dynamic array *)
dynamicArray := __NEW(INT, 10);
(* Initialize the elements of the dynamic array *)
FOR i := 0 TO 9 DO
dynamicArray[i] := i * i;
END_FOR;
(* Access and show the elements of the dynamic array *)
FOR i := 0 TO 9 DO
elementOutput := dynamicArray[i];
END_FOR;
(* Deallocate memory for the dynamic array *)
__DELETE(dynamicArray);
Pointer arithmetic example
VAR
myArray : ARRAY[1..6] OF INT := [1, 2, 3, 4, 5, 6];
PtrStart : POINTER TO INT := ADR(myArray);
PtrItem : POINTER TO INT;
elementOutput : INT;
bMove : BOOL;
i : INT := 1;
END_VAR
IF bMove THEN // start trigger to move pointer to next array element
ptrItem := ptrStart + (i - 1) * SIZEOF(INT); // increment pointer address by 1 with address size of array element (INT)
elementOutput := ptrItem^; // output of the vaule from the pointed element
i := i + 1; // increment by 1
IF i = 7 THEN // reset index value at the end of last array element to avoid access to undefinded storage area
i := 1;
END_IF
END_IF
bMove := FALSE; // one cycle done
Reference use case: Pass input signal by references via FB_INIT
into a class
At the outside of the simple senor class the hardware input is assigned to the reference “di_Sensor”:
{attribute 'qualified_only'}
VAR_GLOBAL
{region "Sensors"}
partOnLocationSensor : SimpleSensor(di_Sensor := GVL_PcInterface.ios.input[BinaryHwInputs._EBOX_SRM_K201_1_I1],
stateOccupied := TRUE );
{endregion}
END_VAR
In the FB_INIT
of the simple sensor the reference is referenced to the sensor input variable of the class:
METHOD FB_INIT
VAR_INPUT
bInitRetains: BOOL;
bInCopyCode: BOOL;
///mask for the sensor state occupied
stateOccupied: BOOL;
///input of the sensor
di_Sensor: REFERENCE TO BOOL;
END_VAR
THIS^.stateOccupied := stateOccupied ;
THIS^.di_Sensor REF= di_Sensor ;
Inside of the class, you can use the input value of the sensor signal like every other common data type without address operator or de referencing:
FUNCTION_BLOCK SimpleSensor IMPLEMENTS ISensor
VAR
///mask for the sensor state occupied
stateOccupied: BOOL;
///input of the sensor
di_Sensor: REFERENCE TO BOOL;
//just for commissioning if sensor does not exist
simulate :BOOL := FALSE;
END_VAR
METHOD isFree : BOOL
isFree := ((THIS^.di_Sensor = (NOT THIS^.stateOccupied)) OR THIS^.simulate);
So far, so good! But when do I use what?
Advantages of pointers
- Null-ability: Pointers can be set to the null value, whereas references cannot. This allows for more flexibility in situations where a variable may or may not be present.
- Aliasing: Pointers can point to different variables at different times, whereas references are bound to a specific variable and cannot be reassigned. This can be useful when working with complex data structures and performing operations on multiple variables at the same time.
- Dynamic memory allocation: Pointers can be used to dynamically allocate memory on the heap, while references cannot. This allows for more dynamic and flexible use of memory in situations where the amount of memory required may not be known at compile time.
- Pointers are more efficient for passing large data structures to a function by reference.
- Pointers can also be used to create linked data structures such as linked lists and trees. This is not possible with references.
Advantages of references
- Easier to use: The reference does not have to be de-referenced (with ^) to access the contents of the referenced object.
- Cleaner syntax for transferring values: If an input is a “REFERENCE TO”, there is no need to write
ADR(value)
at a(refInput := value)
. - Type safety: In contrast to pointers, for references the compiler checks whether the base type is the same when assigning two references. (Please note that for pointers this check can be performed using the compiler extension TE1200 Static Analysis. See rule SA0019.)
Conclusion
The biggest difference between pointers and references is that pointers are way more powerful than references but less safe to use. With pointers you can do a lot more things, like working with dynamic storage and arithmetic calculations. Also, pointers are not type-related, which is useful when you are calculating with stings. However, it is important to note that pointers are generally considered more error-prone than references, as they can be de-referenced improperly, leading to undefined behavior or memory access violations. Also, references are safer to use because they cannot be null, and their type is known at compile time. In general, it is recommended that references be used in most cases. Pointers should be reserved for special cases where their advantages outweigh their disadvantages.
Here you will find more about pointers in TwinCAT: https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529453451.html&id=
Here you can learn more about references in TwinCAT: https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_plc_intro/2529453451.html&id=
For more insights on this topic, check out our in-depth video on pointers and references on YouTube here.