Pointer Tutorial Request

BlitzMax Forums/BlitzMax Tutorials/Pointer Tutorial Request

Chroma(Posted 2005) [#1]
Can someone go over the basics of pointers and why they are useful?

Stuff like:
VarPtr

Etc...


Matt McFarland(Posted 2005) [#2]
Chroma, you posted in the wrong category. Tutorials is for tutorials that are already made, not requests :)


ImaginaryHuman(Posted 2005) [#3]
Ok.

Pointers are a descendent from the fact that a CPU is attached to main memory which is separate from the CPU, and that the CPU needs a way of telling where in memory something is.

If you just had a CPU by itself, with no memory attached, it would have some `registers`, which are built-in hardwired `variables` if you will, with preset names like R1, R2, R3 etc. You would use these variables to directly store data. For example you might put the size of your waist in R1, the length of your nose in R2 and the width of your eyebrow in R3. This is `thinking of` the registers as places to store `data`. The content of the register IS the data. Whenever you go look at that register, what you see there IS the number you stored, ie the length of your arm, etc.

Now, having a CPU by itself isn't too useful because there are only so many registers and they tend to have some limited ways of interacting with each other. You can move stuff around, add data to other data, do some simple math etc, pretty simple stuff. But you can't really store an image there because there isn't enough space, or enough space to store a text file, or a screen display. For that, you need main memory.

So as you add memory to the computer, so that you now have a CPU and storage space *separate from* the CPU, you have to have a way of doing what they call `addressing` the memory. Ie, in the CPU it knows how many registers there are, what they are called, where they are located, how to get to them, etc. But if you introduce separate memory chips/space, you have to have a way of replacing that hardwiring with something more flexible.

To make an analogy, in the CPU, the registers are located in a similar way to how a Const (constant) variable has a fixed value. Register 0 is, for example, the first one in the register space, Register 1 immediately follows that, etc. So either the CPU has specific instructions that work exclusively with a specific register, ie the whole thing is hardwired, or it uses some `bits` in the CPU instruction (opcode) to define which registers to use. But let's get back to our topic...

So, what you need then, to handle main memory from the CPU, is to think about data storage places differently. Instead of saying "put the size of my shoe in register 0", knowing that then when you look in register 0 you see the size of your shoe, you have to say "put the size of my shoe in some location in memory and then put the number of that *memory location* in register 0. So now, when you look in register 0, you don't see your shoe size, you see a POINTER TO some place in memory where your shoe size is stored.

So, register 0, for example, can either be used to hold a useful number like your shoe size, OR to hold a number of a slot in memory that holds your shoe size. Sometimes in a CPU they will have registers that you can only use for data and registers that you can only use to store `addresses`, but often they a multipurpose as, after all, addresses and data are all just binary numbers being `thought of` differently.

In higher level programming languages, like C, C++, Basic, Blitz, etc, a `pointer` is basically the same thing. It is pointing TO a value, rather than BEING the value. So what you do is you have a pointer to, for example, an integer, and if you want to store your shoe size in that integer, you first have to look in the place where the ADDRESS OF the memory slot is kept, use that value as the address of the data, go to that memory address and then read what is stored there. It's basically just an `indirect` way of accessing a value rather than directly accessing it.

So, in BlitzMax:

Local MyShoeSize:Int=12 'Directly put my shoe size into a memory space
Print MyShoeSize 'Directly read my shoe size and print it

At this point, BlitzMax actually thinks of the MyShoeSize variable in terms of a POINTER TO that variable, but that is concealed from you behind the scenes. If you ONLY worked in pointers things would be confusing. So somewhere in memory, as well, there is something equivalent to:

Local MyShoeSize:Int Ptr=VarPtr(MyShoeSize)

Let's backtrack a bit. In BlitzMax, a pointer is just another way of saying "it is a variable which points to another variable". Or in other words, a pointer is a variable, which holds an address, which is an address in memory of a value, which you have labelled with the variable name MyShoeSize.

So by creating an Int variable, what the BlitzMax *language* does is presents to you the programmer the idea that you have a dedicated storage space with a name attached to it. That's nice and convenient and usually all you need to be bothered with. Blitz takes care of where it is actually stored and how to get the CPU to find its way to that place, to work on the data.

Whatever variable you create, that variable is just a number being stored somewhere in memory, at a given address. It has a convenient variable `name` to refer to it. If you want to find out where in memory that variable is stored, ie what address it is stored at, you need the POINTER TO the variable, or in other words, the MEMORY ADDRESS OF the variable. Same thing. So in order to properly have a valid BlitzMax variable which points to an Integer variable, for example, it has to be a `pointer to an integer`, aka "Int Ptr".

So...

Local MyInteger:Int=15
Local MyPointerToMyInteger:Int Ptr=VarPtr(MyInteger)

What the `VarPtr` does is, for whatever variable you specify, it returns the address in memory of that variable - ie, what actual address is the number in the variable stored at. ... ie, where in memory is that "15" stored. VarPtr is an abbreviation of Variable Pointer. ie Get the address of the variable/the pointer to the variable.

Think of pointers and addresses as basically the same thing. A pointer is an address, at which something else is stored.

So VarPtr() lets you know where any given variable is stored, and you must store THAT returned address in the correct type of pointer.

Local MyInteger:Int=15
Local PointerToInteger:Int Ptr=VarPtr(MyInteger)

Local MyFloat:Int=15.539
Local PointerToFloat:Float Ptr=VarPtr(MyFloat)

etc... use the right type of pointer for the type of variable you are storing the address of/pointer to.

Pointers are necessary in order for the cpu to reference main memory locations. They are just data being thought of in an indirect way, instead of being taken literally.

Using pointers can get complicated, it's harder to think in terms of things that are indirectly referenced than directly.

Types in BlitzMax are also basically pointers.

ie

Type MyType
Field a:Int
Field b:Int
End Type

Local MyInstance:MyType = New MyType

The MyInstance variable is basically a pointer to a MyType `variable`. Obviously the MyType `variable` is actually a collage of two separate values, a and b. So in order to POINT TO a MyType type of variable, you have a MyType Pointer. Just like you have an Int Ptr for pointing to a single Integer, you have a MyType Ptr pointing to a MyType.

If you wanted to reference variable `a` within MyType, in BlitzMax it is as easy as MyInstance.a or MyType.a, but here Blitz is doing the pointers for you. It first gets the MyType Ptr which points to the instance of this MyType, then it gets the `a Ptr`, an local offset from 0, which indicates where in memory the a variable is, then reads from or writes to THAT location to handle the a variable. The `.` operator is allowing you to work with pointers without having to mess with finding out what the pointers are yourself.

For highly complex Types, for example, you can do MyWorld.MyCountry.MyState.MyTown.MyStreet.MyNumber - all jumping along the chain of pointers. I have no idea if BlitzMax optimizes this and just gives you an overall `final` pointer to the end item in the chain (ie the Street Number, which has its own address), but you get the idea.

So in many ways you can have Blitz handle all the pointer stuff for you, and largely it is doing a lot more with pointers than you `need` to know about as a programmer. But if you want to explicitly work with pointers yourself, you can.

Local MyArray:Int[16]

The MyArray variable is actually an Int Ptr, a pointer to a memory space where 16 Integers are stored. So you could do:

Local MyArray2:Int[]=MyArray

Now the MyArray2 variable `point to` the same memory space, the same array, as the original variable did.

And, behind the scenes, that array pointer is really an Int Ptr.

Also Function Pointers are just Int Ptr() .... ie, an integer-sized pointer to a function. ... why integer sizes? Because addresses are 32-bit values, which is the size of an integer.

You can do more `down and dirty` stuff with pointers. They can be much more efficient than some other techniques and faster too. For example, if I set MyStreetName:String=MyWorld.MyCountry.MyState.MyTown.MyStreet, then I only now have to use MyStreetName, a pointer, to reference the street, which was otherwise stored deeply inside a nested type. It cuts corners.

Things get interesting when you start to get into `indirect addressing`. That is, you can have direct addressing where a variable holds the value of an address in memory, which you then go to to read/write a value. With indirect addressing, you have a second pointer which points to a value, which is an address of another pointer, which you read and then jump to, then read/write the final value. There used to be specific `addressing modes` in CPU's to deal with doing those longwinded types of memory access. Either way, a single pointer is indirect enough.

I hope this helps explain it a bit better.


bradford6(Posted 2005) [#4]
now THAT should go in the official docs!


ImaginaryHuman(Posted 2005) [#5]
Some other things to know about pointers..

One good use for pointers is if you are working on a bunch of data and stepping through it, processing it in some way. For example, you might do this if you have your own software bitmap drawing routine, or some image processing. You would use a pointer to the start of the bitmap data, and then reference each piece of the bitmap one pixel at a time (or whatever). In this case you need the pointer so you can keep track of where in memory you are reading/writing the data. So you increment the pointer each time you want to move on to the next pixel.

Now this is where something else comes in that you should know about. When you add `1` to any pointer, what REALLY happens is that the SIZE of the pointer's TYPE is added to the address in the pointer.

i.e..

Local MyInt:Int Ptr=1500
MyInt:+1
Print Int(MyInt) 'This will print 1504, because you added 1 Int to the address which is 4 bytes

Local MyByte:Byte Ptr=1500
MyByte:+1
Print Int(MyByte) 'This will print 1501, because you added 1 Byte to the address which is 1 byte in size

The same applies to the other types.

Adding 1 to a Byte Pointer increases it by 1
Adding 1 to a Short Pointer increases it by 2
Adding 1 to an Int Pointer increases it by 4
Adding 1 to a Float Pointer increases it by 4
Adding 1 to a Double Pointer increases it by 8

You have to keep this in mind because if you are thinking in terms of bytes of data but you are using an Int pointer to move through it, everytime you increment the Int Ptr by 1 you are actually skipping 4 bytes ahead not 1. Actually it can be convenient that BlitzMax handles it this way because then you if you want to read/write 4 bytes at a time, which is optimal for the CPU, you can just use an Int Ptr, read a whole Integer each time, then add 1 to the Int Ptr to go to the next one.

You can also add an index number to a pointer to access the data at that address, e.g.

Local MyIntPtr:Int Ptr=1500
MyIntPtr[12]=2

This puts the value 2 at memory address 1500+(12*4) (at 1548).

With this you can have you `base address` as a pointer, then use the number in the [ ] brackets as an offset - and remember it's an offset in the size of the TYPE of the pointer, so in the above example, offset 12 adds 48 to the pointer value, not 12, because it's an INT pointer. If it were a Byte Pointer it would add 12.

Also note that when you access data using a Byte Pointer you will only get 1 byte of data, not an integer.

By putting [12] on the end of the pointer, you read the contents of memory AT that address. It's a way of interpreting the pointer as a direct location. Another way of doing it is MyIntPtr[0] and then just increment the Int Ptr itself by 1 each time, rather than using an offset value. It does the same thing and might be more efficient for larger amounts of data, because the CPU instructions might only accept values up to a certain amount as an offset.

Also keep in mind that Function Pointers are not quite a flexible as other pointers. You can convert a Function Pointer into a Byte Pointer but nothing else. If you want an Int Pointer from a Function Pointer you have to make it into a Byte Ptr first, ie Int Ptr(Byte Ptr(MyFunction)). And to convert it back you have to make it into a Byte Pointer first, or directly from another Function Pointer. (Function pointers are actuall Int Ptr()'s).

All pointers consume 4 bytes of memory regardless of the size of the data that they point to.


Dubious Drewski(Posted 2005) [#6]
Very comprehensive. Thanks for the effort, Daniel. This is great.


Chroma(Posted 2005) [#7]
Light bulb went on...thanks Daniel.


ImaginaryHuman(Posted 2005) [#8]
I could never quite get the hang of pointers in the `C` language, seemed real confusing, but Blitz has a nice way of dealing with it.

Sometimes pointers are the ONLY way that you can do certain things, mainly to do with accessing memory at a low level.


Ant(Posted 2006) [#9]
Fantastic stuff guys. Appreciate it.


ImaginaryHuman(Posted 2006) [#10]
Surely


Picklesworth(Posted 2006) [#11]
Thanks Daniel, that is an excellent tutorial!
Is this on BlitzWiki? Its Pointers article seems to be empty...

Anyway, here is a quick example to simply demonstrate creating a pointer, reading the pointed to value, and reading the address being pointed to. (A very important difference to know).
Strict

Local MyVar:Int = 15
Print "My variable: " + MyVar

Local MyVarPointer:Int Ptr = Varptr(MyVar)
Print "My variable's memory address: " + Int(MyVarPointer)
Print "My variable's value, accessed through a pointer!: " + MyVarPointer[0]


A few questions...
Can I create a pointer to a generic object?
Can BlitzMax automatically turn function parameters into pointers, so that the user of a library only has to pass a function as a parameter and it is automatically accessed as a pointer? (PHP does this, I believe).


ImaginaryHuman(Posted 2006) [#12]
A pointer to an object has to be of the type of that object, so if you had a MyType type that you made, you need a MyType Pointer. Not sure exactly if max supports that. There is the generic `Object`, not sure if there is an Object Ptr though. Like Local Instance:MyType=New MyType would give you some kind of handle to the instance but turning it into a pointer you probably get a byte pointer.

Yes you can pass a function pointer as a parameter, it's a variable like any other. e.g.

Function MyFunctionToPass()
Print "Doing it!"
End Function

Function MyFunctionToPassTo(MyFunc())
Print "Here we go..."
MyFunc()
End Function

MyFunctionToPassTo(MyFunctionToPass)

Note it's important not to use () in passing the function to the function, otherwise it will CALL that function and pass the returned value.


Picklesworth(Posted 2007) [#13]
I was fiddling around, trying to get function pointers to take arguments, and I think I've got it. (Yay!)
So, just in case it hasn't been mentioned here:
Function CallMyFunction(MyFunc(In:String) , VarToSend:String)
	Rem
	This function takes a function pointer as an argument.
	It expects that the function pointed to takes one argument , which is a String.
	
	To demonstrate the magic of function pointers , this function takes another argument , 
	which is the variable to pass along to the function it is sent in the MyFunct argument.
	End Rem
	Print "Calling a function passed as an argument..."
	MyFunc(VarToSend)
End Function

Function MyFunctionToPass(In:String = 1)
	Rem
	Prints the In variable which is passed as an argument
	End Rem
	Print In:String
End Function

Rem
Call the function CallMyFunction with a pointer to MyFunctionToPass as an argument and a regular String as an argument
End Rem
CallMyFunction(MyFunctionToPass,"This variable is passed via a function pointer!")


What happens here is that the program has to know what arguments the pointed to function takes! (Otherwise it, err, can't figure it out).
To do this, just have the necessary variables in the relevant places (where you have parentheses) as demonstrated above.


Edit:
Daniel explains it infinitely better (of course!) here:
http://www.blitzbasic.com/Community/posts.php?topic=48316#537481


Here is the above example, slightly modified to store MyFunctionToPass in a variable as well.
Global FunctionPointerVariable(In:String) 'This "dummy function" can be pointed to any function that takes one string as an argument



Function CallMyFunction(MyFunc(In:String) , VarToSend:String)
	Rem
	This function takes a function pointer as an argument.
	It expects that the function pointed to takes one argument , which is a String.
	
	To demonstrate the magic of function pointers , this function takes another argument , 
	which is the variable to pass along to the function it is sent in the MyFunct argument.
	End Rem
	Print "Calling a function passed as an argument..."
	MyFunc(VarToSend:String)
End Function

Function MyFunctionToPass(In:String)
	Rem
	Prints the In variable which is passed as an argument
	End Rem
	Print In:String
End Function


FunctionPointerVariable=MyFunctionToPass 'Set FunctionPointerVariable to point to MyFunctionToPass

Rem
Call the function CallMyFunction with a pointer to MyFunctionToPass as an argument and a regular String as an argument
End Rem
CallMyFunction(FunctionPointerVariable,"This variable is passed via a function pointer!")