Pointers
Pointers in TSharkRex provide a way to refer to the memory address of a variable or array element rather than its value. While TSharkRex is not a general-purpose systems language, pointers serve an important role: they allow function blocks to operate on data buffers without copying entire arrays. In practice, you will encounter pointers most often when working with CAN communication function blocks and DID modules.
Declaring Pointers
A pointer is declared using the POINTER TO syntax, followed by the type it points to:
VAR
pData : POINTER TO BYTE;
pValue : POINTER TO DINT;
pFlag : POINTER TO BOOL;
END_VAR;
The pointer variable itself stores a memory address. The type after POINTER TO tells the compiler what kind of data lives at that address, which determines how the data is read and written when dereferencing.
Creating Pointers (Address-of @)
The @ operator takes the address of a variable or array element and produces a pointer:
VAR
data : ARRAY[0..7] OF BYTE;
pData : POINTER TO BYTE;
counter : DINT;
pCounter : POINTER TO DINT;
END_VAR;
pData := @data[0]; // Point to first element of array
pCounter := @counter; // Point to a scalar variable
You can point to any element within an array, not just the first one:
pData := @data[3]; // Point to element at index 3
pData := @data[i]; // Point to element at variable index
Address-of with 2D Arrays
For two-dimensional arrays, provide both indices to get a pointer to a specific element:
VAR
PAYLOAD : ARRAY[0..3, 0..7] OF BYTE;
pRow : POINTER TO BYTE;
i : BYTE;
END_VAR;
// Point to the start of row i
pRow := @PAYLOAD[i, 0];
This pattern is commonly used when you need to pass a specific row of a 2D array to a function block that expects a data buffer pointer. For example, when cycling through multiple UDS DID request payloads stored in a 2D array.
Dereferencing (^)
The ^ (caret) operator dereferences a pointer - it reads or writes the value at the memory address the pointer holds:
VAR
data : ARRAY[0..7] OF BYTE;
pData : POINTER TO BYTE;
value : BYTE;
END_VAR;
pData := @data[0];
// Read the value at the pointer
value := pData^; // value now contains data[0]
// Write a value through the pointer
pData^ := 0x55; // data[0] is now 0x55
Dereferencing is the inverse of the address-of operation: @ goes from value to address, and ^ goes from address back to value.
@ before dereferencing it. Dereferencing an uninitialized pointer leads to undefined behavior and could cause the device to malfunction.
Implicit Pointer Passing
In many cases, TSharkRex handles pointer conversion automatically. When a function block parameter expects a data buffer, you can pass an array directly and the compiler will treat it as a pointer to the first element:
VAR
CANSEND : CAN_TX;
SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;
// The DATA parameter receives SENDDATA as a pointer implicitly
CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE,
DATALENGTH := 8, DATA := SENDDATA);
You do not need to write DATA := @SENDDATA[0] here - the compiler understands that passing an array name to a pointer parameter means “pass the address of the first element.”
CAN_TX and CAN_RX function blocks. Explicit pointer manipulation is mainly needed for advanced patterns like 2D array row selection or manual buffer management.
Common Use: CAN Function Blocks
The most common place you will encounter pointers is when working with CAN transmit and receive function blocks. These function blocks operate on byte array buffers passed by reference.
CAN Transmit
VAR
CANSEND : CAN_TX;
SENDDATA : ARRAY[0..7] OF BYTE;
END_VAR;
// Prepare payload
SENDDATA[0] := 0x02;
SENDDATA[1] := 0x10;
SENDDATA[2] := 0x03;
// Send - array passed as pointer implicitly
CANSEND(ENABLE := TRUE, ID := 0x7DF, EXT := FALSE,
DATALENGTH := 8, DATA := SENDDATA);
CAN Receive
VAR
CANRECV : CAN_RX;
RECVDATA : ARRAY[0..7] OF BYTE;
udsResponse : BOOL;
END_VAR;
// Receive - data buffer filled by the function block
CANRECV(ENABLE := TRUE, ID := 0x7E8, EXT := FALSE,
MSG_COUNT := 5, DATA := RECVDATA);
IF CANRECV.AVAILABLE > 0 THEN
// RECVDATA now contains the received frame payload
udsResponse := (RECVDATA[0] = 0x06) AND (RECVDATA[1] = 0x62);
END_IF;
Common Use: DID_EXT Module
The DID_EXT function block (from the standard library) uses pointer parameters extensively. Each DID module reads diagnostic data into an output buffer that you provide. Since DID_EXT is a system function block provided by the standard library, you instantiate and call it like any other function block:
VAR
MODULE_0 : DID_EXT;
MODULE_0_DATA : ARRAY[0..63] OF BYTE;
MODULE_0_DATA_LEN : BYTE;
MODULE_1 : DID_EXT;
MODULE_1_DATA : ARRAY[0..63] OF BYTE;
MODULE_1_DATA_LEN : BYTE;
END_VAR;
// Each module writes its response into the provided buffer
MODULE_0(
OUT_DATA := MODULE_0_DATA,
OUT_DATA_LEN := MODULE_0_DATA_LEN
);
MODULE_1(
OUT_DATA := MODULE_1_DATA,
OUT_DATA_LEN := MODULE_1_DATA_LEN
);
Here, OUT_DATA is a pointer parameter - it receives the address of your byte array so the function block can write the DID response data directly into it. OUT_DATA_LEN is set by the module to indicate how many bytes of the buffer are valid.
DID_EXT is available through the UDS standard library. It is not a standalone type you can define yourself - it is provided by the system. See chapter 22 - DID_EXT for full details.
Array Access via Offset
When working with data buffers, you often need to copy a range of elements from one array to another at a specific offset. This is done with a FOR loop and computed indices - not with pointer arithmetic:
VAR
OUT_DATA : ARRAY[0..63] OF BYTE;
IN_DATA : ARRAY[0..63] OF BYTE;
offset : BYTE := 8;
i : BYTE;
END_VAR;
// Copy 8 bytes from IN_DATA starting at 'offset' into OUT_DATA
FOR i := 0 TO 7 DO
OUT_DATA[i] := IN_DATA[offset + i];
END_FOR;
This is a safe and clear alternative to pointer arithmetic. The compiler can optimize this pattern effectively, so there is no performance penalty compared to manual pointer manipulation.
Extracting Fields from DID Response
A practical example - extracting a 16-bit value from a DID response buffer:
VAR
MODULE_0_DATA : ARRAY[0..63] OF BYTE;
temperatur : INT; // Extracted temperature value
END_VAR;
// DID response data starts at byte 3 (after service ID + DID bytes)
// Combine two bytes into a 16-bit value (big-endian)
temperatur := SHL(MODULE_0_DATA[3], 8) BOR MODULE_0_DATA[4];
Summary
| Operation | Syntax | Description |
|---|---|---|
| Declare pointer | p : POINTER TO BYTE; |
Create a pointer variable |
| Address-of | p := @variable; |
Get memory address of a variable |
| Address-of element | p := @array[i]; |
Get address of an array element |
| Dereference (read) | val := p^; |
Read value at pointer address |
| Dereference (write) | p^ := 0x55; |
Write value at pointer address |
| Implicit pass | FB(DATA := array); |
Array passed as pointer automatically |
NULL Pointer
TSharkRex supports NULL (or nullptr) for pointer variables.
A null pointer indicates that the pointer does not point to any valid memory:
VAR
pData : POINTER TO BYTE;
value : BYTE;
END_VAR;
pData := NULL; // or: pData := nullptr;
IF pData <> NULL THEN
// Safe to dereference
value := pData^;
END_IF;
Dereferencing a NULL pointer causes undefined behavior and may crash the
device. Always check for NULL before dereferencing if the pointer could
be uninitialized.