Pointers (@)

A pointer is a variable that holds the 32-bit (4 byte) address of code or data located elsewhere in memory.  It is called a pointer because it literally points to that location.  The location pointed to is known as the target of the pointer.

Pointers represent a powerful addition to the BASIC programmer's arsenal.  The address is defined at run-time, so your program can reference any memory location as if it were a standard variable.  When a pointer is used to access a memory location, it is called "indirect addressing".

Pointers are declared using the DIM statement, and the type of the target must be specified.  The keywords PTR and POINTER are synonymous.

DIM i AS INTEGER PTR 'declares i as a pointer to an Integer

or:

DIM i AS INTEGER POINTER

The above example declares i as an Integer pointer.  Before it can be used, i must be initialized with an actual address of a variable (easily done with the VARPTR function; or STRPTR for strings ).  When you assign a value to a pointer variable, you are giving it an address to use later when you wish to reference the actual target.  A pointer's name alone references the pointer variable.  A pointer's name with an at sign (@) prefix, references the pointer's target:

DIM Ptr1 AS BYTE PTR ' declares Ptr1 as a byte pointer

DIM Ptr2 AS BYTE PTR ' declares Ptr2 as a byte pointer

DIM Byte1 AS BYTE    ' Declares Byte1 as a byte variable

DIM Byte2 AS BYTE    ' Declares Byte2 as a byte variable

Ptr1 = VARPTR(Byte1) ' Ptr1 points to Byte1

@Ptr1 = 36           ' Sets Byte1 to the value 36

Ptr2 = VARPTR(Byte2) ' Ptr2 points to Byte2

@Ptr2 = @Ptr1 + 4    ' Sets Byte2 to 40 (36 + 4)

In summary, when you reference a pointer variable without an at-sign, you are referencing the 32-bit address contained in it.  When you precede the name with an at-sign, you are referencing the target data located at the address "pointed to" by the pointer.

By assigning the address of another pointer to a pointer, we can set up another level of indirection.  Pointers to pointers are useful when setting up linked lists in memory.  You can then access the target by adding a second at-sign in front of the pointer's name:

DIM y AS STRING POINTER

DIM z AS STRING POINTER

DIM TmpStr AS STRING

y = VARPTR(TmpStr)  ' y points to TmpStr

z = VARPTR(y)       ' z points to y

@y = "A"            ' put an "A" in TmpStr

@@z = "B"           ' overwrite it with a "B"

Display @y          ' display the target value of y

PowerBASIC supports up to 200 levels of indirection.  For each level, you add another preceding at-sign to the pointer name.  You can only use the (@) prefix with pointer variables.

A pointer with a value of zero (0) is considered a null-pointer by PowerBASIC.  Windows will generate a General Protection Fault (GPF) if you attempt to access data at an invalid pointer address.  See the section on assembler programming for more information.

The true power of pointers resides in their speed and flexibility.  Traditionally, to access memory, a BASIC programmer had to use combinations of PEEK and POKE.  This allowed the programmer to address memory as bytes.  If the target data took any other form, conversion was necessary.  Pointers allow you to address the target data in any fashion you desire, even as a user-defined structure.  Moreover, because the setup of calling PEEK and POKE is no longer necessary, access is much faster.

Let's say that we want to scan all the characters in a buffer, replacing all upper case "A"s with lower case "a"s.  The code might look something like this:

SUB Lower(zStr AS STRING)

  DIM s AS BYTE PTR, ix AS INTEGER

  s = STRPTR(zStr) ' Access the dynamic string directly

  FOR ix = 1 TO LEN(zStr)

    IF @s = 65 THEN @s = 97  ' "A" -> "a"

    INCR s

  NEXT

END SUB

When using a pointer to a structure, the prefix is placed before the structure name when you wish to access an element of the structure.  The structure name by itself refers to its address.  This distinction is extremely important when treating structures as a whole.  The following example shows two ways of doing a simple bubble sort of an array of User-Defined Types.  The first uses conventional BASIC methods, the second uses pointers to illustrate their speed and efficiency.

 

'-- Example 1 --------------

#COMPILE EXE

#DIM ALL

TYPE NameRec

  Last   AS STRING * 20   ' Last name

  First  AS STRING * 20   ' First name

END TYPE

