C++ Pointers
Some notes on C++ Pointers…
Opinion
The way you learn pointers can set you back years on your path to C++ proficiency. It is my firm belief that I learned pointers incorrectly which is why I struggled with the language for the longest time. Well…the struggle is forever when it comes to C++ anyway!
“Uh what is pointer?”
On the surface, pointers are variables that store memory addresses. You declare one by simply slapping an asterix after the type but before the identifier like int* dumbptr;
The placement of the asterix can be defined by the coding standard for your project. I choose to put right after the type declaration because it reads like a type-modifier i.e. it’s not just an int but a pointer-to-an-int. There’s also another reason which is avoiding confusion with dereferencing. More on that in a few paragraphs.
So why would we want to use a pointer? What’s the benefit of storing memory addresses? It comes down to performance; working with pointers cost less than working with the objects they point to. As an analogy, it’s easier/cheaper for me to send you a link to a big document on the web than to send you the big document itself.
To understand the cost benefit, it’s important to understand how much space primitive data types take. See [[C++ Data Type Sizes]] for some details. Depending on the architecture and other implementations, pointers take up a certain number of bytes. On a 32-bit system, a pointer takes up 4 bytes. A double would take up 8 bytes. A class or struct with 6 ints would take up 24 bytes. You can see it would be prudent to use pointers instead of heavier data structures or types simply because they take up less space. This is particularly important when passing values to a function where they get copied. It’s cheaper to copy a pointer than a bulky data structure.
Pointers are variables that store memory addresses. Because they can be relatively smaller, they are more efficient to store and copy.
So, why not store memory addresses as strings or some numeric value instead? Because you would not know what type of object was residing in the memory address with simply the address itself. The pointer’s type is the type of the content it is pointing to!
However, just declaring a pointer does not mean it points to valid memory. It might point to memory you can’t access and crash with segmentation fault. It might point to actual data and lead to erroneous results that are hard to debug. In other words, pointers need to be initialized before they can be safely used. If a pointer is just declared and not initialized, it will not be possible to identify if it was initialized since an arbitrary memory address will be assigned. For this reason, it is highly recommend to initialize your pointers as soon as you declare them.
Pointers are initialized with the assignment operator. Pointers can be initialized to:
- memory addresses of variables of the same type
- other pointers of the same type
- a null pointer
Memory addresses can be retrieved from variables using the address-of operator which is ampersand. Below is an example of the above points.
int alpha = 23;
int* omega = α // Set to memory address of alpha
int* psi = omega; // psi points to the same address as omega
int* later = nullptr; // Set to null ptr, can be re-set later
If you don’t know what to initialize a pointer to, set it to nullptr. This way it can compared against nullptr or 0 or null-checked before being used and errors can be avoided.
Whenever you want to use the contents of a pointer, you dereference it.
When you “dereference” a pointer, you can get the value or object stored at that location in memory. What might make it a little confusing is that the dereference operator is ALSO an asterix. So slapping one in front of just the identifier like this *dumbptr
(without a type declaration preceding it) means we are dereferencing it. Once dereferenced, you can use it like you would a normal variable.
int numLegs = 4;
int* dumbptr = &numLegs;
int result = *dumbptr++; // result is 5
It is important to note that dumbptr++ is not the same as *dumbptr++. The latter is incrementing the dereferenced value while the former is incrementing the pointer itself, effectively changing what it’s pointing to!