Pointer Data Type

The pointer data type is unique among the FreeBasic numeric data types. Instead of containing data, like the other numeric types, a pointer contains the memory address of data.

On a 32-bit system, the pointer data type is 4 bytes. FreeBasic uses pointers for a number of functions such as ImageCreate, and pointers are used heavily in external libraries such as the Windows API. Pointers are also quite fast, since the compiler can directly access the memory location that a pointer points to. A proper understanding of pointers is essential to effective programming in FreeBasic.

For many beginning programmers, pointers seem like a strange and mysterious beast. However, if you keep one rule in mind, you should not have any problems using pointers in your program. The rule is very simple: a pointer contains an address, not data. If you keep this simple rule in mind, you should have no problems using pointers.

Pointers and Memory

You can think of the memory in your computer as a set of post office boxes (P.O. Box) at your local post office. When you go in to rent a P.O. Box, the clerk will give you a number, such as 100. This is the address of your P.O. Box. You decide to write the number down an a slip of paper and put it in your wallet. The next day you go to the post office and pull out the slip of paper. You locate box 100 and look inside the box and find a nice stack of junk mail. Of course, you want to toss the junk mail, but there isn't a trash can handy, so you decide to just put the mail back in the box and toss it later. Working with pointers in FreeBasic is very similar to using a P.O. Box.

When you declare a pointer, it isn't pointing to anything which is analogous to the blank slip of paper. In order to use a pointer, it must be initialized to a memory address, which is the same as writing down the number 100 on the slip of paper. Once you have the address, find the right P.O. Box, you can dereference the pointer, open the mail box, to add or retrieve data from the pointed-to memory location. As you can see there are three basic steps to using pointers.

  1. Declare a pointer variable.
  2. Initialize the pointer to a memory address.
  3. Dereference the pointer to manipulate the data at the pointed-to memory location.

This isn't really any different than using a standard variable, and you use pointers in much the same way as standard variables. The only real difference between the two is that in a standard variable, you can access the data directly, and with a pointer you must dereference the pointer to interact with the data.

Typed and Untyped Pointers

FreeBasic has two types of pointers, typed and untyped. A typed pointer is declared with an asscoaited data type.

Dim myPointer as Integer Ptr

This tells the compiler that this pointer will be used for integer data. Using typed pointers allows the compiler to do type checking to make sure that you are not using the wrong type of data with the pointer, and simplifies pointer arithmetic.

Untyped pointers are declared using the Any keyword.

Dim myPointer as Any Ptr

Untyped pointers have no type checking and default to size of byte. Untyped pointers are used in the C Runtime Library and many third party libraries, such as the Win32 API, to accommodate the void pointer type in C. Unless you specifically need an untyped pointer, you should use typed pointers so that the compiler can check the pointer assignments.

Pointer Operators

The following table lists the pointer operators.

Operator Syntax Comment
* (Indirection) B = *myPointer
*myPointer = B
The indirection operator returns or sets the data at the address specified in myPointer.
[ ] (Index Method) B = myPointer[index]
myPointer[index] = B
The index format uses the size of the data type to determine the proper indexing.
@ (AddressOf) myPointer = @myVar
myPointer = @mySub()
myPointer = @myFunction()
Returns the memory address of a variable, subroutine or function.

You will notice that the addressof operator not only returns the memory address of a variable, but it can also return the address of a subroutine or function. You would use the address of a subroutine or function to create a callback function such as used in the CRT QSort function.

Memory Functions

FreeBasic has a number of memory allocation functions that are used with pointers, as shown in the following table.

Function Syntax Comment
Allocate myPointer = Allocate(number_of_bytes) Allocates number_of_bytes and returns the memory address. If myPointer is 0, the memory could not be allocated. The allocated memory segment is not cleared and contains undefined data.
Callocate myPointer = Callocate(number_of_elements, size_of_elements) Callocate allocates number_of_elements that have size_of_elements and returns the memory address. If the memory could not be allocated, Callocate will return 0. The memory segment allocated is cleared.
Deallocate Deallocate myPointer Frees the memory segment pointed to by myPointer.
Reallocate myPointer = Reallocate(pointer, number_of_bytes) Reallocate changes the size of a memory segment created with Allocate or Callocate. If the new size is larger than the existing memory segment, the contents of the memory segment remained unchanged. If the new size is smaller, the contents of the memory segment are truncated. If pointer is 0, Reallocate behaves just like Allocate. A 0 is returned if the memory segment cannot be changed.

These functions are useful for creating a number of dynamic structures such as linked lists, ragged or dynamic arrays and buffers used with third party libraries.

When using the Allocate function you must specify the storage size based on the data type using the equation number_of_elements * Sizeof(datatype). To allocate space for 10 integers your code would look like this: myPointer = Allocate(10 * Sizeof(Integer)). An integer is 4 bytes so allocating 10 integers will set aside 40 bytes of memory. Allocate does not clear the memory segment, so any data in the segment will be random, meaningless data until it is initialized.

