Pointer Tutorial Request

BlitzMax Forums/BlitzMax Tutorials/Pointer Tutorial Request

The forum is locked. These are the original blitzbasic.com forums, kept so they can still be read. No new topics, replies or edits can be made here. To join in, head over to the Blitz3D+ forums.

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.