Saturday, 27 July 2013

POINTERS IN C LANGUAGE-NULL,DANGLING,DEAFAULT POINTERS

Introduction:

Before pointers one should understand about address of memory blocks.
Consider the declaration:
            int i=5;

This tells compiler:
1)      to reserve a memory space of two bytes
2)      to associate a name i (name of block)
3)      to assign 5 as a content


Up till now we never used address of variable, but from now we will use it to access memory location.

main()
{
     int i=5;
    
     printf(“\n %d “,i);
     printf(“\n %u “,&i);
     printf(“\n %d “,*&i);
}
Output
5
65524
5

Explanation:
First printf() is very simple, it prints 5 which is the value of i. Second printf() prints address of block i. Notice operator ‘&’ which we were invariably used in scanf(). Third printf() is a bit tricky which prints 5. We used ‘*’ operator whose operand is address of block i.
Our observation

 ►  & is called ‘address of’ operator
 ►  &i returns the address of the variable
 ► * is called ‘value at address’ operator or simply ‘value at’.
 ►  * returns the value stored at a particular address
 ►  * is also called an ‘indirection’ operator.

Remember:
  1. We cannot store anything in &i as &i is not a variable, it is the way to represent address of block i.
  2. We can collect the address of i (&i) in another variable. Let’s say i and j are two variables and we want to store address of i in j. we can do this by the following statement:
j=&i;
  1. Remember j is not an ordinary variable like any other integer variable.
  2. j is a variable which contains the address of another variable
  3. A compiler would provide a separate space in memory for this variable ‘j’.
  4. As the j is also a variable and it requires a separate space, it is obvious that it has some address also (location number).
  5. We can’t use j in program without declaring it. And since j is a variable, which contains the address of i, it is declared as
            int *j;
  1. This special declaration tells the compiler that j will be used to store the address of an integer value. In other words j points to an integer.




EXAMPLE: 



main()
{
     int i = 5, *j;
     j= &i;
     printf(“\n%u”, &i);
     printf(“\n%u”, j);
     printf(“\n%u”, &j);
     printf(“\n%d”, *&j);
     printf(“\n%d”, i);
     printf(“\n%d”, *(&i));
     printf(“\n%d”, *j);
}

Output:
65524
65524
3276
65524
5
5
5

Explanation:
&i:  read as ‘address of i’ that is 65524
j:  read as j, that represents value in j, that is 65524
&j: read as ‘address of j’ which is 3276
*&j: read as ‘value at address of j’ that is 65524
i: read as i, that represents value in i that is 5
*(&i): same as *&i: read as ‘value at address of i’ that is 5
*j: read as ‘value at address contained in j’ that is 5

Note operand of value at operator (*) must be address of some variable.

Pointer:

 ► Pointer is a variable that contains address of another variable.
 ►  Address of variable is an integer ranges from 0 to 65535.
 ►  Since address range is same as the range of unsigned int variable, we use %u format specifier when printing address of variable.
 ►  Addresses of variables can be used to access them.
 ►  Variable address can be stored in pointer variable.
 ►  Pointer variable when declared always prefixed with * symbol. This special symbol tells the compiler that this is a pointer which can only store address of some variable.
 ►  When pointer is declared without initialization, it contains garbage value. This garbage is interpreted as address because it is stored in pointer variable. Obviously this garbage represents invalid address as we haven’t made any reservation to that memory location. Such pointers are called un-initialized pointers or wild pointers.
 ►  To avoid un-initialized pointers we can assign 0 to them or NULL. This value when stored in pointer, it is interpreted as if it is not pointing to anywhere. Such pointers are called NULL pointer.
 ►  NULL is a macro defined in stdio.h.
 ►  Since pointers always stored address, they occupy 2 bytes in memory.

An important point to understand:
float *s;
Above declaration does not mean that ‘s’ is going to occupy 4 bytes and would store floating point value. It actually means ‘s’ is going to contain address of floating point value.
‘s’ will store an address of some float block which contains floating point value.
We can call such pointer as float pointer.

