Pointers

Started by
16 comments, last by Trienco 11 years, 1 month ago

Anyone have a good article on pointers? I understand them somewhat, but i don't get the full gist of them.

Advertisement

A pointer is just a variable that hold an address of a memory location, or if you prefer, the adresse of another variable.Every variables you create have an address in memory, the place where it is stored. In 32 bit OS, this address take up 32 bits, so, it's the same size as an unsigned integer. So if you take the address of a BYTE variable, wich is 8 bit, or 1 byte, it's address is still 32 bits width, hence the 4Gb memory limit on 32 bits OS.

Why is that usefull? Well it could be for many things. One of the most usefull thing you can do with them is to pass a pointer to a function, so it can modify it, instead of making a copy of the variable your passing to it. It's also usefull for passing arrays and structures to a function for the same purposes. For the case of structures, it is much efficient to pass an address as an argument, wich is only 4 bytes width, than making a copy of an entire structure of, say, 100 bytes in size.

Ex. using arrays:


void SumArrays(int *p1, int *p2, int *pResult){	for(int i = 0; i < 10; i++){		pResult = p1 + p2;	}}void main(){	int Array1[10];	int Array2[10];	int SummedArray[10];	// Fill arrays with random values	for(int i = 0; i < 10; i++){		Array1 = rand() % 1000;		Array2 = rand() % 1000;	}		SumArrays(&Array1[0], &Array2[0], &SummedArray[0]);}

Whitout pointers, this wouldn't be possible because the arrays are all local to the main() function, but using pointers allow us to use them outside the function into another one.

That's not the only usage of pointers but that should give you a good idea.

I would like to make a better examples using pictures but i don't feel i have the force to make some, it would take me hours.

Have a look here instead, it's pretty well explained.

A pointer is just a variable that hold an address of a memory location, or if you prefer, the adresse of another variable.Every variables you create have an address in memory, the place where it is stored. In 32 bit OS, this address take up 32 bits, so, it's the same size as an unsigned integer. So if you take the address of a BYTE variable, wich is 8 bit, or 1 byte, it's address is still 32 bits width, hence the 4Gb memory limit on 32 bits OS.

You should never assume that a pointer has the same size as an unsigned int as the C++ standard doesn't mention that at all, and is not true when you are running a 64bit app where the pointer is 8 bytes big. A pointer will have the size of the memory address space used and this can differ considerable from an integer on a platform.

Assuming that your pointer size is always 4 bytes can lead to some weird bugs when you try to port your application to an other compiler or subsystem that doesn't have 4 byte pointers.

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

Well i understand the basis of pointers. It's more the memory allocation for pointers that gets me.

Yes, that why i said on a 32 bits OS. I agree with you about this assumption, that's what sizeof if for, i forgot to mention it.

As for memory allocation, it's not very different than allocating variables or arrays normally on the stack, except you have to remember to free your memory after using it. That's why, as a rule of thumb, i always write the code to free memory at the same time i allocate it. I also like to use the SAFE_DELETE macros from directx to handle that stuffs, since it protect you from trying to free a null pointer using only one line of code, and also make sure to reset the pointer to NULL afterward.


//These helper macro will release or delete safely the passed object#define SAFE_RELEASE(x)	       if((x) != NULL){(x)->Release();(x) = NULL;}#define SAFE_DELETE_ARRAY(x)   if((x) != NULL){delete[] (x);(x) = NULL;}#define SAFE_DELETE_OBJECT(x)  if((x) != NULL){delete (x);(x) = NULL;}

What do you not understand about allocating memory using pointers exactly?

Those two programs does exactly the same thing


BYTE Array[1000];BYTE *pArray = &Array[0]; // pArray have the same address as the 1st element of Array// Those 2 lines of code does the same exact thingsArray[0] = 123;pArray[0] = 123;

// Dynamically allocate an array of 1000 bytes and store the address of the 1st element in pArrayBYTE *pArray = new BYTE[1000];pArray[0] = 123;if(pArray != NULL)	delete [] pArray;

orSAFE_DELETE_ARRAY(pArray)

The only difference is the memory.deallocation. You also have to use the star symbol when assigning value to a variable, but it's not necessary for arrays like in this example. Another small difference is the use of -> instead of comma for structure, but both does the same thing really.

It's good to think about how memory manager in operating system works. So you probably know that memory is broken into blocks, every block has some kind of it's header (sometimes called also bitmap), this header contains few data - whether block has been already allocated, and the size of the block.

Now imagine your memory, it might look like this (free blocks are unallocated, and lets say one '-' is a byte) - let's say that start is at address 0x10000000:


+----------------+------------+--------+------------+------------+
|      free      |#some#data##|  free  |#other#data#|    free    |
+----------------+------------+--------+------------+------------+

So it's clear to see that:

some data are at memory address 0x10000000 + 0x10 (or +16 in decimal)

other data are at memory address 0x10000000 + 0x24 (or +36 in decimal)

Now you want to allocate another 10 bytes of memory, so you call char* ptr = malloc(sizeof(char) * 10); (or in C++ call char* ptr = new char[10]) - what does it actually do? In short:

1.) It finds suitable free block (suitable = it is at least 10 bytes large, and better it is the smallest available that is at least 10 bytes large)

