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:
- We cannot store anything in &i as &i is not a variable, it is the way to represent address of block i.
- 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;
- Remember j is not an ordinary variable like any other integer variable.
- j is a variable which contains the address of another variable
- A compiler would provide a separate space in memory for this variable ‘j’.
- As the j is also a variable and it requires a separate space, it is obvious that it has some address also (location number).
- 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;
- 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.
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.
|
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