Base Address:
Consider the following declarations:
 int *p, a;
 float *s, k;
 char *m, ch;
 double *d, b;
 p=&a;
 s=&k;
 m=&ch;
 d=&b;
Now we have four pointers, each of different type. Each of the pointers consumes 2 bytes in memory. p contains address of int block ‘a’,  s contains address of float block ‘k, m contains address of char block ‘ch’ and d contains address of double block ‘b’.

Consider the following diagram:
It is important to note that operating system allocates address to every byte. In our example ‘a’ is a variable of type int whose address is 1000 (assume), but this is the address of first byte of int block. 1001 is the address of the next byte. Similarly 2000 is the address of first byte of float block. Since float block is of 4 bytes, each byte has different address in a sequential manner.
Address of the first byte of the block of any type is known as base address of that block.
Pointer variable can store one address at a time and it is always base address. To access any block through pointer requires address of remaining bytes of the block. This is possible because of two reasons. First, addresses of bytes of a block are always in a sequence. Second, type of pointer that stores base address.
If the pointer is declared as float pointer (consider ‘s’ in above example), it is aware of 3 more addresses to be accessed along with base address. Similarly, if the pointer is declared as double pointer (consider ‘d’ in above example), it is aware of 7 more addresses to be accessed along with base address.

Void Pointer:
Pointers declared with keyword void are void pointers or generic pointers. Since type of pointer is void, pointer can not access block, whose address it holds, by just de-referencing pointer. As we studied in the above section, pointers only hold base address and type of pointer decides how many more bytes need to be accessed. If the type of pointer is void then we can not know how many more bytes need to be accessed. Every time when we access block through pointer, typecasting is needful.
Example:
main()
{
  void *p;
  int x=3;
  float k=3.14;
  p=&x;
  printf(“%d”, *((int *)p));
  p=&k;
  printf(“%f”, *((float *)p));
}


Extended Concept of pointers:

Pointer is a variable which contains address of another variable. Now this variable itself could be another pointer. Thus we now have a pointer, which contains another pointer address

Consider the following example

main()
{
        int i = 3, *j, **k;
        j=&i;
        k=&j;
        printf(“%u %u %u\n”, &i, j,*k);
printf(“%u %u %u”, &j, k, &k);
        printf(“\n%d %d %d”, i, *j, **k);
}

In memory consider the following diagram to support above program.
3276
 
Output is:

6048 6048 6048
3276 3276 5140
3 3 3

Pointer jargons:

int i;      // i is an int
int *j;    // j is a pointer to an int
int **k;  // k is a pointer to a pointer to an int

Pointers arithmetic:

Although C language is not very strict about compatibility issues between various type of data, but programmer has to take special care about type conversions. It is also needful in forthcoming programming languages.

Rule 1: Take care for the compatibility
Example
main()
{
       int i=3;
       char *j;
       j = &i; //Error as incompatible assignment
       printf(“%d %u”, i, &i);
}

Error: cannot convert ‘int*’ to ‘char*’
(error message depends on compiler most of the C compilers do not show any error)


Example

main()
{
       long int i=3;
       int  *j;
       j = &i;     //Error as incompatible assignment
       printf(“%d %u”, i, &i);
}

Error: cannot convert ‘long*’ to ‘int*’


Example
main()
{
       int k, i=3, *j;
       j = &i;
       k = j; //Error as incompatible assignment
       printf(“%u”, k);
}

Error: cannot convert ‘int*’ to ‘int’

Example
main()
{
     int k, i=3, *j;
     j = &i;
     k = *j;
     printf(“%d”, k);
}

The output is
3

Example
main()
{
       int k, i=3, *j;
       j = &i;
       k = *j + *j;
       printf(“%d”, k);
}
The output is
6

In this program *j would means ‘value at address contained in j’ and it is 3.

Rule 2: We can not add two addresses.

Example
main()
{
       int *k, i=3, *j;
       j = &i;
       k = j + j;
       printf(“%d”, k);
}
Error: Invalid pointer addition.

Call by reference:

A function is called by passing address is called ‘function call by reference’.
Function can only access its own memory and can not access variables of other functions but if we pass address of variables during function call, we actually give power to called function to access variables of calling function with the help of addresses of variables.

To illustrate above convention go through the following example:
#include<stdio.h>
#include<conio.h>
void swap(int *, int*);
main()
{
    int a,b;
    clrscr();
    printf(“Enter two numbers\n”);
    scanf(“%d%d”,&a,&b);
    swap(&a,&b);
    printf(“”a=%d b=%d”,a,b);
   getch();
}
void swap(int *x, int *y)
{
    int temp;
    temp=*x;
    *x=*y;
    *y=temp;
}

Output:
Enter two numbers
10
20
a=20 b=10

 ►  In the above program, two variables (a and b) are declared in main() function. They can be accessed from main() function only by their names.
 ►  Let us assume user enter values 10 and 20, which get stored in variable a and b.
 ► Observe the function call swap(). Instead of passing values of variable a and b, addresses of a and b are passed.
 ► Since addresses are always stored in pointer variables, we create two pointer variables in function swap() as formal argument.
 ►Pointer variables x and y received addresses of variable a and b.
 ►  In function swap(), variable a and b of main() function can be accessed with the help of pointers x and y.
 ►  *x represent variable a and *y represent variable b.
 ► Notice that values need not to be returned as changes made in actual locations only.
 ►  Also notice the declaration of function swap(). As two addresses of int block would be passed during function call, it is specified as void swap(int*, int*);


Pointers with arrays:

Remember two things about pointer and array:
1)      However big an array it always stored in a contiguous location.
2)      When pointer is incremented it always points to immediately next location of its type.
Consider the following example:

#include<stdio.h>
#include<conio.h>
void input(int *);
void display(int *);
main()
{
   int a[5];
   clrscr();
   input(&a[0]);
   display(&a[0]);
   getch();
}
void input(int *p)
{
   int i;
   printf(“Enter five numbers”);
   for(i=0; i<=4; i++)
      scanf(“%d”, p+i );
}
void display(int *p)
{
   int i;
   printf(“Five numbers are: ”);
   for(i=0; i<=4; i++)
      printf(“%d ”, *(p+i));
}

Explanation:
 ►Address of very first block of array that is address of a[0] is passed during function call of input() and display()
 ►  Address is received in pointer variable p.
 ►  We need not to create as many pointer variables as we have array blocks. We need to store address of first block only (a[0]). Rest of the addresses can be obtained by simple pointers arithmetic. By adding 1 to p we get address of a[1], similarly by adding 2 to p, we get address of a[2] and so on.
 ►In the function input(), see scanf(), we wrote p+i in it. As the value of i increases with every iteration of for loop, p+i represents address of successive blocks of array.
 ►In function display(), see printf(), we wrote *(p+i), as we need to pass values of array blocks in function printf().




Pointers with two dimensional arrays:

There are two ways to create pointers that will be used to store address of two dimensional arrays.
First is simply create a pointer and second is pointer to an array.

The following example illustrates the first way.

main()
{
  int i;
  int a[2][3]={3,5,1,6,7,8};
  int *p;
  p=&a[0][0];
 for(i=0;i<=6;i++)
  printf(“%d”,*(p+i));
}

Since array of any dimension always consumes memory in contiguous manner. In our example we made an array of six blocks. Address of the first block gets stored in pointer p. Now by incrementing address contained in p by one each time, we can point to next block in array.

Second way is to create a pointer to an array.

main()
{
   int i,j;
   int a[2][3]={3,5,1,6,7,8};
   int (*p)[3];
   p=a;
   for(i=0;i<=1;i++)
  {
        for(j=0;j<=2;j++)
     printf(“%d “,*( *(p+i)+j));
        printf(“\n”);
  }
}