2.) It marks it used (possibly splitting it into 2 - because we wan't exactly 10 bytes of memory, not more)

3.) Returns address of that memory (e.g. pointer to that memory)

So, step 1:


+----------------+------------+--------+------------+------------+
|      free      |#some#data##|  free  |#other#data#|    free    |
+----------------+------------+--------+------------+------------+
^                             ^                     ^ 
Good, but larger than last    Too small             This one is good and smaller than 1st one :) we got a match

So we've found suitable block at address 0x10000000 + 0x30 (or +48 in decimal). It's time to go to step 2:


+----------------+------------+--------+------------+----------+--+
|      free      |#some#data##|  free  |#other#data#|###ptr####| f|
+----------------+------------+--------+------------+----------+--+
                                                    ^
                                                    Address 0x10000000 + 0x30

We just marked the block used and it's size as 10 bytes. Of course rest of bytes must form new block, that is still free.

Step 3:

Memory manager just returns pointer to address 0x10000000 + 0x30 to us.

So now we can perform any reads/writes to memory we allocated.

But what happens when we want to free or delete that memory? It's a lot more simple, call free(ptr); or in C++ delete [] ptr; will just do following:

1.) Marks block in it's header as unused (e.g. free)

2.) Finds whether we can merge that blocks with free block before/behind it.

Let's use our scheme again, we want to deallocate other data, e.g. deallocate location 0x10000000 + 0x24.

Step 1 - mark it as unused, so it'll change into this right away:


+----------------+------------+--------+------------+----------+--+
|      free      |#some#data##|  free  |    free    |###ptr####| f|
+----------------+------------+--------+------------+----------+--+

Now you see that those 2 free blocks could be merged - why merge? What if we would like to allocate like 20 bytes on memory? Those 2 blocks together are 20 bytes of memory, but right now they're just 8 bytes and 12 bytes, so memory manager would say that both are larger than memory we want to allocate. So right now we just go through memory and merge free blocks together, resulting in this:


+----------------+------------+--------------------+----------+--+
|      free      |#some#data##|        free        |###ptr####| f|
+----------------+------------+--------------------+----------+--+

And it's done.

This is how memory manager in operating system works (of course simplified a bit) - you should get idea about what's behind memory allocation/delocation from this. Basically the pointer just stores address of where your allocated block begins, you have to remember size of allocation on your own.

My current blog on programming, linux and stuff - http://gameprogrammerdiary.blogspot.com

Yes, that why i said on a 32 bits OS. I agree with you about this assumption, that's what sizeof if for, i forgot to mention it.

As for memory allocation, it's not very different than allocating variables or arrays normally on the stack, except you have to remember to free your memory after using it. That's why, as a rule of thumb, i always write the code to free memory at the same time i allocate it. I also like to use the SAFE_DELETE macros from directx to handle that stuffs, since it protect you from trying to free a null pointer using only one line of code, and also make sure to reset the pointer to NULL afterward.


//These helper macro will release or delete safely the passed object
#define SAFE_RELEASE(x)	       if((x) != NULL){(x)->Release();(x) = NULL;}
#define SAFE_DELETE_ARRAY(x)   if((x) != NULL){delete[] (x);(x) = NULL;}
#define SAFE_DELETE_OBJECT(x)  if((x) != NULL){delete (x);(x) = NULL;}

Sadly does macros don't safe you from pointers cleaned up elsewhere in debug builds when they have 0xDEADBEEF, 0xFEFEFEFE, 0xCDCDCDCD, 0xFEEEFEEE, 0xBAADF00D or other patterns that indicate a heap cleanup somewhere. Most game engines that overwrite the default memory allocations and placement new will still use similar patterns in debug builds to tell you what's going on. You should get to know these patterns btw as they can help you debug memory issues, here is a link about the CRT Heap in Win32 http://www.nobugs.org/developer/win32/debug_crt_heap.html

Worked on titles: CMR:DiRT2, DiRT 3, DiRT: Showdown, GRID 2, theHunter, theHunter: Primal, Mad Max, Watch Dogs: Legion

While not directly related to the question, I suggest also you to take a look into std::vector (and other std:: containers). Std::vector is a handy solution for most of the cases where you need array allocation, deallocation (or even resizing). Cleaner / shorter code, less strange memory bugs, less memory leaks, all that a programmer needs.

Cheers!

Here are a couple resources:

http://www.cplusplus.com/doc/tutorial/pointers/

http://www.learncpp.com/cpp-tutorial/67-introduction-to-pointers/

A pointer is simply a variable which holds a memory address. Because of this, all pointers are the same size on your system. If you have a pointer to an object which is eight bytes and a pointer to a type of object which is four bytes, they both are the same size. Pointers are useful because they allow us to operate on the same data throughout our program. Here's an example:


void add(int op1, int op2, int result) {
  result = op1 + op2;
}

In this, we don't actually change the value of result. The function creates a copy of result whose value is changed to the sum of op1 and op2. What if we wanted to change the actual value of result? If we have the memory address of our original result variable, then we can change the value at the memory address (thus changing the value of result):


void add(int op1, int op2, int * const result) {
  *result = op1 + op2;
}

This changes the actual value of result for us. However, we now have new syntax. Some noticeable changes in syntax here are how we declare a pointer. To declare a pointer, we use the syntax:


type * name;

We specify the type of variable name points to by changing type. You may be wondering why we have the dereference operator (*) in-between the type and name. This tells the compiler that we are creating a pointer. Later on, we will discuss the dereference operator. When we create a pointer and use (*), it is completely unrelated to the actual dereference operator.

To assign a pointer a memory address, we use the reference operator (&). This returns the memory address or the variable following it. Examine:


int normalVariable = 5;
int * pointerVariable = &normalVariable;

Here, we create a normal integer variable (normalVariable). We then create a pointer to an integer (pointerVariable) and assign it to the memory address of normalVariable.

If we look back at the first pointer function example, we have this line of code:


*result = op1 + op2;

This syntax may seem confusing at first. When we created a pointer, we used the dereference operator (*) to specify that a variable was a pointer. In reality, when we use (*) to create a pointer, it is completely unrelated to the real use of the dereference operator (*): to dereference. We use the reference operator (&) to return the address of a variable, and thus we use the dereference operator (*) to get and set the value of a variable. When we put the dereference operator (*) before a pointer, it means that we're returning the value of the pointer. This can also be used to set the value of the pointer, as seen above.

This means that in the code above, we're setting the value of result to the sum of op1 and op2.

Pointers also allow us to create memory on the heap (a large area of memory set aside for dynamic allocation). To do this, we use the new and delete operators. This part of pointers is very complicated, so I recommend you read about The Stack and the Heap along with Dynamic Allocation With Pointers.

You might now be wondering: When would I ever use pointers? This is a valid question, and many tutorials don't explain it well. So, I'll use an example which I deal with right now: Windows.

I use an API called SFML for game development. SFML is very object oriented, so you create a RenderWindow object. Many components of my game have to draw to the window. I can't pass the window by value, because then the components are drawing to a RenderWindow copy, located in separate memory. I use pointers so that they all have access to the same RenderWindow.

(You can PM me for the source code of my State Machine, which uses pointers so that every state can draw to the same window.)

Cheers :)!

I'm a game programmer and computer science ninja !

Here's my 2D RPG-Ish Platformer Programmed in Python + Pygame, with a Custom Level Editor and Rendering System!

Here's my Custom IDE / Debugger Programmed in Pure Python and Designed from the Ground Up for Programming Education!

Want to ask about Python, Flask, wxPython, Pygame, C++, HTML5, CSS3, Javascript, jQuery, C++, Vimscript, SFML 1.6 / 2.0, or anything else? Recruiting for a game development team and need a passionate programmer? Just want to talk about programming? Email me here:

hobohm.business@gmail.com

or Personal-Message me on here !

This topic is closed to new replies.

Advertisement