Pointer World !!


In this post, I will just give a brief overview of using pointers in several ways in our C/C++ programs. Pointer is sort of a known concept. Hence, the post will be focusing on the usage in various scenarios like arrays, strings, structs, functions along with code examples.

A pointer is a variable that stores an address of a memory location, and that is why it is a pointer. By itself it does not store typical user data. All it has is a memory address of some other variable of some type.

int var = 10;
int *ptr;
ptr = &var;

The above simple code example initializes a variable “var” of type “int” and an integer pointer ptr to that integer variable. So ptr stores the address of integer variable var.

Pointer to Pointer

We can have a pointer pointing to another pointer and thus the former pointer is a double pointer:

int var = 10;
int *ptr;
ptr = &var; // ptr is a single pointer and contains the address of variable var
int **dptr;
dptr = &ptr; // dptr is a double pointer and contains the address of pointer variable ptr.

Let’s say the addresses are as follows:

1. Address of var = 100
2. Contents of var = 10
3. Address of ptr = 200
4. Contents of ptr = Address of var = 100
5. Contents of dptr = Address of ptr = 200

Accessing contents of var through dptr => **dptr
Accessing contents of ptr through dptr => *dptr

int var1 = 20;
*dptr = &var1; 

The above statement makes the pointer ptr point to the address of variable var1. So now doing (**dptr) will fetch the value of var1 and not var.

Pointers and Arrays

The following declaration of an array of size 10 implicitly declares the variable “arr” as a pointer to the base/first element of the array. So “arr” stores the address of element arr[0]

int arr[10];

Following are equivalent ways of indexing array elements:

int arr[3] = {1, 2, 3};
for(i=0;i<3;i++)
{
   printf("%d %d \n", arr[i], *(arr + i));
}

Output =>
1 1
2 2
3 3

int *ptr;
ptr = arr;
for(i=0;i<3;i++)
{
   printf("%d %d \n", ptr[i], *(ptr + i));
}

Output =>
1 1
2 2
3 3


for(i=0;i<3;i++) 
{ 
   printf("%d ", *ptr); 
   ptr++; 
}

Output => 1 2 3

*(arr + i) => arr has an address of base element at index [0]. Whenever doing pointer arithmetic, the increment/decrement operate in terms of type of data the pointer is pointing to. Here the pointer is to an int. So the pointer will be incremented by (i * sizeof(int)) bytes. We then dereference it to get the value.

When we store the address of base element of array in integer pointer ptr, we do ptr = arr because as explained before arr stores the address of base element. This statement is equivalent to ptr = &arr[0].

In the last for loop, we simply increment the pointer to get to consecutive elements of array, and just dereference it every time to get value.

This fact about array allows us to pass an array to a function in a pointer like manner.

void foo(int *arr, int size)
{
  for(i=0; i<size; i++)
  {
     printf("%d ", arr[i]);
  }
}
int arr[3] = {1, 2, 3};
foo(arr, 3);

Output => 1 2 3

Because the array variable “arr” is just a pointer, we passed the array to the function “foo” by just passing the base address of array.

Similar examples can be shown for chars.

char *s = “abc”; declares “s” as a pointer of type char and it points to the first character of string “abc”.

/* a string declared as an array of 3 characters. str points to the first character in the array */
char str[3] = "abc"; 
/* s is a char pointer also pointing to the first element in the array */
char *s = str; 
printf("%s", s);
for(i=0; i<3; i++)
{
  printf("%s ", s);
  // advances to the next character in the string
  s++; 
  printf("%s\n", s);
}

Output =>
abc
abc bc
bc c
c

When we increment the char pointer “s”, it is incremented by sizeof(char) which is 1 byte. Hence it points to the subsequent character of the string. printf() starts printing from the memory location pointed to by “s” until a null/termination character is encountered.

Array of Pointers

We use the following syntax to declare an array of integer pointers :

int *arr[10];

The following alternate declaration also works:

int *(arr[10]);

The above statement declares “arr” as an array of 3 integer pointers. So an element at each index is a pointer to an integer and not the actual integer value. Following code example shows how to traverse through such an array.

int *arr[3];

int x = 1;
int *ptr1 = &x;
int y = 2;
int *ptr2 = &y;
int z = 3;
int *ptr3 = &z;

arr[0] = ptr1;
arr[1] = ptr2;
arr[2] = ptr3;