FUNCTION PBMAIN () AS LONG

    DIM Rec(1 TO 10) AS NameRec

    DIM RP AS NameRec POINTER

    DIM ix AS LONG, ij AS LONG

    DIM hFile AS DWORD

    '-- Put some data in the records --

    FOR ix = 1 TO 10

        Rec(ix).First = CHOOSE$(ix,"Jacob","Michael","Joshua","Matthew","Ethan", _

                                "Emily","Emma","Madison","Abigail","Olivia")

        Rec(ix).Last  = CHOOSE$(ix,"SMITH","JOHNSON","WILLIAMS","JONES","BROWN",

_

                                "DAVIS","MILLER","WILSON","MOORE","TAYLOR")

    NEXT ix

    '-- Sort UDT array in ascending order using a bubble sort

    '-- ARRAY SORT Rec(),FROM 1 TO 20,ASCEND  will do this as well

    FOR ix = 9 TO 1 STEP -1

        FOR ij = 1 TO ix

            IF Rec(ij-1).Last > Rec(ij).Last THEN

                SWAP Rec(ij-1), rec(ij)

            END IF

        NEXT ij

    NEXT ix

   #IF %DEF(%PB_CC32)

        FOR ix = 1 TO 10

            PRINT TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First)

        NEXT ix

        PRINT

        PRINT "Press any key to quit ... "

        WAITKEY$

   #ELSE

       DIM msg AS STRING

       FOR ix = 1 TO 10

           msg = msg + TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First) + $CRLF

           MSGBOX msg

       NEXT ix

   #ENDIF

END FUNCTION

 

'-- Example 2 --------------

' The difference between example 1 and this example is

' that we're manipulating pointers (4 bytes) instead

' of whole records (40 bytes).

#COMPILE EXE

#DIM ALL

TYPE NameRec

  Last   AS STRING * 20   ' Last name

  First  AS STRING * 20   ' First name

END TYPE

FUNCTION PBMAIN () AS LONG

    DIM Rec(1 TO 10) AS NameRec

    DIM RP AS NameRec POINTER

    DIM ix AS LONG, ij AS LONG

    DIM hFile AS DWORD

    

    '-- Put some data in the records --

    FOR ix = 1 TO 10

        Rec(ix).First = CHOOSE$(ix,"Jacob","Michael","Joshua","Matthew","Ethan", _

                                "Emily","Emma","Madison","Abigail","Olivia")

        Rec(ix).Last  = CHOOSE$(ix,"SMITH","JOHNSON","WILLIAMS","JONES","BROWN",

_

                                "DAVIS","MILLER","WILSON","MOORE","TAYLOR")

    NEXT ix

    '-- Sort UDT array in ascending order using a bubble sort with pointers

    '-- note a bubble sort is not recommended for large collections

    '-- and note ARRAY SORT Rec(),FROM 1 TO 20,ASCEND  will do this as well

    '-- so this is only to show pointers to UDT arrays in action!

    RP = VARPTR(Rec(1))

    FOR ix = 9 TO 1 STEP -1

        FOR ij = 1 TO ix

            'note pointers to array elements use zero based subscripts in brackets!

            IF @RP[ij-1].Last > @RP[ij].Last THEN

                SWAP @RP[ij-1], @RP[ij]

            END IF

        NEXT ij

    NEXT ix

   #IF %DEF(%PB_CC32)

        FOR ix = 1 TO 10

            PRINT TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First)

        NEXT ix

        PRINT

        PRINT "Press any key to quit ... "

        WAITKEY$

   #ELSE

       DIM msg AS STRING

       FOR ix = 1 TO 10

           msg = msg + TRIM$(Rec(ix).Last)+ ", " +TRIM$(Rec(ix).First) + $CRLF

           MSGBOX msg

       NEXT ix

   #ENDIF

END FUNCTION

If you declare a member of a structure as a pointer, the @ prefix is used with the member name, not the structure name.  The previous example could be improved by adding a couple of pointers to the structure to point to the previous and next record, respectively.  This lets you allocate memory for a record only when needed, instead of pre-allocating a fixed-size array of records.  The modified structure would look something like this:

TYPE NameRec

  Last AS STRING * 20   ' Last name

  First AS STRING * 20  ' First name

  Nxt AS NameRec PTR    ' Pointer to next record

  Prv AS NameRec PTR    ' Pointer to previous record

END TYPE

DIM Rec AS NameRec

The pointer members are then accessed like this:

Rec.@Nxt   ' next record

Rec.@Prv   ' previous record

Putting the @ prefix in front of the structure name (i.e., @Rec) would cause a compile-time error, as Rec itself is not a pointer.

When calculating the length of the Type, all pointers are internally stored as Double-word (DWORD) variables, so NameRec is 48 bytes long (20 + 20 + 4 + 4).  If you need to know the length of a Type, it is easier to let PowerBASIC calculate it for you using the LEN function than doing it yourself:

length = LEN(structure)

 

See Also

Pointers to ASCIIZ and fixed-length strings

Pointers to arrays

Pointers to arrays with dual indexes