Callocate works in the same fashion, except that the calculation is done internally. To allocate the same 10 integers using Callocate your code would look like this: myPointer = Callocate(10, Sizeof(Integer)). Unlike Allocate, Callocate will clear the memory segment.

Reallocate will change the size of an existing memory segment, making it larger or smaller as needed. If the new segment is larger than the existing segment, then the data in the existing segment will be preserved. If the new segment is smaller than the existing segment, the data in the existing segment will be truncated. Reallocate does not clear the added memory or change any existing data.

All of these functions will return a memory address if successful. If the functions cannot allocate the memory segment, then a NULL pointer (0) is returned. You should check the return value each time you use these functions to be sure that the memory segment was successfully created. Trying to use a bad pointer will result in undesirable behavior or system crashes.

There is no intrinsic method for determining the size of an allocation; you must keep track of this information yourself.

Be careful not to use the same pointer variable to allocate two or more memory segments. Reusing a pointer without first deallocating the segment it points to will result in the memory segment being lost causing a memory leak.

Pointer Arithmetic and Pointer Indexing

When you create a memory segment using the allocation functions, you will need a way to access the data contained within the segment. In FreeBasic there are two methods for accessing data in the segment; using the indirection operator with pointer arithmetic, and pointer indexing.

Pointer arithmetic, as the name suggests, adds and subtracts values to a pointer to access individual elements within a memory segment. When you create a typed pointer such as Dim myPointer as Integer ptr, the compiler knows that the data being used with this pointer is of size Integer or 4 bytes. The pointer, when initialized, points to the first element of the segment. You can express this as *(myPtr + 0). To access the second element, you need to add 1 to the pointer, which can be expressed as *(myPtr + 1).

Since the compiler knows that the pointer is an Integer pointer, adding 1 to the pointer reference will actually increment the address contained in myPtr by 4, the size of an Integer. This is why using typed pointers is preferable over untyped pointers. The compiler does much of the work for you in accessing the data in the memory segment.

Notice that the construct is *(myPtr + 1) and not *myPtr + 1. The * operator has higher precedence than +, so *myPtr + 1 will actually increment the contents myPtr points to, and not the pointer address.

*myPtr will be evaluated first, which returns the contents of the memory location and then +1 will be evaluated, adding 1 to the memory location. By wrapping myPtr + 1 within parenthesis, you force the compiler to evaluate myPtr + 1 first, which increments the pointer address, and then the * is applied to return the contents of the new address.

Pointer indexing works the same way as pointer arithmetic, but the details are handled by the compiler. *(myPtr + 1) is equivalent to myPtr[1]. Again, since the compiler knows that myPtr is an integer pointer, it can calculate the correct memory offsets to return the proper values using the index. Which format you use if up to you, but the index method resembles the standard array access method and visually is easier to understand than the indirection operator.

Pointer Functions

Freebasic has a set of pointer functions to complement the pointer operators. The following table lists the pointer functions.

Function Syntax Comment
Cptr myPtr = Cptr(data_type, expression) Converts expression to a data_type pointer. Expression can be another pointer or an integer.
Peek B = Peek(data_type, pointer) Peek returns the contents of memory location pointer to by pointer. Data_type specifies the type of expected data.
Poke Poke data_type, pointer, expression Puts the value of expression into the memory location pointed to by pointer. The data_type specifies the type of data being placed into the memory location.
Sadd myPtr = Sadd(string_variable) Returns the location in memory where the string data in a dynamic string is located.
Strptr myPtr = Strptr(string_variable) The same as Sadd.
Procptr myPtr = Procptr(function) Returns the address of a function. This works the same way as the addressof operator @.
Varptr myPtr = Varptr(variable) This function works the same way as the addressof operator @.

The Sadd and Strptr functions work with the string data types to return the address of the string data. The Peek and Poke functions have been added for the purposes of supporting legacy code. Procptr and Varptr both work just like the address of operator @, but Proptr only works on subroutines and functions and Varptr only works on variables. Cptr is useful for casting an untyped pointer to a typed pointer, such as a return value from a third party library.

Subroutine and Function Pointers

Subroutines and functions, like variables, reside in memory and have an address associated with their entry point. You can use these addresses to create events in your programs, to create pseudo-objects and are used in callback functions. You create a sub or function pointer just like any other pointer except you declare your variable as a pointer to a subroutine or function rather than as a pointer to a data type.

Before using a function pointer, it must be initialized to the address of a subroutine or function using Procptr or @. Once initialized, you use the pointer in the same manner as calling the original subroutine or function.

You declare a function pointer using the anonymous declaration syntax.

Dim FuncPtr As Function(x As Integer, y As Integer) As Integer

You then need to asscoate this function pointer with an actual subroutine or function within your code.

Function Power(number As Integer, pwr As Integer) As Integer
    Return number^pwr
End Function

FuncPtr = @Power

You can then call the function pointer much like you would call the real function.

FuncPtr(2, 4)

