Maybe it would help to see a sample block of memory…
Address Data
0x0000 0
0x0004 10
0x0008 16
0x000C 0
0x0010 254
0x0014 13
A pointer is a memory address!
In this fictional example, you could have a pointer which contains the value 0x0008.
int * myPtr = 0x0008; //totally fake, setting it to point to address 0x0008
If you print out the pointer, you’ll print its address and get 0x0008.
cout << myPtr; //output: 0x0008
If you dereference the pointer and print it out, you’ll get the DATA contained at the address 0x0008:
cout << *myPtr; //output: 16
Your pointer is also a variable in memory with its own address. The memory block looks like this:
Address Data
0x1234 ???
0x1238 ???
0x123C 0x0008 //myPtr
0x1240 ???
0x1244 ???
myPtr has its own address stored at 0x123C. So, if you printed the address of your pointer…
cout << &myPtr; //output: 0x123C
You’d get the address of the pointer itself rather than the address of the data its pointing to.
There’s also this neat stuff called “pointer arithmetic”. It’s kinda dangerous if you don’t know what you’re doing, but it can be used for some cool tricks.
Looking back to our data, let’s say that we created an array of integers, starting at memory address 0x0000 and our array length is 6 elements. We know that an integer is 4 bytes long. If our pointer ‘myPtr’ is pointing to address 0x0008 and we wanted to go to the next array element, we could just do the following:
myPtr++; //0x0008 becomes 0x000C
Notice how we added 4 to the address value the pointer is pointing at? We automatically know to add 4 bytes to the address because that is the byte length of an integer. If our pointer was a pointer to a class which was 12 bytes in length, we’d add 12 instead to the address value.
Now, if we dereference ‘myPtr’ and print out its data… we get the next element in memory!
cout << *myPtr; //output: 0
The order of operations with pointers matters a lot. What do you think the difference between these two lines of code are?
(*myPtr)++;
*(myPtr++);
The first line adds the value 1 to the data pointed to by the pointer.
The second line adds 4 to the address pointed to by the pointer.
You can also assign pointers to each other. This is actually very common!
int * myPtr = 0x0008;
int * myOtherPtr = 0x000C;
cout << *myOtherPtr; //output: 0
myOtherPtr = myPtr;
cout << *myOtherPtr; //output: 16
So, why use pointers?
A pointer points to an address in memory. The byte length of a pointer is ALWAYS 4 bytes (on a 32 bit system), regardless of what data type it points to. If you have a pointer pointing to a class which contains dozens of variables of various sizes (strings, ints, floats, etc), the size of the class may be hundreds of bytes in size. If you have a function, you can either pass data in by value or by reference. If you pass in by value, you increase your call stack size by the size of your data passed in. This can both slow things down, and cause data to be copied unnecessarily. If you pass data in by reference, the data size passed in is always 4 bytes.
Another example: Suppose you have a linked list of objects. Every time you use the “new” keyword, you get a pointer to a newly created objects memory address. With linked lists, these memory addresses may be scattered all over memory. A pointer can be used to traverse each element in the list, sequentially, like so:
Node* ptr = MyList.Front;
while(ptr != null)
{
cout << "Data: " << ptr->Data << endl;
ptr = ptr->NextNode;
}
I won’t get into this much, but you can also have pointers to pointers:
int **myPtrPtr;
And you can also have function pointers, which UE4 calls “delegates”.