Pointers and Composite Types

Introduction

In this tutorial you will see how to use pointers with user-defined types as well as how to use pointers within a type definition. You should first read the introductory tutorial on pointers first if you haven’t done so since this tutorial builds on the concepts presented in the introductory tutorial.

Quick Review of the Type Def

FreeBasic allows you to create user-defined, aggregate types that allow you to group different types of data into a single object. The following program illustrates how to define, create and use a simple type def.

Type vector
    x As Single
    y As Single
End Type

Dim myVector As vector

myVector.x = 1.5
myVector.y = 10.2

Print "X = "; myVector.x
Print "Y = "; myVector.y

Sleep
End

The type definition is contained within the Type <name> and End Type keywords. As you can see from the example, each element within the type is defined as one of the standard data types. Once the definition has been created, you can then Dim the type like you would any other data type. To access each of the elements within the type, you use the type variable name, in this case myVector, followed by the dot . operator and finally the type element. myVector.x access the x element while myVector.y access the y element.

Pointing to Types

You create a pointer to a type in the same manner as a pointer to one of the standard data types. The following program creates a pointer to the vector type.

Type vector
    x As Single
    y As Single
End Type

Dim myVector As vector Ptr

myVector = Callocate(1, Sizeof(vector))

myVector->x = 1.5
myVector->y = 10.2

Print "X = "; myVector->x
Print "Y = "; myVector->y

Deallocate myVector

Sleep
End

This program looks almost the same as the previous program, except for a few minor changes. The Dim statement has been changed to vector Ptr, to create a pointer variable to the type definition. The Callocate statement is then used to initialize the address of the pointer. To access each element of the type, the arrow -> operator is used instead of the dot operator. Everything else in the program is the same.

You can also use the index method rather than the arrow operator as the following program illustrates.

Type vector
    x As Single
    y As Single
End Type

Dim myVector As vector Ptr

myVector = Callocate(1, Sizeof(vector))

myVector[0].x = 1.5
myVector[0].y = 10.2

Print "X = "; myVector[0].x
Print "Y = "; myVector[0].y

Deallocate myVector

Sleep
End

When you use the index method, you can access each type element using the dot operator after the square brackets. This method will be expanded upon in the following program examples.

Pointer Memory Arrays of Types

Creating a memory array of types is much like creating a memory array of a standard data type. The following program creates a pointer memory array of the vector type.

#define numvectors 5

Type vector
    x As Single
    y As Single
End Type

Dim myVector As vector Ptr
Dim As Integer i

Randomize Timer

myVector = Callocate(numvectors, Sizeof(Vector))

For i = 0 To numvectors - 1
    myVector[i].x = Rnd 
    myVector[i].y = Rnd 
Next

For i = 0 To numvectors - 1
    Print "I = "; i
    Print "X = "; myVector[i].x
    Print "Y = "; myVector[i].y
Next

Deallocate myVector

Sleep
End

In this program a #define has been added so that the program knows how many elements to create. The Randomize Timer is used to seed the random number generator, which will be used to load random values into the vector elements. As in the previous example, the Callocate function is used to create a memory segment equal to the number of needed elements.

The first For-Next loop loads the individual vectors with a random value for the x and y elements. By using the index method, you can use the dot operator to access each of the type elements, that is the x and y members of the vector type. This makes working with multiple type elements very similar to a standard array of types.

The access hierarchy is important here. You first need to select the correct type from the memory segment using myVector[i]. This puts the pointer at the start of the memory address of the desired type element. You then use the dot operator to further qualify which element of the type you want to access. myVector[i].x moves the pointer to the x memory location, while myVector[i].y moves the pointer to the y memory location.

The second For-Next prints out the values of the memory segment by using the same index-dot notation method. Once the values are printed to the screen, the memory is Deallocated. Once again, it is not strictly necessary to deallocate the memory segment in this case since the memory segment has global scope, but it is always good practice to clean up pointer references when you are finished working with them.

Pointers Within a Type

There will come a time when you will find it convenient to have a dynamic array within a type definition. FreeBasic doesn’t support standard dynamic arrays within types, but you can create them using pointers. The following program creates a type def with a pointer element that will hold a number of vector elements.

#define numvectors 5

Type vector
    x As Single
    y As Single
End Type

Type arrvector 
    v As vector Ptr
End Type

Dim myVector As arrvector
Dim As Integer i

Randomize Timer

myVector.v = Callocate(numvectors, Sizeof(Vector))

For i = 0 To numvectors - 1
    myVector.v[i].x = Rnd 
    myVector.v[i].y = Rnd 
Next

For i = 0 To numvectors - 1
    Print "I = "; i
    Print "X = "; myVector.v[i].x
    Print "Y = "; myVector.v[i].y
Next

Deallocate myVector.v

Sleep
End

This program is similar to the previous example with a few important changes. A new type definition was added, arrvector, which has a single element v defined as a pointer to the vector type. v will be the dynamic vector type memory array. myVector is then Dimmed as a standard type variable, that is without the ptr qualifier.

Callocate is again used to initialize the memory segment, this time though, the memory segment is contained within the arrvector type definition so you must use the dot notation to qualify the v member of myVector. To access each element within the memory array, the index method is used with the v element to get the address of the individual vector elements, which is followed by the dot operator to qualify the individual vector elements since they are standard data types.

This may seem a bit confusing, so here is the hierarchy of the object.

myVector (arrvector type)
--.v (points to vector type)
--.v[i] (selects vector type element)
----.x (vector elements)
----.y

The v element is a standard element within myVector so you use the dot notation. v however is a pointer element, so you use the index method with v to select the proper vector type. x and y are standard elements within the vector type definition so the dot operator is used to select the x and y elements. The rule is: if the element is a standard type, use the dot operator. If the element is a pointer type, use the index method.

Keeping this rule in mind you can see how the vector elements are accessed within the two For-Next loops. v is a standard type of myVector so it is qualified with a dot operator. The index method is then used with v to select the vector type elements, since v is a pointer that is pointing to the type vector. x and y are standard types within vector, so the dot operator is again used to qualify the individual vector elements. If you keep the rule in mind when accessing these types of structures you won’t get lost in the object hierarchy.

Memory Arrays Containing Memory Arrays

Dynamic memory arrays within a type offer a lot of flexibility, but you can increase the flexibility of the object by making the object itself dynamic. The following program, based on the previous example, makes myVector dynamic.

#define numvectors 2

Type vector
    x As Single
    y As Single
End Type

Type arrvector 
    v As vector Ptr
End Type

Dim myVector As arrvector Ptr
Dim As Integer i, j

Randomize Timer

myVector = Callocate(numvectors, Sizeof(arrvector))

For i = 0 To numvectors - 1
    myVector[i].v = Callocate(numvectors, Sizeof(Vector))
Next

For i = 0 To numvectors - 1
    For j = 0 To numvectors - 1
        myVector[i].v[j].x = Rnd 
        myVector[i].v[j].y = Rnd
    Next 
Next

For i = 0 To numvectors - 1
    For j = 0 To numvectors - 1
        Print "I = "; i
        Print "J = "; j
        Print "X = "; myVector[i].v[j].x
        Print "Y = "; myVector[i].v[j].y
        Print
    Next
Next

For i = 0 To numvectors - 1
    Deallocate myVector[i].v
Next

Deallocate myVector

Sleep
End

While this program may seem a lot more complex than the previous example, it only adds a single level of indirection to the structure. As you go through the program, keep the access rule in mind and you won’t get lost in the structure hierarchy.

At the top of the program, the #define was changed from 5 to 2, to make the output easier to read. This illustrates the benefit of using a #define or variable to hold the number of memory array elements. By simply changing the #define statement, you can change the number of elements without any further code changes.

The two type definitions remain the same, but myVector is now Dimmed as a pointer since this will be a pointer memory array. Following the Dim statements, Callocate is used to initialize myVector with numvectors of elements. Since the program is creating a memory array of arrvectors the size of each element corresponds to the size of arrvector using the Sizeof function.

Once myVector has been initialized, the program uses a For-Next loop to initialize each v element of myVector with a memory array of vector types. You can see the rule in action here: myVector is a pointer so the index method is used, followed by the dot operator since v is a standard type of myVector. Keep in mind that v is a pointer so each v element must be initialized before it can be used.

The following For-Next loop loads data into both memory arrays. The i loop selects each myVector element while the inner j loop selects each vector element of v. Again, refer to the rule so you don’t get lost here. myVector[i]selects the individual myVector type elements, since myVector is a pointer to arrvector. myVector[i].v[j] selects the individual vector type elements, since v is a pointer to the vector type. The dot operator is finally used to reference the x and y elements of the vector memory array since these are standard data types. The For-Next loops used in the Print statements follow the exact same access methods.

Go over this a few times to be sure you understand how the hierarchy is structured and how you access the various structure elements. Once you firmly grasp the different levels of indirection, you can create quite complex and flexible data structures for any situation that may arise.

The last change to this program is the Deallocation methods. This structure is a pointer that contains a pointer, so you must deallocate the inner pointer first before you can deallocate the outer pointer. A For-Next loop is used to deallocate each v element in myVector. When all of the v pointers have been released, then it is safe to release the myVector pointer, since the v pointers are now NULL pointers. The rule here is: when deallocating nested pointers, start with the innermost pointer and work outward. If you keep this simple rule in mind, you won’t have any problems with memory leaks.

When you run the program you should see something similar to the following.

I =  0
J =  0
X =  0.9423408
Y =  0.4058496

I =  0
J =  1
X =  0.9952765
Y =  0.7194054

I =  1
J =  0
X =  0.2346756
Y =  0.2338194

I =  1
J =  1
X =  0.5729287
Y =  0.6099271

Since Rnd is used to load the x and y values, the numbers you see will be different.