[26/11/2019] GUIDE: Pointers made simple

Preface and history

Let me start this by explaining a little bit of history. In the olden days of code, C was used as a systems programming language, and shortly after Java came along. The developers of Java observed the most common cause of programming errors in C. One of these was POINTERS. Java chooses instead to flat out replace pointers, and replace them with REFERENCES(At the implementation level references are usually pointers though). This is also where people often talk about the Garbage Collection in Java. It's handy sometimes, and linked here is a discussion about it. This blog post will not be talking about references in Java or comparing it to them. It will be talking about POINTERS and specifically POINTERS for C. I will provide some basic examples of its usage too.

Contents

What is a pointer anyway?

A pointer is a variable that stores an address. An address is just an integer. This means you can do addition or subtraction on it as well. To find the address of a variable in C you can use the below code.

void* the_data;
i = 6;
the_data = &i;

This gets an integer corresponding with the address of variable i. This is useful because you can then pass this address into a function instead of copying the data to the function(which for some cases would be inefficient). The void* used to declare the pointer is notable. Declaration of pointers is as follows >TYPE< * >VARIABLE_NAME<;. Pointers have types which should be noted. We will discuss the significance of this when we come to pointer arithmetic. So now we can create a pointer, and get the address of a variable and assign it to the pointer. Now we need to consider how we get the value of a variable given a pointer.

int the_value = *the_data;

This allows us to DEREFERENCE a pointer. In laymans terms, this means we go to the address in memory and see what the value is. Remember pointers are just storing addresses, so this operation just gets the value from the address in that variable.

Memory allocation and pointers

Pointers tend to get confused when it comes to memory allocation. We will compare creating an array in the stack and on the heap. The stack is as it sounds, a First In, Last Out system(FILO). Basically you put variables on it, and when the function where the variable was defined ends, your variable no longer exists. This makes it dangerous and difficult to return large amounts of data from a function call. Instead, you can allocate memory on the heap which is just memory you are free to allocate as you see fit. You have much more memory in heap than in stack, so heap usage is encouraged. Below is an example of using a pointer with memory allocation and consequently freeing it.

void* the_data = (void*) malloc(4*sizeof(int));
*buffer=4;
free(the_data);

What the above code does, is declare a pointer the_data and assigns it to (void*) malloc(4*sizeof(int)). The (void*) takes the pointer it returns and casts it to the void* type. The malloc is a function provided in C's Standard Library, and it takes a number of bytes to allocate as a parameter. For this example I have allocated enough space to store 4 integers. The sizeof() is a function that returns the size in bytes of either a variable or type. The 2nd line, *buffer=4 dereferences the pointer and sets the value of what is dereferenced to be 4. Remember, POINTERS ARE JUST VARIABLES STORING ADDRESSES. What malloc returns is an address to where the allocated memory is, and the pointer lets you modify that memory. The final line just frees the memory up in the heap. If we didn't include it, the memory would be locked up and unusable elsewhere in our code. This is an error and is called a memory leak. They are often noticeable in programs that will run for a long time, since the heap size is limited. The below code is an example of the same program, except using an array to use the stack instead.

int the_data[4]; 
the_data[0]=4;

You can also use pointers with stack variables. Here's the above again, except using pointers for assignment.

int the_data[4]; 
void* the_pointer = (void*) &the_data;
*the_pointer=4;

You will notice I do not call free, there is no reason to as this pointer points to a variable on the stack, not on the heap. Returning this pointer as a value is very dangerous, as the variable is no longer defined outside of the function, and so anything could be at that point in memory.

Pointer arithmetic

POINTERS ARE JUST VARIABLES STORING ADDRESSES. ADDRESSES ARE JUST INTEGERS. When you consider this, pointer arithmetic makes sense. So you can increment a pointer by 1 or decrement it by 1. This is where some errors occur due to off by one errors. The below code demonstrates assigning all variables in an array to 4.

int the_data[4]; 
void* the_pointer = (void*) &the_data;
*(the_pointer)=4;
*(the_pointer+sizeof(int))=4;
*(the_pointer+sizeof(int)*2)=4;
*(the_pointer+sizeof(int)*3)=4;

An observent reader will notice, I do not increment by 1. I increment by the size of an integer. This is because pointers when you add 1 will increment in bytes depending on the type of the pointer. Since it is a void* or a null pointer, it increments in 1 byte. A char* pointer increments in 1 byte since char is a 1 byte variable. Depending on the machine you're running on, an int* pointer would be a 4 byte variable. The above code is simplified below by going up in units. Remember the units are only determined based on the size of the pointer's type.

int the_data[4]; 
int* the_pointer = (int*) &the_data;
*(the_pointer)=4;
*(the_pointer+1)=4;
*(the_pointer+2)=4;
*(the_pointer+3)=4;

As you can see, by making it an integer pointer, I can now increment or decrement the pointer by 1 unit to get to where I want to be in the pointer. The easiest way to think about this I find, is to think of the pointer type as the unit you use for pointer arithmetic. I generally prefer using void* instead of type* simply because then you know how many bytes forward or backwards you're moving.

Pointers and structs

Pointers have some syntax for structs I'll talk about here. Below is a demonstration of this.

struct Vector2D {
    int x;
    int y;
};
...
// In a function
struct Vector2D point;
void* pointPointer = &point;
int xPosition = *(pointPointer).x;
int yPosition = pointPointer->y;
...

The above code demonstrates an example for using structs and pointers. It works about how you expect it. The pointer stores the address of the point variable. When getting a value from it, we have 2 choices of syntax, they both do the same thing behind the hood, though the latter tends to be much more common as it makes source code shorter, and is easier to read.

Summary

I hope from all of this, you take home 1 particular thing. Pointers are just variables that store addresses. Addresses can thus be manipulated to allow for a lot of nice behaviour that makes C so powerful at a low level. If I made an error in this writing, please message me or email me.

<Latest><Next><List><Previous><Oldest>
Take me home!
Bad for health, Good for education