Explanation:
 ► Notice the way we declare pointer. This is not a simple pointer but it is a pointer to an array of 3 int blocks.
 ►  In the line p=a, address of array gets stored in pointer p.

  

 ► Consider the above diagram, let us assume the address of array is 1000 and it is stored in pointer p.
 ►  p+1 represent address of second array that is 1006. This happens because p is not a simple pointer to an int, rather it is a pointer to an int array of size 3. Thus every time when pointer is incremented, it points to next array.
 ►  *(p+1) represent address of first block of first address and if we add 1 to it that is *(p+1)+1, it means address of next block of the first array.
 ►  Chart below gives you a clear picture of pointer jargons and their meaning.


Expression
Meaning
Expression
Meaning
P
1000
*(*(p+0)+0)
3
p+1
1006
*(*(p+0)+1)
5
*(p+0)
1000
*(*(p+0)+2)
1
*(p+1)
1006
*(*(p+1)+0)
6
*(p+0)+1
1002
*(*(p+1)+1)
7
*(p+0)+2
1004
*(*(p+1)+2)
8
*(p+1)+0
1006
*p
1000
*(p+1)+1
1008
**p
3
*(p+1)+2
1010




Pointers with strings:
Just like an int array, char array can also be managed with pointers. We can store address of char array in char pointer. Now this pointer can be used for any string manipulation.

main()
{
   char str[ ]=”Welcome”;
   char *p;
   p=str;
   printf(“%s”,p);
}

Output:
Welcome

There is a difference between p and str. Pointer p is used to store address of char array. We can change value of p an any instant as it is merely a variable. On the other hand str is not a variable. Name of array like str is always used to represent address of first byte of array. Since str is not a variable operations attempting to change value of str leads to compilation error.

Define a function to calculate length of string:

int stringlength(char *p)
{
   int i=0;
   while(*(p+i)!=’\0’)
     i++;
   return(i);
}




Array of pointers and pointer array:

Array of pointers and pointer array are same, but careful about pointer to an array (see pointer with two dimensional arrays in previous page).

Pointer array is like you are creating many pointers in one go, all of them are of same type.

main()
{
   int a=3,b=4,c=5,i;
   int *p[3];
   p[0]=&a;
   p[1]=&b;
   p[2]=&c;
   for(i=0;i<=2;i++)
     printf(“%d ”,*p[i]);
 }
 Output:
3 4 5

   In the above example we have three pointers, p[0], p[1] and p[2]. These pointers are used to contain addresses of a, b and c. Thus pointer array means sequence of pointers.

Dangling pointer:

A pointer becomes dangling when memory of the block is released whose address is still reside in pointer. It is much like wild pointer. The only difference in wild pointer and dangling pointer is, wild pointers are un-initialized pointers thus containing invalid address. Wild pointer points to unknown location which can be dangerous from programming point of view.
On the other hand dangling pointers are initialized perfectly but due to programmers carelessness it is still pointing to the memory area which was released.

main()
{
   int *p;
   {
     int x=4;
     p=&x;
    }
    *p=5;
}

See the scope of variable x is limited to the inner block. Pointer p is containing address of variable x. As soon as control comes out from inner block, memory of variable x is released, but the pointer p still contains address of x. Here p becomes dangling.




Const pointer and pointer to a const:

Const pointer is a pointer whose value can not be altered.

Pointer to a const is a pointer that stores an address of const variable thus we can not modify variable data using pointer.

Declaration of pointer to a const:

const int  x = 4;
const int *p; //or int const *p;
p=&x;

The above code tells that the pointer p is a pointer to a const. This means we can not modify variable x using pointer p.
For example:
 *p=6; //error: cannot modify const variable
  p++; //valid:

Another example:

int x=4;
const int *p; //or int const *p;
p=&x;

x++; //valid
*p=6;//error: can not modify const variable
p++; //valid

Another example

const int x=4;
int *p;
p=&x;
x++; //error: can not modify const variable
*p=3; //undefined behavior , should be an error
p++; //valid

Example for const pointer

int x=5;
int *const p=&x;
p++; //error: can not modify const variable p
x++; //valid
*p=7; //valid

Another example:

int x=5;
int *const p;
p=&x; //error can not modify const variable p.



Remember that const variables must be initialized during declaration.

No comments:

Post a Comment