diskodev

DESIGN & DEVELOPMENT AGENCY

Pointers 101 - Part 1

(This is a first post of a three part series giving an introduction to pointers in C/C++)

Pointers are objects that contain the address of another object. Pointers can contain the address of any datatype in C/C++. Pointers are mainly used for dynamic memory allocation, representing dynamic structures, recursion among various others. Pointers are sometimes tricky if you do not get your head around it. You can easily shoot your foot when using pointers. Program crashes and memory overwrites are some common problems that occur with incorrect pointer usage.

In part 1, we shall be looking at the basics. Pointers to arrays and strings & pointer arrays will be covered in part 2. Advanced topics like dynamic memory allocation, data structures etc will be covered in part 3.

Please do read the comments in the code samples given in the post. Lots of explanations are contained within it.

Do we need to know about pointers?
Although no modern languages and frameworks support pointers (at least explicitly), most concepts in the architecture are represented and implemented using pointers. Memory management, Representation of File Systems, Databases, Data Structures etc are some places these concepts are used extensively. Even if you are going to work only with high level languages, it is essential to know about pointers. They help you understand the internal workings better.

Pointers and Modern Computer Architecture
First, to understand how pointers work, we need to understand how memory is represented in modern computer architecture. Memory is abstracted and represented as an array of consecutive storage locations.  Variables, created by a program, are stored in these locations and each one of them has an address associated with them. In order to access the value stored in a memory location, we need to know its base address, which is nothing but the starting address. Using the base address along with the size of the datatype we can access the value of the type.

Pointers provide a level of indirection from the actual physical representation and helps you abstract from it. It can be best explained by the following illustration:

image001.png

Here, p is a pointer which is pointing to some object o ie, the address of o is contained in p. We can use p (after indirection) wherever o is needed. During the run of the program, p can stop pointing to o and can point to some other variable. This level of indirection not only has its uses, it also brings in performance gains and gets you closer to the underlying hardware.

In C/C++, you can declare a pointer by the following synta

        datatype *pointer_name;

Here, datatype can be a built-in or a user defined type and pointer_name is the variable name you decide to give. We can make the pointer point to a variable using the & (address-of) operator.

        int index = 15;
        int *idx_ptr = &index;

We have an int variable index which has a value 15. We then create a pointer and make it point to index by taking its address. The & operator only applies to objects in memory. This is so because objects not in memory do not have an address. Hence you cannot use the & operator to constants and expressions.

Now you can use indx_ptr whever an index is needed. For example, if you want to increment index, you could giv

        *idx_ptr += 1;

Pointer Expressions
The blow code sequence shows how to declare various pointers and how to use them in programs.

When you run the above code, you should be getting a similar output as shown below (Except for the addresses)

        Address of i - 0x22ccd4
        Address of i - 0x22ccd4
        Address of i_ptr - 0x22ccd0
        Value of i - 5
        Value of i_ptr - 0x22ccd4
        Value of i - 5
        Value of i - 5

The expressions are the same for other built-in types too. Try not to make a pointer point to a different type. You might lose some information when manipulating the type through the pointer. This is called slicing. For example,

        int i = 5;
        char *c_ptr = &i;

Above, c_ptr here slices the value of i. Only the char part of i is stored in c_ptr. Rest of the value is lost in c_ptr.

Pointer to a Pointer and Function Pointers
A pointer which points to another pointer is called a pointer to a pointer. Since pointer too has an address, you could get another pointer to point to it. In memory, it would be represented a

image002.png

Above, i_ptr is the pointer which points to the variable i. i_ptr_ptr is a pointer which points to i_ptr. You can manipulate i using both i_ptr & i_ptr_ptr. Below code samples show how to use pointer to a pointers and function pointers.

The output for the code should be something similar to

        Address of i - 0x22ccd4
        Value of i - 5
        Address of first level pointer - 0x22ccd0
        Address of first level pointer - 0x22ccd0
        Address of pointer to a pointer - 0x22cccc
        Value of pointer to a pointer - 0x22ccd0
        Hello World

Function pointers are pointers that point to a function instead of a variable and when de-refrenced, they call their respective function. It can take in arguments like any other functions.

Function pointers can be assigned, placed in arrays, passed to functions, returned by functions etc. The below snippet explains the concepts behind function pointers.

Here, we are trying to use the same built-in qsort() method to sort both integers and char arrays. The way we do this is pass different comparison function for each of the type. The comparison function is passed as a function pointer to the qsort method. When qsort runs, the comparison function is called through the function pointer, for each pair of values that need to be compared. Based on the return value of the comparison function, the elements in the array are ordered accordingly.

In the above code, do not be overly concerned with understanding all of it. Concepts present here will be explained in successive posts.

Function pointers sometimes make it verbose to decode its declaration. For example,

        int *some_func()

and

        int (*some_func)()

is that the first is a function - taking no arguments and return an int pointer and the next is a pointer to a function - taking no arguments and returning an int. The point to be noted here is that * has a lower precedence than (). Hence the () are required when declaring a function pointer.

Function pointers are mainly used in function callbacks, event driven programs and selecting functions to execute dynamically based on some values.

Pointers and Function Arguments
In C/C++, you cannot alter the value of a variable in the calling function. All variables are passed by value rather than references. So when you write a swap function like this,

       void swap(int x, int y)
       {
            int temp;
            temp = x;
            x = y;
            y = temp;
        }

and call it with swap(a, b), because of call by value, swap cannot interchange the values. The values of a and b would remain the same when the call returns. If you want to forever change its values, you need to use pointers in the function. So swap should be rewritten as,

        void swap(int *x, int *y)
        {
            int temp;
            temp = *x;
            *x = *y;
            *y = temp;
        }

Now the function should be called as, swap(&a, &b). Since the & operator produces the address of a variable, &a is a pointer to a. Pointer arguments enable a function to access and change objects in the function that called it.

Another advantage in passing pointers as arguments in functions is that we could make the function return more than one value. We pass pointers as arguments to the functions, and inside the function, we write the values directly into the variable's memory using pointers. For example,

        void some_func(int *val1, int *val2, int *val3)
        {
            *val1 = 5;
            *val2 = 6;
            *val3 = 7;
        }

Here, the values of the three arguments passed into some_func will be maintained even after we exit the function.

Understanding Pointer Declarations
Pointer declarations are like every other declaration in C/C++. Methods to successfully decode declarations can be found in this post. It basically advices,

        "Start at the variable name (or innermost construct if no identifier is present. Look right without jumping over a right parenthesis; say what you see. Look left again without jumping over a parenthesis; say what you see. Jump out a level of parentheses if any. Look right; say what you see. Look left; say what you see. Continue in this manner until you say the variable type or return type."

So following from above, some example decoding would be,

        int *some_var[5]
            some_var is an array[5] of pointers to an int
        int (*some_var)[5]
            some_var is a pointer to an int array[5]
        void (*some_func)()
            some_func is a pointer to a function - taking no args and returning none
        void *some_func()
            some_func is a function which return a void pointer
        char (*(*x())[])()
            x is a function returning pointer to a array of function pointers returning chars

That is it for part 1 of the series. Part 2 will be coming up shortly.

If you have read this far, you should follow me on twitter.

Also feel free to follow my projects on github.