Page 1 of 4
In a modern language the argument is that you should never need to get down and dirty with pointers because to do so simply reveals that you are thinking at too primitive a level and in severe danger of meeting the real hardware that underpins everything.
While this is true there are still times when reality pokes through the abstraction and you do have to interact with the hardware in ways that can only be achieved using pointers. In addition there are all those wonderful C and C++ programs that use pointers and need to be converted to something more polished and safe.
In short, you certainly should know about pointers even if hopefully you never actually make use of them in a real production application. If you do find that pointers are essential to something low level that you are trying to implement then it is also important that you know how to implement them in as “safe” a way as possible.
Reference, pointer, address
First we need to clear up some confusion that exists between the three terms “reference”, “pointer” and “address”.
Starting with the most basic “address” means the numerical address where something is stored. Most computer hardware assigns addresses using a simple incrementing scheme starting at some value and continuing to some other value, e.g. 0 to 1000, and each address corresponds to a memory location capable of storing some data with a given number of bits.
This simple idea has become increasingly complicated as hardware has developed and with the introduction of hardware memory mapping. Now the address that data is stored at can change without the data being moved due to the use of address translation hardware. Even if the hardware doesn’t get in the way, addresses change while an application is running because the operating system or a garbage collection system often moves things around to make things more efficient.
The point is that while once the address of something was a fixed and reliable way of accessing it, today it is surrounded by a range of problems.
The “pointer” is the step in abstracting the idea of an address. At its most basic a pointer is simply a variable that can be used to store the address of something. You can use a pointer to gain access to the data item pointed at – a process called dereferencing. You can also subject the pointer to arithmetic operations that move its location in the “store”.
That is, if there are a number of objects stored one after the other in the store you can perform pointer arithmetic to change the object that is pointed at. Pointer arithmetic is the reason that many programmers like pointers but it is also the reason why pointers are dangerous.
A mistake in the pointer computation can result in it pointing somewhere it shouldn’t and the whole system can crash as a result. There really is no reason why a pointer shouldn’t be abstracted away from the basic idea of an address but in most cases pointers are just wrappers for machine addresses and this also raises the question of what happens if the system does something that changes the address of an object. More of this later.
Finally we reach the highest point of abstraction of the address idea in the form of a “reference”. A reference is just that – a reference to an item of data or an object. If this sounds like a pointer there is a sense in which this is true but the key idea is that you can’t manipulate a reference directly.
That is, while there certainly is pointer arithmetic there can be no reference arithmetic.
All you can do with a reference is to pass it to another user or dereference it and access the data that it references. As in the case of abstract pointers there is no reason why a reference shouldn’t be abstracted away from the underlying machine address but in most cases, and C# in particular, a reference is just a wrapper for an address. In future implementations, however, a reference could be implemented as a handle to a table, to another table, to a resource index and so on, until finally the hardware converts the reference to the address of an actual data object.
The point is that while we all know that a reference, and for that matter a pointer, in C# is simply a wrapper for an address this is a detail of implementation and not something you should rely on or make use of.
In C# we make use of references all the time in the form of variables that have been assigned any reference type.
For example, if you create an instance of a class then the instance variable isn’t an object but a reference to an object of the appropriate type. That is, after:
MyClass MyObject = new MyClass();
then MyObject is a reference to an instance of MyClass. In practice it contains the address of the instance but as already explained you shouldn’t rely on this form of implementation.
This storing of a reference in MyObject rather than a value is most clearly seen if you create another variable which references the same object as MyObject.
MyClass MyObject2 = MyObject;
Of course, we now don’t have another complete independent copy of the object referenced by MyObject, instead both variable reference the same instance. If you make a change to the instance using MyObject2 say then the same changes will be found via MyObject. The difference between a reference and a value basically comes down to this assignment semantics – does assignment produce a copy of the original data/object. If it does then we have value semantics; if it doesn’t we have reference semantics.
This behaviour that assignment doesn't create a new copy of anything is essentially the key idea at the root of what makes a pointer different. What gets duplicated in an assignment is the pointer/reference not the object.