While this may not be useful at first glance, you can use this technique to implement polymorphic functions where a single variable instance can point to one of several different subroutine or functions.

For example, suppose you have a dog and cat object. Both objects need a Speak method. By defining Speak as a function pointer and associate Speak with a Bark subroutine for a dog and a Meow subroutine for a cat, you can make Speak either issue a "Woof!" or "Meow!" depending on the object type.

Creating a Callback Function

One of the primary uses for function pointers is to create callback functions. A callback function is a function that you have created in your program that is called by another function or subroutine either in your own code space or in an external library. Windows uses callback functions to enumerate through Window objects like fonts, printers and forms.

The qsort, function contained within the C Runtime Library sorts the elements of an array using a callback function to determine the sort order. The prototype for the qsort function is contained in stdlib.bi:

declare sub qsort cdecl alias "qsort" (byval as any ptr, byval as size_t, byval as size_t, byval as function cdecl(byval as any ptr, byval as any ptr) as integer)

The following lists the parameter information for the qsort subroutine.

  1. The first parameter is the address to the first element of the array. The easiest way to pass this information to qsort is to append the address of operator to the first element index: @myArray(0).
  2. The second parameter is the number of elements in the array, that is the array count.
  3. The third parameter is the size of each element in bytes. For an array of integers, the element size would be 4 bytes.
  4. The fourth parameter is a function pointer to the user created compare function. The function must be declared using the Cdecl passing model, as shown in this parameter.

Using this information, you can see how qsort works. By passing the address of the first element along with the count of elements, and the size of each element, qsort can iterate through the array using pointer arithmetic.

Qsort will take two array elements, pass them to your user defined compare function and use the compare function's return value to sort the array elements. It does this repeatedly until each array element is in sorted order.

You need to declare the function prototype as Cdecl which ensures that the parameters are passed in the correct order.

Declare Function QCompare Cdecl (Byval e1 As Any Ptr, Byval e2 As Any Ptr) As Integer

You would then define the function like the following.

'The qsort function expects three numbers 
'from the compare function:
'-1: if e1 is less than e2
'0: if e1 is equal to e2
'1: if e1 is greater than e2
Function QCompare Cdecl (Byval e1 As Any Ptr, _
                         Byval e2 As Any Ptr) As Integer
        Dim As Integer el1, el2
        Static cnt As Integer

        'Get the call count and items passed
        cnt += 1
        'Get the values, must cast to integer ptr
        el1 = *(Cptr(Integer Ptr, e1))
        el2 = *(Cptr(Integer Ptr, e2))
        Print "Qsort called";cnt;" time(s) with";el1;" and";el2;"."
        'Compare the values
        If el1 < el2 Then
            Return -1
        Elseif el1 > el2 Then
            Return 1
        Return 0
        End If
End Function

You would then call the QSort function passing the address of the callback function.

qsort @myArray(0), 10, Sizeof(Integer), @QCompare

Pointer to Pointer

In FreeBasic you can create a pointer to any of the supported data types, including the pointer data type. A pointer to a pointer is useful in situations where you need to return a pointer to a function or in creating specialized data structures such as linked-lists and ragged arrays. A pointer to a pointer is called multi-level indirection.

One application of a pointer to pointer is the creation of a memory segment that behaves just like an array. For example, suppose you want to create a memory segment to hold an unknown number of integers. You can create a dynamic memory segment that you can resize as needed during runtime to handle as many integers as you need. You would start by creating a pointer-to-pointer variable.

Dim myMemArray As Integer Ptr Ptr

You would then initialize the pointer reference by using Allocate or Callocate.

'Create 10 rows of integer pointers
myMemArray = Callocate(10, Sizeof(Integer Ptr))

Notice that the variable is initialized to an Integer Ptr since this list is going to point to another list; this is the pointer that points to another pointer. You can then initialize the individual pointer references just created to point to the needed memory segments.

'Add 10 columns of integers to each row
For i = 0 To 9
    myMemArray[i] = Callocate(10, Sizeof(Integer))

In this code snippet, the individual pointers in the list are initialized to 10 memory segments that will contain the actual integer data.

'Add some data to the memory segment
For i = 0 To 9
    For j = 0 To 9
        myMemArray[i][j] = Int(Rnd * 10)

This code snippet uses the index method to load the actual data into the memory segments. Notice that this looks and acts just like a two-dimensional array. While this may not seem useful within this context, you could use this code to create a dynamic array within a type definition. Since you cannot have a standard dynamic array within a type, this allows you get the same functionality.

One thing you need to be aware of is how to deallocate a structure such as this. The rule is to just do the reverse of the allocation operations. Since the last allocate operation initialized the data memory segments, you deallocate these memory segments first and then you can deallocate the base pointer.

'Free memory segment
For i = 0 To 9
    Deallocate myMemArray[i]

'Free the pointer to pointer
Deallocate myMemArray

You need to be sure that you deallocate in the right order, otherwise you will end up with memory segments that are not freed, but inaccessible. These are memory leaks and can cause a whole host of problems in your program.