for(i=0;i<3;i++) 
{ printf("%d \n", *(*(arr + i)); }

Output => 1 2 3

We will apply the same method here. *(arr + i) gets us the value stored in array “arr” at index “i”. However, because it is an array of pointers, this value we got is again a pointer to an integer. Hence, in order to get to the corresponding integer value, we just de-reference the pointer through

*(*(arr + i))

Now let us try to use double pointers to get the actual integer values.

for(i=0;i<3;i++) 
{ 
   /* first de-reference arr to get an integer pointer. This comes from the fact that arr is an array of int pointers */
   int **ptr;
   *ptr = arr[i];
   /* now de-reference the int pointer to get to the actual int value */
   printf("%d \n", **ptr);
}

Output => 1 2 3

We use the following syntax to declare an array of char pointers :

char *str[3];

So an element at each index “i” is a pointer to a char.

char *str[3];
str[0] = "abc";
str[1] = "def";
str[2] = "ghi";

for(i=0; i<3; i++)
{
   char *ptr = str[i];
   printf("%s ", ptr);
   ptr++;
   printf("%s\n", ptr);
}

Output => 
abc bc
def ef
ghi hi

When traversing the array, we first get a pointer to a char and store it in pointer “ptr” of type “char”.  The printf() statement just prints the characters from the memory location pointed to by “ptr” until ‘\0’ character is encountered.

str array is nothing but an array of 24 bytes as {address1, address2, address3}. It stores addresses which are (char *) pointers. The content at each index of the array is a pointer (address) whose size is usually 8 bytes on modern 64 bit architectures.

Pointer to an Array

We use the following syntax to declare a pointer to an array :

int (*arr)[10];

This however doesn’t really provide any different purpose and I have rarely used it.

int (*arr)[3];
int *ptr = arr;
ptr[0] = 1;
ptr[1] = 2;
ptr[2] = 3;
for(i=0; i<3; i++)
{
   printf("%d ", *ptr);
   ptr++;
}

Output => 1 2 3

Pointers and Structs

Let’s say we have the following struct definition

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

struct ele var;
struct ele *ptr;

Above we declare two different variables for this struct. Variable “var” is of type struct and “ptr” is a pointer to a struct of type ele.

Initializing the struct variable var:

var.x = 1;
var.y = 2;
var.z = 3;

Storing the address of var in the pointer variable and accessing the members:

ptr = &var;
printf("x is %d %d\n", var.x, ptr->x);
printf("y is %d %d\n", var.y, ptr->y);
printf("z is %d %d\n", var.z, ptr->z);


Output =>
x is 1 1
y is 2 2
z is 3 3

“->” is the operator used to dereference the pointer variables for structs. The other simple way of accessing the member variables through the pointer variable would be (*ptr).x

It works because ptr has the address of the memory region allocated for struct variable var. We dereference it using (*ptr) just like we do for any other pointer. It then gets us the value of first 4 (or 8) byte integer member x.

Pointers, Structs and Arrays

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

struct ele var[3] = {
                      {1, 2, 3},
                      {4, 5, 6},
                      {7, 8, 9}
                     };
struct ele *ptr;

We have declared an array variable of type struct ele. Each element in the array is of type struct ele. Here is how we can traverse the array assuming all the array elements (which are 10 different structures) are initialized.

/* Because var is an array, it stores the base address. Hence we can use the following syntax */
ptr = var;
for(i=0; i<3; i++)
{
   printf("x is %d %d %d\n", var[i].x, (*(var + i)).x, ptr[i].x);
   printf("y is %d %d %d\n", var[i].y, (*(var + i)).y, ptr[i].y);
   printf("z is %d %d %d\n", var[i].z, (*(var + i)).z, ptr[i].z);   
}

Output =>
x is 1 1 1
y is 2 2 2
z is 3 3 3
x is 4 4 4
y is 5 5 5
z is 6 6 6
x is 7 7 7
y is 8 8 8
z is 9 9 9

for(i=0; i<3; i++) 
{ 
   printf("%d %d %d\n", ptr->x, ptr->y, ptr->z);
   ptr++;
}

Output =>
1 2 3
4 5 6
7 8 9

In the above examples we have just used the mechanism of iterating through the array using a pointer.

Arrays of Pointers with Structs

We can use a similar declaration to declare an array of struct pointers:

struct ele *var[3];

Here is how we can use it:

struct ele v1;
v1.x = 1;
v1.y = 2;
v1.z = 3;
struct ele *p1 = &v1;
struct ele v2;
v2.x = 4;
v2.y = 5;
v2.z = 6;
struct ele *p2 = &v2;
struct ele v3;
v3.x = 7;
v3.y = 8;
v3.z = 9;
struct ele *p3 = &v3;
 
struct ele *var[3];
var[0] = p1;
var[1] = p2;
var[2] = p3;

struct ele *var[3];
for(i=0; i<3; i++) 
{ 
   struct ele *ptr = var[i]; 
   printf("x is %d %d\n", ptr->x, var[i]->x);
   printf("y is %d %d\n", ptr->y, var[i]->y);
   printf("z is %d %d\n", ptr->z, var[i]->z);
}

Output =>
x is 1 1 
y is 2 2 
z is 3 3 
x is 4 4 
y is 5 5 
z is 6 6 
x is 7 7 
y is 8 8 
z is 9 9

Because “var” is an array of pointers, var[i] gives the element at index “i”, and it is a pointer to a struct. We then dereference it using “->” operator to access the member variables of the struct.

Pointers in Nested Structs

struct s1
{
   int x;
   int y;
};

struct s2
{
   int a;
   int b;
   struct s1 *ptr;
};

struct s2 var1;
struct s2 *var2;

Usage:

var1.a = 10;
var1.b = 20;
var1.ptr = (struct s1 *)malloc(sizeof(struct s1));
var2 = &var1;

(var1.ptr)->x = 1;
(var1.ptr)->y = 2;

printf("a is %d %d \n", var1.a, var2->a);
printf("b is %d %d \n", var1.b, var2->b);

printf("x is %d %d \n", (var1.ptr)->x, (var2->ptr)->x);
printf("y is %d %d \n", (var1.ptr)->y, (var2->ptr)->y);

Output =>
a is 10 10 
b is 20 20 
x is 1 1 
y is 2 2 

Pointers with Arrays in Nested Structs

struct s1
{
   int x;
   int y;
};

struct s2
{
   int a;
   int b;
   int count;
   struct s1 *ptr;   
};

struct s2 var1;
struct s2 *var2;

Usage:

var1.a = 10;
var1.b = 20;
var1.count = 2;
var1.ptr = (struct s1 *)malloc(var1.count * sizeof(struct s1));
var2 = &var1;

for(i=0; i<var1.count; i++)
{
    var1.ptr[i].x = i;
    var1.ptr[i].y = i;
}
printf("a is %d %d \n", var1.a, var2->a);
printf("b is %d %d \n", var1.b, var2->b);

for(i=0; i<var1.count; i++)
{
   printf("x is %d %d \n", var1.ptr[i].x, var2->ptr[i].x);
   printf("y is %d %d \n", var1.ptr[i].y, var2->ptr[i].y);
}

Output =>
a is 10 10 
b is 20 20 
x is 0 0 
y is 0 0 
x is 1 1 
y is 1 1

Constant Pointers and Pointers to Constants

A constant pointer is a pointer that can’t be changed. It implies that once a pointer points to some memory location/address, we can’t make the pointer point to some other memory location. However, we can change the value/data at that memory location provided the pointer is not pointing to some constant data. See the following examples:

We use the following syntax to declare a const pointer to non-const data:

<pointer type> * const <pointer name>;

Examples:

int * const ptr1; This declares a constant pointer to an integer

char * const ptr2; This declares a constant pointer to char

int x = 10;
/* A constant pointer ptr */
int * const ptr;
ptr = &x;
printf("%d\n", *ptr);

int y = 12;
/* ERROR - The below statement will throw error because the pointer is constant */
ptr = &y; 

/* The below statements won't throw error because the constant pointer is pointing to non-const data */
x = 15;
*ptr = 20;
printf("%d\n", *ptr);
/* A constant pointer s1 */
char * const s1 = "abc";
printf("%s \n", s1);

char ch = 'X';
/* ERROR - The below statement will throw error because the pointer is contant */
s1 = &ch;

Compilation Errors:
error: assignment of read-only variable ‘ptr’
error: assignment of read-only variable ‘s1’

Output after commenting out offending statements:

10
20
abc

We use the following syntax to declare a non-const pointer to a const data:

const <pointer type> * <pointer name>;

Examples:

const int * ptr1; This declares a non-const pointer to a const int.

const char * ptr2; This declares a non-const pointer to const char

int x = 10;
/* A non-const pointer ptr to a const int */
const int *ptr = &x;
printf("Address held in ptr %p\n", ptr);

/* ERROR - The below statement will throw error because we are attempting to change the value at memory location */
/* The pointer is pointing to constant value. We can't change the value through this pointer */
*ptr = 15;

int y = 12;
/* The below statement is fine because the pointer by itself is non-const and can point to any location */
ptr = &y; 
printf("Address held in ptr %p\n", ptr);

/* A non-const pointer s1 to const char */
const char *s1 = "abc";
printf("Address held in s1 %p \n", s1);

char ch = 'X';
/* The below statement is fine because the address pointer is pointing to can be changed */
s1 = &ch;
printf("Address held in s1 %p\n", s1);

Compilation Errors:
error: assignment of read-only location *ptr

Output after commenting out offending statement:
Address held in ptr 0x7fffa8bc12dc
Address held in ptr 0x7fffa8bc12d8
Address held in s1 0x4010dc
Address held in s1 0x7fffa8bc12d7

Note how the addresses held in the pointer variables have changed. This is possible because the pointer is a non-const.

We use the following syntax to declare a const pointer to a const data:

const <pointer type> * const <pointer name>;

Examples:

const int * const ptr1; This declares a const pointer to a const int.

const char * const ptr2; This declares a const pointer to const char.

int x = 10;
/* A const pointer ptr to a const int */
const int * const ptr;
ptr = &x;

/* ERROR - The below statement will throw error because we are attempting to change the value at memory location */
/* The pointer is pointing to constant value. We can't change the value through this pointer */
*ptr = 15;

int y = 12;
/* ERROR - The below statement will also throw error because the pointer is also constant */
ptr = &y; 
/* A const pointer s1 to const char */
const char * const s1 = "abc";

char ch = 'X';
/* ERROR - The below statement will also throw error because the pointer is also constant */
s1 = &ch;

Compilation Errors:
error: assignment of read-only location *ptr
error: assignment of read-only variable ptr
error: assignment of read-only variable s1

This completes the post. I haven’t covered few things like use of function pointers and some other code examples simply because the post was getting too long. I will cover them in the next post in this series.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: