5

POINTERS

5.1 INTRODUCTION

The use of pointers is one of the most powerful features in C. Pointers are simply variables that point to another variable. Relation between these two variables is established by the fact that the value of the pointer variable is the address of the variable it points to. In this chapter we discuss the basics, understanding the use of pointers.

5.2 FUNDAMENTALS AND DEFINING POINTERS

A pointer is a variable that holds the address of some other variable. Depending on the data type of the variable, amount of bytes in memory is reserved. In the IBM range of machines a character variable requires 1 byte of memory, an integer variable requires 2 bytes, and a floating point-variable requires 4 bytes. Consider the following declarations:

image

Fig. 5.1 Relation between pointer variable and ordinary variable

To be precise, pointer to c means that the pointer variable that points to c holds the address of variable c, that is it holds &c. Similarly a pointer to i holds the address of i ( &i) and a pointer of f holds the address of f (&f).

Thus, to get the address of a variable we make use of the & operator, which is an unary operator. C provides the facility to store such an address to another variable, known as pointer ( because these variables are holding the address of another variable). Consider three pointer variables: pc, pi, and pf. This means all of them can hold addresses of other variables. Since they are variables, we must define them before their use. We will see the method of defining such variables within a moment. As pc, pi, and pf are pointer variables we can easily make the following assignments:

pc=&c;
pi=&i;
pf=&f;

Assume the following three variable definitions:

char    cc;
int       ii;
float    ff;

C provides another unary operator * to get the content of an address. More pricisely, *pc gives you the contents of address pc and we know that it is a character. So we can easily write the statements of the form

cc=*pc;

Similarly, we can write

ii=*pi;
ff=*pf;

This highlights the fact that though the variables pc, pi, and pf are pointers, the data type of their contents differs. More precisely, pc is a pointer that points to a character, pi is a pointer pointing to an integer, and pointer pf points to a floating-point value. This distinction of pointers should be reflected at the time of defining these variables and we define these variables as

char     *pc;

int        *pi;

float     *pf;

Clearly, the sequence of statements,

pi=&i;
ii=*pi;

is equivalent to the statement

ii= i;

The statements

*pi=*pi+l;

*pi+=l;

and

++*pi;

are identical and increments (by 1) of what pi points to. We should note that pointers never point to anything useful until they are initialized.

5.3 TYPE SPECIFIERS AND SCALARS FOR POINTERS

We have seen that an integer pointer is defined by the declaration

int  *pi ;

Here the name of the pointer variable is pi and its type specifier is int. This means that the pointer pi will be used with an int type value. The scalar for the pointer variable pi does not refer to itself, but refers to the element it points to. The scalar size for a pointer is defined by its type specifier, which is int in this case. We may use the sizeof ( ) operator to determine the scalar size of a pointer.

By this we mean that for the pointer definitions

float      *pf;
double     *pd;
int        *pi;

the scalar size of pf, pd, and pi sets to

          sizeof(float)=4

          sizeof(double)=8

and    sizeof(int)=2

respectively. Actually the compiler requires this scalar of a pointer to perform the pointer operations properly.

5.4 OPERATIONS USING POINTERS

Sometimes we may need to test the pointer variables. Relational operators such as >=, <=,>, <,==, and != may be applied to pointers only when both operands are pointers, for example,

if ( pntrl >= pntr2)
{
.
.
.
}

is acceptable, but the following example,

if ( pntr > 50)
{
.
.
.
}

is invalid. The reason behind this is that the numeric constant 50 is not of pointer type. Note that the equality(==) and inequality(!=) test operators may also be applied if one of the operands is a null pointer (i.e., NULL or‘ 0’). This discussion is not yet sufficient because one may seek to find the situation when pointers in comparisons are pointing to different data types. In fact, we should not use these tests when the pointers point to different data types. Unfortunately, some compilers do not detect this sort of errors and it may be very difficult to track the bugs that crop up due to such pointer comparisons.

5.5 PASSING POINTERS TO FUNCTIONS

We have already seen that C does not provide any direct way for the called function to change the value of a variable in the calling function. The reason is that, in C, the argument passing between functions uses the ‘call by value’ method. So the following function interchange ( ), to interchange two values cannot affect the arguments in the calling function.

interchange (int a, int b)

{
     int t;

     t = a ;
     a = b ;
     b = t ;
     return;
}

This is because the formal parameters a and b hold the private copy of the values of corresponding actual parameters. To achieve the goal we may pass the addresses of the variables to the function. For example, to interchange two integers x and y, the function reference may be of the form

interchange(&x, &y);

and the function definition may be written as the following:

        interchange (int *pa, int *pb)
        {
              int t ;

              t = *pa;
              *pa = *pb;
              *pb = t ;
        }

As earlier, this function does not have the provision to change the arguments, but as the arguments are pointers, it can very well alter the contents of the pointers. So by passing pointers as function arguments we can bypass the problem of the ‘call by value’ technique.

5.6 POINTERS AND ARRAYS, POINTER ARITHMETIC

One of the striking features of C pointers is its relationship with arrays. In fact, an array reference is converted to a pointer expression in C. Recall that an array name is constant, which holds the address of the first element of the array. To get a clear view, let us consider the following definitions:

            int *px;
            int x[5];

This implies that px is a pointer variable that can hold the address of an integer, and x is an array of five integer values. The elements of the array are

            x[0],x[l],x[2],x[3],x[4]

and x is the array name which is a constant and holds the address of x[0]. Pictorially, it may be represented as Fig. 5.2.

image

Fig. 5.2 x is the pointer constant pointing to x[0]

So the assignments

            px=&x[0];

and

            px=x;

are equivalent.

Now, if we write the statement

            px = px+1; or px++;

it will advance px by 1, meaning px will now point to the next element of the array, that is, x[l]. More generally, if px points to x[0], then (px+i) will point to the element that is i elements after px and this is true regardless of the size of the elements of the array. This means that *(px+i) is identical to x[i]. In fact, *(x+i) is same as x[i], since x is holding the address of x[0].

To visualize this fact we need to understand the pointer arithmetic little clearly. When a pointer is added to or subtracted from an integer, the scalar size of the pointer comes in use. In fact, this integer is scaled by the scalar size. That is, to the compiler the expression

            *(px+i)

looks like

            *(px+(i*scalar size of px))
            = *(px +i* sizeof(int))
            = x[i]

This indicates that (px+i) points to the ith elements of the array if px points to the first element of the array, irrespective of the type of the array elements.

We should note that a statement like

            x = px;

is an illegal statement as x is a constant. Like addition, a pointer may be subtracted by an integer to point an element before it. We should also note that two pointers pointing to the same type may be subtracted to yeild the distance between the elements they point to. These features of pointers are known as address arithmetic in C.

As mentioned in the earlier section we know that when an array is passed as a parameter to a function, what is passed is the address of the first element of the array. Example 5.1 is presented for visualization of this fact which is a program for arranging an integer array by using a function.

In this example we called bubsort ( ) function to perform the array sorting. It receives the array to be sorted together with the array size and sorts the array within the function. Notice that the array elements may be changed inside the function. Another version of the same problem is presented in Example 5.2, which uses pointers instead of an array.

Example 5.1: Arranging an integer array in ascending order of sequence.

 /* The following program uses Bubble Sorting technique */
 #include <stdio.h> 
 #define SIZE 10
 main( )
 {
             int i, a[SIZE]; 
 
             for (i=0; i<SIZE; i++)
                   scanf (“%d”, &a[i]);
             bubsort (a, SIZE);
             for  (i=0; i<SIZE; i++)
                   printf (“%d%c”, a[i], (i+l<SIZE)? 	’: ‘
’ );
 }
 bubsort (int x[], int limit)
 {
      int bound, i, temp, flag;
 
      bound = limit-1;
      do 
      {
 
            flag = 0;
            for (i=0; i < bound; i++)
                       if (x[i] > x[i+1])
                       {
                            temp = x[i];
                            x[i] = x[i+1];
                            x[i+l]= temp;
                            flag = i;
                       }
            bound = flag;
      } while (bound);
      return;
 }

The version of the program given in Example 5.2 needs little discussion. The program receives n (the number of elements to sort) from the user.

Example 5.2: Arranging a set of integers in ascending order of sequence.

 /* The following program uses Bubble Sorting technique */
 /* The program uses pointers instead of arrays */
 #include <stdio.h>
 main( )
 {
            void *calloc( ), free( ) ;
            int i, n, *a;
 
            printf (“Enter number of elements (integers):”) ;
            scanf (“%d
”, &n) ;
            a = (int *) calloc(n, sizeof(int)) ;
            for ( i=0; i<n; i++)
                  scanf ( “%d”, a+i ) ;
            bubsort (a,n) ;
            for (i=0; i<n; i++)
                  printf (“d%%c”, a[i], (i+l<n)? ‘	’:‘
’) ;
            free (a) ;
 }
 bubsort (int *x, int limit)
 {
            int bound, i, temp, flag;
 
            bound = limit-1;
            do    {
                  flag=0;
                  for ( i=0; i < bound; i++)
                        if (*(x+i) > *(x+i+1)) {
                                   temp = *(x+i);
                                   *(x+i) = *(x+i+1);
                                   *(x+i+1) = temp;
                                   flag = i;
                        }
 
                  bound = flag;
            } while (bound);
            return;
 }

The statement

        a = (int *) calloc (n, sizeof(int));

is new to us. The call to the function calloc (a, b), which is a library function, reserves a memory area for storing a number of elements where the size of each element is b bytes and returns the pointer to this area.

This pointer is of void type. To convert it to a pointer of integer type we write

               (int *) calloc(a, b);

which is known as casting. This function is used to allocate storage space dynamically (at runtime). Another function that may be used to allocate storage space dynamically is malloc ( ) function. This also returns a pointer to void type. A call to this function looks like

               malloc(a)

It reserves a bytes in memory and returns a pointer to this memory area.
Check the first ‘for’ statement in the example that reads n integer values from standard input device and stores them in the locations pointed by

               a, a+1, a+2,…, a+n-1

The function bubsort ( ) receives the address of the first element and the number of elements to sort. The rest of the program is self-explanatory, except for the function call free (a). This function is used to deallocate the storage space reserved earlier by calloc or malloc which is pointed by a.

5.7 POINTERS AND TWO-DIMENSIONAL ARRAYS

In the last chapter we have already discussed two-dimensional arrays and have seen their usefulness, especially when working with tabular data. If we want to define a two-dimensional array for storing the names of six different programming languages, we would

define it as

              char lang[6][8];

The above definition states that there are six elements in the array, each of which may hold upto eight characters. The process of initialization of a two-dimensional array is already discussed in the last chapter and to do this initialization we would write

      static char lang[6][8] = {
         “FORTRAN”, “BASIC”, “COBOL”, “PASCAL”, “C”, “Ada”
          };

The first index 6 of this two-dimensional array says that there are six elements (rows) in the array and the second index 8 says that each element may have at the most eight elements (columns). We have selected 8 because the number of characters in the longest element FORTRAN has seven characters plus one character more for the NULL character which is the terminator of any string. At the time of initialization of the array we have declared the variable as of static storage class which may not be required in some compilers (ANSI C allows auto storage class variables to be initialized). To establish how pointers are related with two-dimensional arrays we first make use of the following program in Example 5.3 which is simple enough and uses no pointers.

Example 5.3: Program to print each of the programming languages together with their memory address.

 #include <stdio.h>
 #define MAX 6
 #define LEN 10
 
 main( )
 {
       static char lang[MAX][LEN] = {
             “FORTRAN”, “BASIC”, “COBOL”, “PASCAL”, “C”, “Ada”
       };
       int i,j;
 
       for (i=0; i<MAX; i++)
       {
                  printf (“
%p”, &lang[i][0]);
                  for ( j = 0; lang[i][j]; j++)
                        printf (“%c”, lang[i][j]);
       }
       return 0;
 }

Output: The output of this example program in my system is

00AB   FORTRAN
00B2    BASIC
00BC   COBOL
00C6    PASCAL
00D0    C
00DA   Ada

Note that the above program uses two ‘for’ loops to display the characters in the array. In the starting pass through the loop, the first printf ( ) displays the address of lang [0][0] and then falls into the j loop and in this loop it prints “FORTRAN”. Then again it executes the outer loop and continues until all the language names are displayed.

Let us now modify the above program a little by removing the inner loop which is controlled by j and adding a %s (string) conversion character to the printf ( ) function. After this modification the ‘for’ statement looks like

          for(i=0; i<MAX; i++)
                printf (“
%p%s”, &lang[i][0], lang+i); 

In this case, the %p in printf ( ) prints the addresses as before but %s in printf ( ) prints a string from the given address lang +i. As we know, pointer arithmetic is always scaled by the scalar size of the data item being pointed to, and lang holds the first address of a two-dimensional array. Note that lang +i will be scaled as

          lang + i*(scalar size of the two dimensional array)?

The scalar for a two-dimensional array is the size of each element multiplied by the second dimension in the array definition. So, lang +i converts to

          lang + i*(sizeof(char)*8)
                =lang + 8i

This is why the expression lang+i in the program increases by 8 each time i is incremented by 1. In general, for a multidimensional array declaration like

    type_specifier    array_name [dl] [d2]…………[dn];

the scalar size is given by

scalar_size = sizeof (type_specifier) * d2 * d3 * . . . . . . * dn.

To illustrate this fact, let us consider the declaration

         float a[5] [8] [10] [12];

The corresponding scalar size is computed as

         scalar_size = sizeof(float)*8*10*12 
                     =4*8*10*12
                     =3840

It is to be noted that the first dimension is not used to determine the scalar size.

5.8 ARRAY OF POINTERS

Instead of going straight to the array of pointers we must understand the difference between a character array and a character pointer. Consider the following definitions:

          char myarray[] = “array”;
 and      char *yourptr = “pointer”;

In the former definition ‘myarray’ is an array (one dimensional) of characters. The size of the array will automatically be set by the number of characters in the initialized string plus 1, and may be depicted as in Fig. 5.3.

image

Fig. 5.3 Memory map of myarray

The ith element may be referred to as myrray[i]. The name myarray holds the address of the character ‘a’, the first element of the array. In the later definition the meaning is completely different. Here the string constant “pointer” is pointed by the pointer variable yourptr. Pictorially, it may be shown as in Fig. 5.4.

image

Fig. 5.4 Memory map of yourptr

It should be appreciated that the assignment

           yourptr = myarray;

will just make yourptr to point to the first element of the array myarray, and is not a string copy. At this point we can start our discussion on array of pointers. Clearly, an array of pointers is nothing but an ordinary array each of whose elements is a pointer. We may define an array aop of pointers as

           char *aop[5];

In this case, each element of the array is a pointer to a character. The array elements are

         aop[0], aop[1], aop[2], aop[3] and, aop[4]

and as we know the array name aop holds the address of aop[0], the address of the first element of the array.

An array of pointers may also be initialized at the time of its definition, as below.

           char *aop[5]= {
                “BASIC”,
                “COBOL”,
                “FORTRAN”,
                “PASCAL”,
                “Ada”
                };

In case of such a definiton, the element aop[0] will hold the address of the string constant “BASIC”, aop[1] will hold the address of “COBOL”, and so on. Pictorially, it may be viewed as shown in Fig. 5.5.

image

Fig. 5.5 Memory map of aop

An example program is given in Example 5.4 to illustrate the working principles of an array of pointers. This example program is displaying the strings those are pointed by the array elements. In the definition of array the size of array is not mentioned and is set automatically (since it is initialized). To get this size we can make use of the sizeof operator discussed earlier.

Example 5.4: Program to display the strings pointed by the array elements of an array of pointers.

 #include <stdio.h>
 main( )
 {
      char *direction[]={
                            “North”,
                            “East”,
                            “West”,
                            “South”
                        };
      int i;
      #define SIZE (int) sizeof (direction) / sizeof(directionfO])
 
      for (i=0; i<SIZE; i++)
            printf ( “%s%c”, direction[i], (i<SIZE-1)?‘ ’:‘ 
’);
 }

In the above program note that the printf ( ) statement prints a blank character after printing North, East, and West but a newline (‘ ’) after printing South.

Another interesting program is presented in Example 5.5 which prints only the first character of each of the strings.

Example 5.5: Program to display the first character of the strings pointed by the array elements of an array of pointers.

 #include <stdio.h>
 main( )
 {
      char *direction[] = {
                      “North”,
                      “East”,
                      “West”,
                      “South”
                      };
      int i;
      #define SIZE (int)sizeof(direction) / sizeof(direction[0])
 
      for(i=0; <SIZE; i++)
           printf (“%c”, direction[i][0]);
 }

From Fig. 5.5 it is obvious that *aop gives the pointer to the string “BASIC”. Since aop is an array (aop+i) is the address of aop[i], and hence *(aop+2) will be give pointer to “FORTRAN”. So *(aop+2)[0] will give the character F, the first character of “FORTRAN”. The program in Example 5.6 is written using this concept of pointers and is a combined form of the last two programs.

Example 5.6: A pointer version of the programs in Example 5.4 and Example 5.5.

 #include <stdio.h>
 main( )
 {
      char *direction[] ={
                             “North”,
                             “East”,
                             “West”,
                             “South”
                         };
      int i;
      #define SIZE (int)sizeof(direction)/sizeof(direction)[0])
 
      disp_array (SIZE, direction);
      printf (“ 
 =======> ”);
      disp_lst_char(SIZE, direction);
      return;
 }
 disp_array (int n, char *ptrarr[]
 {
      while (n–– > 0)
            printf(“%s%c”, *ptrarr++, (n > 0 ) ?‘ ’:‘
’);
      return;
 }
 disp-lst-char (int n, char *ptrarr[]
 {
      while (n–– > 0)
            printf (“%c”, (*ptrarr++)[0]);
      printf (“
”);
      return;
 }

5.9 POINTERS TO POINTERS

Consider again Fig. 5.5 where aop is an array name and holds the address of the first element of an array which is itself a pointer to a character. Now since aop is a constant we may want to store it to a variable for some purpose. To do so how should the variable be defined? The answer is simple. Definitely, it is to be stored to a pointer variable which points to a pointer to a character. This suggest the variable definition of pop as below.

           char **pop;

With such a definiton we can safely write an assignment statement like

           pop = aop;

Note that pop is nothing but a pointer to a pointer.

5.10 POINTERS TO FUNCTIONS

As we know, the returned value of a function is available within the name of the function. Actually, a function name is referring a memory location. So in C, we may have the concept of a pointer to a function also. In fact, this may be passed as an argument to another function. To define a variable (say ptrtofn) as a pointer to a function which returns a value of type type_name we write

           type_name (*ptrtofn)( );

To illustrate the use of such pointers we present a program in Example 5.7. The program reads two integers from the standard input device and the operation to be performed on these operands, performs the operation by calling a function that uses an argument which is pointer to a function, and finally prints the result to the standard output device. The program is self-explanatory.

Example 5.7: Program to simulate a rudimentary calculator by using pointers to functions.

 #include <stdio.h>
 main( )
 {
      int x, y, operation,
      double result, operated, add( ), subtract( ), mul( ), divide( );
 
      printf (“Enter two integers :”); 
      scanf (“%d %d”, &x, &y),
      operation = getchar( );
                  /* Throw away character in keyboard buffer */
      printf (“Choose an operation ( +, -, *,/) :”);
      operation = getchar( );
      switch (operation)
      {
      case ‘+’ : result = operate (add, x, y);
                          break;
      case ‘-’ : result = operate (subtract, x, y);
                          break;
      case ‘*’ : result = operate (mul, x, y);
                          break;
      case ‘/’ : result = operate (divide, x, y);
                          break;
      default  : printf (“You entered a bad operator 
”);
                 exit(1);
      }
      printf (“The result is = %g/n”, result);
      return;
 }
 
 double operator (double (*pf)( ), int a, int b)
 {
      double value;
      value = (*pf)(a, b); /* Basically a function call */
      return value;
 }
 double add ( int p, int q )
 { 
      return( double ) (p+q);
 }
 double subtract ( int p, int q )
 {
      return ( double )(p-q);
 }
 double mul ( int p, int q )
 {
      return (double )(p*q);
 }
 double divide ( int p, int q)
 {
      return ( double )(p/q);
 }

5.11 COMMAND THE ARGUMENTS

So far we dealt with many C programs, all of which have no arguments in the main ( ) function. As a matter of fact, the function main ( ) may have two arguments, traditionally written as argc and argv. These two arguments of function main ( ) are useful when we want to pass the arguments supplied in the command line. The argument argc is an integer parameter while the parameter argv is an array of pointers. Each element of this array points to a character. The values of these arguments are set automatically at the time of execution. Consider that we have a program whose executable module is named flush, to display the arguments that appear in the command line (other than the program name).

That is, if we issue the command

flush Dear I Always Remember You

the program flush will display

Dear I Always Remember You

On execution of the program flush, the function main ( ) will get the value of argc as 6 and an array argv of argc (in this case 6) number of pointers will be created automatically. The pictorial view of the argv array will look like that in Fig. 5.6.

image

Fig. 5.6 Pictorial view of argv array

A program code to achieve this is listed below in Example 5.8. As an additional task it also displays the first characters of all these arguments. It treats the array elements as pointers. Precisely saying that it is a pointer version program and is essentially same as the program code listed in Example 5.6, except for a few changes.

Example 5.8: The C code for the program flush.

 #include  <stdio.h>
 main(int argc, char *argv[])
 {
            flush_array (argc, argv);
            printf(“
=====>”);
            flush_l_array(argc, argv);
            return;
 }
 flush_array(int n, char *ptrarr[])
 {
            while (n–– >0)
                  printf(“%s%c”, *ptrarr++, (n>0)?‘ ’ :‘
’);
                  return;
 }
 flush_l_array(int n, char *ptrarr[])
 {
            while(n–– >0)
                  printf(“%c”, (ptrarr++)[0]);
            printf (“
”);
            return;
 }

Another C code is presented in Example 5.9 which receives a date in the format dd-mm-yyyy from the command line and checks whether it is a valid date or not. This program is not only an example of command line argument, but also covers many aspects relating to pointers in C. Note that the function convert receives a parameter ptrarr which is a pointer to a pointer to character. This function highlights the way of changing the value of variables by passing pointers to variables.

Example 5.9: The C code to check the validity of date given in command line.

 #include <stdio.h>
 main(int argc, char *argv[])
 {
      int d, m, y;
      int leap;
 
      if (––argc>0)
      {
           convert(++argv, &d,&m,&y);
           leap = y % 4 == 0 && y % 100 != 0 || y % 400 == 0;
           printf (“The date %s is %s 
”, *argv,
                 (valid (d,m,y,leap)) ? “valid” : “not valid”);
      }
      else
           printf (“Usage :: VALIDATE <dd-mm-yyyy>
”);
      return;
 }
 convert (char “ptrarr, int *pd, int *pm, int *py)
 {
      char *curptr, c;
      int n;
 
      curptr = *ptrarr; /* Points to date string */ 
      for (n=0; ((c = *curptr) >= ‘0’ && c<=‘9’); curptr ++)
            n = 10*n + (c-‘0’);
      *pd = n;
      ++curptr;
      for (n=0; ((c=*curptr)>=‘0’ && c<=‘9’); curptr++)
            n = 10*n + (c-'O’);
      *pm = n;
      ++curptr;
      for (n=0; ((c=*curptr)>=‘0’ &&c<=‘9’); curptr++)
            n = 10*n + (c-‘O’);
      *py = n;
      return;
 }
 valid (int d, int m, int y, int leap)
 {
      if (d<=0||d>31||m<=0||m>12||y<= 0||y>3000)
            return 0;
      if ((m==4||mm==6||m==9||m==11) && d>30)
            return 0;
      if (m==2 && d>29)
            return 0;
      if (m==2 && leap==0 && d>28)
            return 0;
      return 1;
 }

In this chapter we have not discussed how pointers are related with structures. Lastly, we make some final observations as follows:

(i) As a single fixed-size data item, pointers provide a homogeneous method of referencing any data structure regardless of the structures' type or complexity.

(ii) In some instance, pointers permit faster inclusion and deletion of elements to and from a data structure.

EimagesXimagesEimagesRimagesCimagesIimagesSimagesEimagesS

1. Write a function to determine the length of a string of characters that is entered by the user from a standard input device.

2. Consider the following pointer definitions:

(i) int (*ptr) [10];

(ii) int *ptr[10];

How do the definitions differ?

3. What is the difference between an array name and a variable defined as a pointer?

4. Write a program to read a group of input lines, each containing one word. The program should print each word that appears in input and the number of times it appeared.

5. Write a function strlast (str1, str2) which returns 1 if the string str2 occurs at the end of the string str1, otherwise the function returns 0.

6. Write a function strsearch which receives two character pointers as arguments to it and returns a character pointer. The function searches the first string to see whether the second string appears in it. If it is so, it returns a pointer to where the second string is in the first string, otherwise, it returns a null pointer.

7. Write a program to read an integer (maximum upto nine digits) and print it in words.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.233.205