FreeBasic Pointers tutorial
By VonGodric email@example.com
About this tutorial
This tutorial is meant to be as a guide to FreeBasi'c pointers world for beginners or who just wish some information about them and their support in FreeBasic. This tutorial will cover several types of pointers and their notations, also breafly about linked lists and their types and pointers to functions/subs. As well as pointers to pointers. Als should be noted that writing this tutorial I used fbc compiler 0.11 and becouse of that some of the stuff won't work on older versions!
What are pointers?
To understand what are pointers, we need to understand what variables are. Variables are references to a memory location in RAM where you can write or read data. So thus, they store some data and have an address in memory. In basic definition Pointers are variables that store address of another variable. That's where the name comes from Pointer -points to something.
Pointers in FreeBASIC
FreeBASIC unlike QBasic and many other BASIC dialects has full support for them. FB have them to following language structures:
Starting with pointers...
here's our first small example:
dim i as integer print @iWhen you run it -it will print out some strange number, further more, every time you run this program, it may show you different number, and it is most unlikely to get the same number after restart. When I ran it the number was: 4218880
This number is the memory address of the variable i. You can experiment with this a little -try different variable types for example. However be noted: that misusing pointers may lead to program crash, in worse situations even system crash!
Creating pointers is very similar to declaring a variable with dim: Dim name Type PTR|POINTER it doesn't matter do you use ptr or pointer, both are the same thing. so here's another litte example:
option explicit dim i as integer dim ip as integer ptr ip = @i print @i print ipAs you will see, both number are the same -however, You may notice that we pass the address of the variable i to the pointer ip. and ip used as is, is just a variable. You can also get the address of the pointer ip itself with @ip -and it will be different, because every variable has a unique address (it is possible to have variables with the same address however with Union's)
Now let's do some actual use of pointers:
option explicit dim i as integer dim ip as integer ptr i = 10 ip=@i print *ip 'Print out the value to what pointer points *ip =20 'Assign value to variable that pointer points print iThe outcome will be 10 and 20 accordingly. The * will give the value of the variable that our pointer points to, or write to the value. Now you may have a question, what are they good for? Well great many things, pointers may speed-up or/and cut down your program in size. They'r most common use is however is in linked lists(will be looking at them shortly) and allocating memory from the heap (we'll discuss about that too). So here's one more example:
option explicit dim i as integer dim m as integer dim ip as integer ptr ip = @i *ip = 10 : m = 25 print *ip * m print sqr(*ip) *ip = *ip -5 print *ip print *ip / m sleepAs you see, pointers can be used just like any normal variables -you can assign values through them, use in calculations, pass as arguments, and mutch more. However word of caution: do NOT mistake *ip with just ip, one is a memory address -and therefore holds completely different value -and the other one the value of the variable the pointer points to. So therefore doing ip = ip * m for example will change the address it points to -and most probably will crash your program when you try to use it as pointer again: *ip = 100.
Pointers and arrays
Hopefully you understood previous stuff -if not try experimenting. Just as pointers may be used with variables, they can also be used with arrays. First a small example, then we'll analize it:
option explicit 'Define array and pointer dim array(10) as integer dim ap as integer ptr dim i as integer 'Fill array with numbers for i = 0 to 10 array(i) = i next 'Pass the address of the first element ap=@array(0) 'And print it out for i = 0 to 10 print *(ap+i) next sleepIt will print out numbers from 0 to 10. So what is going on in there? We pass the address of the first array element to the pointer (ap=@array(0)), and then we go through the loop from 0 to 10 and print out. The notation *(ap + i) means: we will print out to what points the value that is in the parenthesis. You may however wonder why it prints out the right numbers? Shouldn't it screw the values -meaning that pointer always points to one byte in memory, but Integer is 4 byte? The FreeBasic compiler knows the size to what the pointer points, and meaning + 2 means actually to step 2 integer sizes forward.
Why is this useful: You may link what ever integer array (or of any other type you have specified) to one pointer and use it, without having to know exactly what is in it. Here is the same thing as above with a bit different notation:
option explicit 'Define array and pointer dim array(10) as integer dim ap as integer ptr dim i as integer 'Fill array with numbers for i = 0 to 10 array(i) = i next 'Pass the address of the first element ap=@array(0) 'And print it out for i = 0 to 10 print ap[i] next sleepDoes the same thing -only difference now is instead of *(ap + i) ap[i] This is just a shorter notation and is a bit cleaner. In this case you don't need (in fact can't) use * in front of the pointer -that's because of the [ and ]. When these are used, compiler automatically knows that this is a pointer. ap[indx] = smth is also valid.
because of greater complexity of multidimensional arrays using pointers in FreeBasic, we'll skip them for now.
Pointers and UDT's
Like to arrays, you can have pointers to user defined types too. Here's for starter another example:
type mytype i as integer s as single z as string end type dim t as mytype dim up as mytype ptr t.i = 10 t.s = 17.87 t.z = "UDT ptr test" up=@t print up->i print up->s print up->z sleepAs you see, pointers with UDT's is very simle, first you define a ptr of the udt type and then take the address of it with @t and for getting the right field we use -> and field name. Note that * is not needed, because of -> what says already that this is a pointer. Assigning values through pointers is ptr->field = value. Like with arrays -different notations can be used:
'variant 2 print *(up).i print *(up).s print *(up).z
'variant 3 print up.i print up.s print up.zThis second and third style, as you see is almost like array's with *(ptr+idx), infact, you can use pointers with arrays of UDT's too. And notation is almost same:
type mytype i as integer s as single z as string end type dim t(5) as mytype dim up as mytype ptr t(3).i = 10 t(3).s = 17.87 t(3).z = "UDT ptr test" up=@t(0) print *(up+3).i print *(up+3).s print *(up+3).z print up.i print up.s print up.z sleepAs you see, all calculations are done for you, the index 3 will give the correct answer and you can choose from several styles and decide what suits you best.
Arrays/UDT's outside of DIM
When you use DIM -you use the memory that has been previously allocated for your running program by the OS (in FB the default is 1MB -however for more see the commandline options of fb), but what if you want more? One way to archive this would be to increase stack size allocated for your program -but if you want to have dynamically allocatable memory (games/programs often need that) -you are still stuck to that problem! Solution is to allocate memory from the system heap -let's just say that it's the rest of the free memory in your computer that is not already used by other applications. To use memory from the heap is more complex and dangerous then with DIM.
Why it's dangerous, is that this is often main source of crashes and errors, you probably have heard of memory leaks? Well you're about to get to know what they are: DIM procedure is controlled by the compiler -so whenever you allocate with it, it get's automatically deallocated once program exits SUB or FUNCTION for example. So memory is freed. However allocating memory from the heap, is not deallocated(freed) after your code has done it's work (exited sub/function or program itself) although modern OS's do free memory for you it is a good practice to not rely on it! Keep in mind a simple rule -once you allocate, next thing you do is make sure it get's deallocated!Now what has this to do with pointers? Well everything, you get the address to the allocated memory block, so only using pointers can you acces to it. Now before we move on, let's make a simple example:
option explicit type mytype i as integer s as string * 12 end type dim p as mytype ptr p = allocate(len(mytype)) if p = 0 then print "ERROR: Out of memory!" sleep end end if *(p).i = 10 *(p).s = "Hello world!" print p->i print p->s sleep deallocate pNow let's analyse that. First we created an udt, then dimmed pointer to udt. then we allocated memory for our udt. ALLOCATE allocates memory from the heap. Len(mytype) returns the size in bytes that udt takes. We need to know how mutch memory to allocate. What allocate returns is a pointer to the first byte of the memory stream. And that's all, now we can use it almost like with Dim, but with exception of being able to access it only through pointers. The last thing is to deallocate -it erases(frees) the memory we had allocated.
Now that warnings have been said (reread them) we can move on and see how can we put an arrays to the heap.
option explicit dim i as integer dim p as integer ptr p = callocate(len(integer) * 10) if p = 0 then print "ERROR: Out of memory!" sleep end end if for i=0 to 9 p[i] = i next for i=0 to 9 print *(p+i) next sleep deallocate p
Notice that we allocated memory by multiplying the size of integer by ten, but true indexing ranges from 0 to 9, be aware of this! Placing UDT's to heap is done just the same way, only use len(udt) and if you want to have an array of it, multiply it by array size
Now that we have understood all previous, let's move on. As you saw pointers can point at UDT's, but they can also be a part of the UDT, furthermore pointer part of the UDT can point to it'own UDT -thus linked lists. All there is about these mysterious things is this that udt's point to other udt's. Let's start with simple example:
'Simple linked list example type linkedlist 'data that list holds i as integer 'points to next node listnext as linkedlist ptr 'pointe to previous node listprev as linkedlist ptr end type 'the head node of the list 'and two for temporary use dim head as linkedlist ptr dim node as linkedlist ptr dim temp as linkedlist ptr for i = 1 to 10 'allocate memory node = callocate(len(linkedlist)) if node = 0 then 'see if allocation was succesful print "ERROR: Out of memory" sleep 'because some initalisations may have 'been successful, we need to deallocate them goto endlinkedlist: end end if node->i = i 'set our data 'point to previously allocated node node->listprev = temp if temp = 0 then 'see if temp is 0 -if is then it is first 'allocation temp = node 'set temp to show current one head = temp 'also set head continue for end if 'set next node of the previous node to be current one temp->listnext = node temp = node 'set this for next run next 'since we save first node as head -we can use it to start reading from the list node = head 'since final node's listnext holds 0, we can use this 'as the condition to terminate when link is over while node 'print out data that node holds print node->i; 'save the current node -for the next demo temp = node 'move to the next one node = node->listnext wend print 'since we saved in previous demo temp to 'be last active node, we can use it node = temp 'Just like listNext of the lastnode is NULL pointer 'so is listPrev of the first noe. So again we can use this as 'terminate condition to make sure we exit the list. while node 'print out data that node holds print node->i; 'instead of next -let's move backwards node = node->listprev wend sleep endlinkedlist: 'again start from the head and move through node = head while node temp = node 'to avoid getting next node from already 'deallocate node, we need to use a temp node = node->listnext deallocate (temp) wendThis should be pretty easy concept to understand -node is one UDT block, that points to previous or/and next node or have NULL pointer meaning the end of the list. Why is this good for? Linked lists are in a way like arrays (you probably notices this by now) but they have several advantages and disadvantages over arrays.
|Each node is seperated unit||Fast -since arrays are aligned in memory.|
|Easy and fast to add/remove/replace nodes with one another||Compact -takes less memory|
|because each node is seperate unit -it doesn't take large memory block with the size of the whole List||Lot Easier handling|
|Same node can be pointed from different lists||Are less bug prones|
|Very dynamic||Easy to copy/pass whole arrays (to a file)|
|Consume more memory||Slow and compilcated remove and insert|
|Slower in most cases||Consume big memory block (sometimes there's enough of free memory, but it is in too small chunks)|
|Introduce many risks for bugs and memory leaks||All pros's of the Linked lists|
'FreeBasic Linked-list example 'Stack 'By VonGodric OPTION explicit TYPE MyStack DATA AS STRING Node AS MyStack ptr END TYPE SUB Push (PushData AS STRING, byref Stack AS MyStack ptr) DIM NewNode AS MyStack ptr NewNode = CAllocate(LEN(MyStack)) NewNode->Data = PushData NewNode->Node = Stack Stack = NewNode END SUB FUNCTION Pop (byref Stack AS MyStack ptr) AS STRING DIM OldNode AS MyStack ptr OldNode = Stack Pop = OldNode->Data Stack = OldNode->Node deallocate(OldNode) END FUNCTION FUNCTION Top (byref Stack AS MyStack ptr) AS STRING Top = Stack->Data END FUNCTION FUNCTION IsEmpty (byref Stack AS MyStack ptr) AS INTEGER IsEmpty = Stack=0 END FUNCTION '------------------------------------------------------------------------------- 'Main code DIM test AS MyStack pointer DIM a AS STRING DO a$=INKEY$ IF a$>"" AND a$<>chr$(13) THEN PRINT a$; : push (a$, test) LOOP UNTIL a$=CHR$(13) PRINT DO WHILE NOT IsEmpty(test) PRINT Top (test); PRINT Pop (test); LOOP SLEEP '-------------------------------------------------------------------------------
'FreeBasic Linked List example 'Queue 'By VonGodric OPTION explicit TYPE MyQueue DATA AS STRING Node AS MyQueue ptr END TYPE TYPE Queue Rear AS MyQueue ptr Front AS MyQueue ptr END TYPE FUNCTION Initalise(QueuePtr AS Queue ptr) AS INTEGER QueuePtr->Front = callocate(LEN(MyQueue)) IF QueuePtr->Front = 0 THEN Initalise = 0 : EXIT FUNCTION QueuePtr->Rear = QueuePtr->Front QueuePtr->Front->Node = 0 Initalise=-1 END FUNCTION FUNCTION Join (Queue AS Queue ptr, QData AS STRING) AS INTEGER DIM NewNode AS MyQueue Ptr NewNode=callocate(LEN(MyQueue)) IF NewNode = 0 THEN Join = 0 : EXIT FUNCTION NewNode->Data = QData NewNode->Node = 0 Queue->Rear->Node = NewNode Queue->Rear = NewNode Join = -1 END FUNCTION FUNCTION leave(QueuePtr AS Queue ptr) AS STRING DIM OldNode AS MyQueue ptr OldNode = QueuePtr->Front->Node leave = OldNode->data IF (QueuePtr->Front->Node->Node = 0) THEN QueuePtr->Rear = QueuePtr->Front ELSE QueuePtr->Front->Node = QueuePtr->Front->Node->Node END IF deallocate(OldNode) END FUNCTION FUNCTION IsEmpty (QueuePtr AS Queue ptr) AS INTEGER IsEmpty = (QueuePtr->Front = QueuePtr->Rear) END FUNCTION '------------------------------------------------------------------------------- 'Main code DIM test AS Queue IF NOT Initalise(@test) THEN END DIM a AS STRING DO a$=INKEY$ IF a$>"" AND a$<>chr$(13) THEN PRINT a$; : Join (@test, a$) LOOP UNTIL a$=CHR$(13) PRINT DO WHILE NOT IsEmpty(@test) PRINT Leave (@test); LOOP SLEEP '-------------------------------------------------------------------------------
Pointers and functions/subs
So far We've been through a lot and there's very little left to describe -although it is a matter on what could be possible to write heavy nooks. First We'll look at pointers in the functions/subs argument lists.
Very similar actually to defining any other pointer -you just put a pointer, and pass the pointer or address with @.
sub test (byval i as integer ptr) print *i end sub dim i as integer i=10 test ( @i ) sleepYou may notice here byval argument -it is meant that pointer(value it holds) is passed by value itself and not reference(aka address) to that variable in what value is stored. Let's see here what byref does:
sub test (byref i as integer) i = 10 end sub dim i as integer print i test i print i sleepInside function/sub byref passed variable obtain the same address as of the variable that was passed. Functions can also return pointers, in that case you need to do function (args...) as type PTR.
However more interesting part ( and very useful ) is pointers to subs/functions themselves. Yes, it is possible!. Here's small example:
sub sub1 ( i as integer ) print "hello from sub1" print "you passed value: "; i end sub sub sub2 ( i as integer ) print "hello from sub2" print "you passed value: "; i end sub dim test as sub (i as integer = 10) test = @sub1 test ( 6 ) test ( ) test = @sub2 test ( 1001 ) test ( ) sleepas you see, different sub's are called -and yes you can set default values on sub/functions pointers too. What is importand in sub/function declarations is that the prototype MUST match the prototype of the function/sub your are using pointers on. You can also pass function/sub pointers inside another function/sub parameter lists as well:
function func2 ( i as integer ) as integer func2 = i * i end function function func1 ( byval tocall as function, byval value as integer ) dim i as function ( i as integer ) as integer i = tocall func1 = i ( value ) end function dim s as function ( byval tocall as function(i as integer), byval value as integer ) as integer s=@func1 print s(@func2, 10) sleepAs you see, using function pointers is almost same, only you must specify all types both the return one and also parameter list. Also as you can see, one pointers value can be given to another.
option explicit dim i as integer dim ip1 as integer ptr dim ip2 as integer ptr ptr ip1 = @i ip2 = @ip1 **ip2 = 157 print i, *ip1, **ip2 print @ip1, *ip2 ip2 = @i *ip2 = 751 print i, *ip2 sleepNow look at this code -it should be pretty straightforward don't you think? Anyway declaring a pointer that points to another pointer must be declared explicitly as ptr ptr ... with the number of maximum redirections allowed. To point to the target you use * as many times as redirection is involved.
Sometimes we do not really know or need to either, the type of the pointer in our program, let's say like when passing to the function/sub as an argument, we may be dealing with different types in it. For that in FB is ANY ptr. Here's example:
option explicit sub test (byval p as any ptr, byval t as integer) dim i as integer ptr dim b as byte ptr dim s as string ptr dim d as double ptr i = p b = p s = p d = p select case t case 1 print *i case 2 print *b case 3 print *s case 4 print *d end select end sub dim i as integer dim b as byte dim s as string dim d as double i = 10001 b = 127 s = "Hello world!" d = 2005.0208 test @i, 1 test @b, 2 test @s, 3 test @d, 4 sleepAs you see, nothing difficult here either. You may experiment with passing "wrong" numbers for wrong types. It should work except for strings. And that's more or less there is to typeless pointers. Also addresses can be passed without a problem from one pointer to another with different types -let's say from an integer ptr to the byte ptr. This way you can archive sort pointer casting from one type to another.
Well that's pretty mutch it for this tutorial. I hope you liked it and found something useful or new for yourself. One more thing however, throughout this tutorial I used @ symbol to obtain and address of smth, however these functions maybe used as well: