Clarify pointers?

Started by
14 comments, last by Tac-Tics 14 years ago
I am having a hard time fully understanding the reason behind pointers. I understand that pointers "point" to the memory address allocated by variables. I just don't seem to understand why they are used and when it is best to use pointers. Can someone please help clarify the purpose of and when its best to use pointers? I think I'm getting confused because of the whole memory address idea and don't know why I would want to know/use the memory address.
Advertisement
So pretend you have a game with a player, and each of your graphics, sound, and physics subsystems needs to know where this player is. Here's the question: do you want to send each subsystem a copy of the player? Or do you want to give each a common way to reference the same player?

If you want to do the latter, you need pointers (or references). Think of pointers (and references) as a way to transmit access to a particular instance of an object.

Consider the example above. If you don't use pointers, each subsystem gets its own copy of player. Now what happens when physics decides to move player because he's falling under the force of gravity? physics has access only to its own copy of player, and any changes it makes to this copy will be internal to physics; no other subsystems will know about them. If instead each subsystem were given a pointer to a single player, any changes made would be visible to every subsystem.

This is one use for pointers. Another use is to avoid copying expensive objects, since passing a pointer to an object avoids copying the entire object.
A pointer is a special variable that can point to other variables / memory addresses. So, like an integer variable contains an integer, a pointer variable might contain an address where an integer resides.

Why use them instead of normal variables?

Conceptually, normal variables are like little houses - each one maps out to a specific address in memory. But sometimes you don't want to declare a variable for every instance of certain types of data, such as structures. Maybe you plan on having 10 million of them in memory, and it would be crazy to declare 10 million variables, right?

So you declare one pointer which can point to any 1 of those 10 million memory locations at any given time. And you use that pointer in your loops or when you need to do something with those records, such as searching through them.

There are other ways to accomplish the same thing, really. Just not as good. For example, a gigantic array with 10 million elements might be used instead of a more complicated pointer system. However, it would end up being much less flexible, (consider the chore of deleting the 5 millionth record in an a giant array), and possibly more complicated, harder to debug, maintain, etc...

Pointers are gud.
--- "A penny saved never boils."
There are many reasons to use pointers. One of them is sharing.

If you want to store the same object in two different vectors, you cannot use the type std::vector<Player>, because each vector will store its own objects.
std::vector<Player> players;players.push_back(Player("Joe", 100, true));std::vector<Player> winners;winners.push_back(players[0]);   // whoops, now there are 2 Joes!

Instead, you have to use std::vector<Player*>. Then both vectors can share the same Player instances which are stored "elsewhere", typically on the heap.
std::vector<Player*> players;players.push_back(new Player("Joe", 100, true));std::vector<Player*> winners;winners.push_back(players[0]);   // both vectors share the same Joe......delete players[0];   // ...which sometime later has to be destroyed manually

Since manually destroying is tedious and error-prone, you would normally use std::vector<boost::shared_ptr<Player> > or something similar.

Another reason for using pointers is subtype polymorphism. A std::vector<Weapon> can only store weapon objects, while a std::vector<Weapon*> can store pointers to weapons or bullets or swords or doomsday devices or...

Most other uses of pointers are so low-level that you normally use them "without knowing" through libraries. For example, std::vector<T> internally stores a pointer to a dynamically allocated array of Ts. If there were no std::vector, you would have to do the dynamic allocation yourself.
Ariste, thanks for the explaination. It was most helpful and easy to understand. Seems every book I've looked at so far has the same way to explain them and is confusing. Using the game reference I think made all the difference. That's how I'm trying to go about with my learning. Now when I play games, I'm thinking "how might that action been programmed".
The real problem with pointers is that you don't really need them in C++. They are the blood of C, but C++ supports references, which diminishes their usefulness. It's easiest to understand pointers if you're working in pure C.

Your computer, at a fundamental level, only understands two datatypes: integers and floats. Everything in your computer is an integer or a float. All operations (arithmetic, logic, copying, etc) work only on these two types.

In C, you can create more complex data types using structs. A struct allows you to group a number of integers and floats together. This is convenient for the programmer because we only have to keep track of one object instead of all its parts. (You drive your "car"... you don't drive your "chassis, motor, seats, dashboard, windshields... etc").

The first obvious problem comes about with functions. Functions in C are pretty naive. You can only pass integers and floats to a C function1.

When you pass an integer or a float to a C function, it copies the value onto the stack2. If you were to pass a structure, it would need to copy the whole structure to the stack. This can't be done in a single operation by the underlying hardware, and so C disallows it. (Remember, C was considered to be unreasonably slow when it first came out).

So if passing a structure is disallowed, how do you pass it? The answer is you don't copy the struct to the stack. You copy a pointer to the struct. A pointer is just an integer with a special operation: it can be dereferenced (or "followed"). And since a pointer is just an integer, it can be passed as usual.

Let's talk about dereferencing. What does that even mean? A real-world analogy would be calling someone's phone number. When you make a new friend, they might give you their phone number. They scribble it down on a piece of paper and you slip it in your pocket. (The person is the structure. The phone number is the pointer.) Later, if you want to know their birthday, you can call them on the phone and ask. (To call the number is to dereference the pointer).

An absurd alternative would be: you make a friend, he CLONES himself. You take the clone home with you. And later, if you wanted to know his age, you could just ask him (without calling him).

Given you had the power to clone somebody, both solutions are viable. The first option, though, is undoubtedly easier.

There is one other small difference. When you clone someone, the clone and the actual person need not be in sync. If the clone breaks his leg, the actual person will not feel it. If you see the clone has a broken leg, it doesn't tell you anything about how your actual friend is doing.

This phenomenon -- that changes to the cloned person don't affect the original -- is the essence of pass-by-reference versus pass-by-value. The telephone number scenario is pass-by-reference. The clone is pass-by-value. Both are useful in different circumstances.

In particular, Pass-by-reference, is useful for communicating to the caller. C only supports one return value (and again, it has to be an integer or a float). But what about functions that need multiple return values? The most common solution is to pass in extra parameters by reference, and then modify them in the function. After the function returns, the modifications have taken place on the actual object (not on a clone), so in effect, you have passed information from the function to the caller.

Pointers are actually used "under the covers" for a lot of programming techniques. In modern languages, the use of pointers is almost completely invisible. But never-the-less, they play a huge part in everything you do. (C++ is a weird language because it covers up many uses of pointers while leaving other users exposed.)

Some things you may not have known pointers are used for:

* Passing by reference (see above). This includes "reference paramters" in C++ and how all objects are passed in Java. Most dynamic languages (Python, Ruby) pass exclusively by reference. Even if those languages don't expose pointers, that is always how they are implemented.

* Dynamic memory allocation. In C, when you declare a variable, space is allocated on the stack for that variable. The downside of this is that those variables are destroyed when the function returns. When you want an object to live longer than the function that creates it, the usual technique is to allocate the memory on the heap with malloc (new in C++). Malloc returns a pointer to an area of memory and says "ok, you do what you need to do with this memory, but remember to call free (delete in C++) when you're done with it!".

* Arrays. In C, arrays are just a thin veil over pointers. A char[] is essentially the same data type as char*. When you do str, you can always rewrite it as *(str + i), which means "dereference the value that's i addresses down from str".

* Structs. Behind the scenes, structs are actually implemented with pointers, exactly the same as arrays!

One final use of pointers that is way beyond the need of game programmers is hardware manipulation. The way most computers interact with hardware is through "memory mapped IO". What this means is that certain addresses in memory don't manipulate your RAM, but instead, are linked to IO devices. For example, there is a block of memory which directly affects your video card (although, only your OS has access to this memory). Without pointers to directly manipulate specific regions of memory, there would be no IO ;)

I hope that helps a bit!

1 - This is not true in modern dialects of C, but I don't know of any C library that uses this functionality.

2 - This is not strictly true for floats on x86, but it's conceptually equivalent to what I said.
Quote:Original post by Tac-Tics
* Passing by reference (see above). This includes "reference paramters" in C++ and how all objects are passed in Java. Most dynamic languages (Python, Ruby) pass exclusively by reference. Even if those languages don't expose pointers, that is always how they are implemented.
I can't speak for Java, but C# actually passes references by value by default. If you passed a reference into a function and in that function you pointed the reference to a new thing the thing that called the function would still point to the same thing it did originally. You can pass a reference by 'ref' so that if you change it in a function it will change outside the function as well. To modify your clone analogy it would be like being able to change which clone gets which phone number. Kind of. The analogy breaks down at this point.

Quote:* Arrays. In C, arrays are just a thin veil over pointers. A char[] is essentially the same data type as char*. When you do str, you can always rewrite it as *(str + i), which means "dereference the value that's i addresses down from str".
Coincidentally, I'm pretty sure the standard states that str is actually transformed to *(str + i). This makes i[str] completely legal code that happens to do the same thing as str.

Quote:* Structs. Behind the scenes, structs are actually implemented with pointers, exactly the same as arrays!
On the stack neither are implemented with pointers. The compiler can hard-code the locations just like normal variables. If you mean to imply that in
int main() { int n; }
that n is implemented using a pointer you would probably be in the minority.

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Quote:Original post by nobodynews
Quote:Original post by Tac-Tics
* Passing by reference (see above). This includes "reference paramters" in C++ and how all objects are passed in Java.

I can't speak for Java, but C# actually passes references by value by default.

It's the same in Java, references are passed by value. There is no pass by reference in Java.
Quote:Original post by nobodynews
I can't speak for Java, but C# actually passes references by value by default.


In C#, by default, objects created from classes are passed by reference and objects created from structs or primitives are passed by value. The ref keyword works just like the reference operator in C++. The tricky part about ref is when you use it on a reference-type object, you get a reference-to-a-reference.

To make a better analogy, a reference-to-a-reference is like giving someone the page and line numbers that their phone number appears on in the phone book.

Quote:
Quote:* Structs. Behind the scenes, structs are actually implemented with pointers, exactly the same as arrays!


On the stack neither are implemented with pointers.


You misunderstood me. When you have a struct like this:

struct Coordinate{  int x;  int y;  int z;}


The block:
Coordinate c;c.x = 1;c.y = 2;c.z = 3;


Will get translated to this:

Coordinate c;*(&c + 0) = 1;*(&c + 4) = 2;*(&c + 8) = 3;


(Slight abuse of notation, so that pointer arithmetic assumes the datasize is 1 byte). Of course, that's handled internally in the compiler, but the point stands: an element of a struct is the dereferenced value of pointer plus an offset.

Quote:Original post by nobodynews
It's the same in Java, references are passed by value. There is no pass by reference in Java.


I'm not talking about passing the references. I'm talking about passing the objects. In the end, everything is passed by value, be it the actual object or a reference to the object.

In Java, all objects are passed by reference. All primitive types (ints, booleans, etc) are passed by value.

In languages like Python and Ruby, even ints and booleans are passed by reference -- but they are immutable objects in those languages, so no changes can propagate up the stack anyway, because no changes to those objects are ever allowed. So from the programmer's viewpoint, they work very similarly to Java.
Quote:
You misunderstood me. When you have a struct like this:

struct Coordinate
{
int x;
int y;
int z;
}



The block:

Coordinate c;
c.x = 1;
c.y = 2;
c.z = 3;



Will get translated to this:

Coordinate c;
*(&c + 0) = 1;
*(&c + 4) = 2;
*(&c + 8) = 3;



(Slight abuse of notation, so that pointer arithmetic assumes the datasize is 1 byte). Of course, that's handled internally in the compiler, but the point stands: an element of a struct is the dereferenced value of pointer plus an offset.

This is just an implementation detail; it's not behavior described by or mandated by the standard (relevant chapter is 5.2.5).

Similarly, even on a specific (nontrivial) implementation it is unlikely that this will always hold everywhere even if it holds anywhere, especially given modern optimizers. In general you cannot predict how any given C++ statement or code fragment will be translated to machine code.

This topic is closed to new replies.

Advertisement