🔍
v1.3.8

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.

Pointers in TSharkRex are simpler and more constrained than in languages like C. There is no pointer arithmetic beyond array offset access, no null pointers, and no dynamic memory allocation. This keeps their use safe and predictable in the embedded automotive context.

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.

Always ensure a pointer has been assigned a valid address with @ 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.”

For standard CAN communication, you rarely need to use explicit pointer syntax. Just declare your data arrays and pass them directly to the 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.

Note: 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;
Warning

Dereferencing a NULL pointer causes undefined behavior and may crash the device. Always check for NULL before dereferencing if the pointer could be uninitialized.