This section covers the pointer concept in C . Pointers are often misunderstood and misused . To understand pointers thoroughly we must write programs and perform experimentation in addition to understanding the theory and the relevant parts of language standard. It is also important to draw or have the image of what happens in the RAM when pointers are involved. The explanation in this section will focus on lot of code examples. You should copy these to your system and compile, run them .
When a program is compiled and run it is loaded into RAM and then run. Along with the code memory is reserved for data also. A simplistic diagram showing this:
RAM
0 | --------------------------
1 | Data
| Program loaded into memory
| Code
1000 | --------------------------
The above diagram shows a program that is loaded into the RAM. The CPU will take an instruction from the code section and execute an instruction. However space also needs to be reserved for the data that the program is going to use. The operating system will allocate some space in the RAM for your program . In the diagram that is indicated by the right most vertical arrow. The operating system will not let the program access memory outside the range that has been reserved for it. Once the program exits then the operating system releases the memory for the program. If a program introduced a memory leak and exits then leak is removed when the operating system removes all the memory that was allocated for the program. There are 2 different types of memory reserved for the program. One is the heap and the other is the stack. Local variables are stored on the stack . Memory created dynamically ( when the program is running ) using special syntax such as "malloc" is allocated on the heap. This memory has to be freed explicitly by the programmer.
When ever a program uses a variable an address is assigned to the variable. Let's take a look at a sample program showing the use of pointers and variables.
File: "ptr1.c"
/* What is a pointer */
#include<stdio.h>
main()
{
int x1 ;
int* ptr1 ;
x1 = 100 ;
printf("x1 value is %d\n" , x1 );
ptr1 = &x1 ;
printf("%p \n" , ptr1 );
*ptr1 = 200 ;
printf("x1 value is %d\n" , x1 );
//A pointer has to be of some type. If we increment by 1 then then the pointer address is incremented by 4
ptr1 = ptr1 + 1 ;
printf("%p\n" , ptr1 );
//Not a legal memory location
ptr1 = ptr1 + 10000 ;
*ptr1 = 300 ;
}
The above is a conceptual diagram. The real addresses for a program loaded will of course be different. We assume that the smallest block of memory is a byte and that the bytes can be referenced using memory addresses that start from the top with a value of 0 and increase downwards. First we declare the variable of type int to be x1 and then assign a value of 100 to it. An int usually takes 4 bytes in the memory. Then we declare a variable "ptr1" that is a pointer and the data type is a pointer and it's pointing to an int. The variable "ptr1" can hold a value but that value is an address of a location in memory. Regardless of the type of pointers a pointer will always hold an address. How do we assign the address of say the variable x1 to the ptr1 variable.
ptr1 = &x1
This is done using the ampersand symbol . Writing "&x1" means take the address of that variable. We also have the operation "*ptr1" where we are placing the star before the variable "ptr1" . What this operation does is, first it finds the value of the ptr1 which is an address and then it goes to where that address and grabs the value that is at that address. It can also change the value at that address. In the above diagram our variable x1 got placed at the address 1 and contains the value 100. The pointer "ptr1" is also a variable and it contains the address of where x1 is at. Thus it contains the value 1 . When we say something like:
*ptr1 = 200
Then first the address of x1 is fetched and in this case that address is 1 and then the value at that address is changed to 200 from 100 . We can see how this type came to be known as the "pointer" type. We can also perform arithmetic with pointers.
The statement
ptr1 = ptr1 + 1 ;
changes the value of the address that ptr1 is holding. But the result may not be what we expect. If the initial value of ptr1 is 1 then the result of "ptr1 + 1" will not be 1 but instead be 5 and that is because the size of an int is 4 and the compiler takes that into consideration. Remember the pointer is of a certain type. When declared the pointer we declared it as
int* ptr1 ;
And what that means is the ptr1 holds an address and that address holds a data type that if of type int. We need to be very careful with pointers . Let us examine the following 2 lines.
ptr1 = ptr1 + 10000 ;
*ptr1 = 300 ;
Initially ptr1 contains the address of x1 and then we add 10,000 to the address giving us a new address and then try to access this new memory location. Since this new memory location is not valid this will most likely result in a crash of our program. Running the above program produces a sample output as below:
$ ./intro_ptr1.exe
x1 value is 100
0xffffcbf4
x1 value is 200
0xffffcbf8
Segmentation fault (core dumped)
The address is in hexadecimal and is a 32 bit address . We can see that after the line :
*ptr1 = 200
the value of x1 has changed to 200. When we incremented the address by 1 the new address least significant bit changed from "4" to "8" . Again this depends on what the data type is that the pointer address points to( in our case that's int) . When we try to access an invalid address location we receive a "Segmentation fault" .
The below exercises cover the above topic.
1)
File: ex1.c
Below is a skeleton program:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x1 = 100 ;
}
Create a pointer named "ptr1" to x1 .
Print the values of x1 and what the pointer "ptr1" points to .
Using "ptr1" increment the value of x1 by 10 .
Print the values of x1 and what the pointer "ptr1" points to .
Print the value of "ptr1" and the address of "ptr1" .
Draw the conceptual RAM diagram of the variables.
Explain what the program printed out and the RAM diagram that you drew.
What does
printf( "%p\n" , & ( &ptr1 ) ) ;
mean ? Try putting the statement in your program and compile your program.
What does
printf( "%p %p\n" , * ( &ptr1 ) , &x1 ) ;
mean?
2)
Use the below starting code. Declare a pointer "ptr1" to ch . Using the pointer change the value of ch to 'b' . Print out the
value of "ch" to make sure the value got changed.
#include<stdio.h>
#include<stdlib.h>
int main()
{
char ch = 'a' ;
}
3)
Use the code below to complete the sections in comments
Some of the code has been written out for you.
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x1 = 100 ;
int x2 = 200 ;
//Assign the address of x1 to ptr1
// Assign the value of ptr1 ( the address not what it points to to ptr2 )
//Print the contents of x1 x2 and what ptr1 ptr2 point to
//Assign the address of x2 to ptr1
//Increment the values pointed to by ptr1 and ptr2
//Print the contents of x1 x2 and what ptr1 ptr2 point to
}
Output should be as:
[amittal@hills Exercises_Discussion]$ ./a.out
x1: 100 x2: 200 *ptr1:100 *ptr2:100
x1: 101 x2: 201 *ptr1:201 *ptr2:101
Explain the output using a RAM diagram
4)
What does the following print ?
#include<stdio.h>
#include<stdlib.h>
int main()
{
int i1 = 10 ;
int *pi = &i1 ;
double d1 = 12.5 ;
double *pd = &d1 ;
printf( "%d\n" , ++i1 ) ;
printf( "%d\n" , ++(*pi) ) ;
printf( "%.1lf\n" , --(*pd) ) ;
}
1)
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x1 = 100 ;
int* ptr1 ;
ptr1 = &x1 ;
printf( "x1: %d *ptr1:%d\n" , x1, *ptr1 ) ;
*ptr1 = *ptr1 + 10 ;
printf( "x1: %d *ptr1:%d\n" , x1, *ptr1 ) ;
printf( "ptr1: %p Address of ptr1:%p\n" , ptr1, &ptr1 ) ;
//printf( "%p\n" , & ( &ptr1 ) ) ;
}
RAM
x1 5 100
ptr1 10 5
The expression "&ptr1" will give us the address of where "ptr1" is . Remember "ptr1" is also a variable. It's contents are an address but it must also have an address. In our diagram the address is "10" . However since "10" is not a variable; taking the address of it again does not make sense. Compiler will give us a warning of type:
d1.cpp: In function ‘int main()’:
d1.cpp:20:21: error: lvalue required as unary ‘&’ operand
| printf( "%p\n" , & ( &ptr1 ) ) ;
2)
#include<stdio.h>
#include<stdlib.h>
int main()
{
char ch = 'a' ;
char* ptr1 = &ch ;
*ptr1 = 'b' ;
printf( "%c\n" , ch ) ;
//Explain the output of the below line
printf( "%p\n" , &ch ) ;
}
3)
#include<stdio.h>
#include<stdlib.h>
int main()
{
int x1 = 100 ;
int x2 = 200 ;
//Assign the address of x1 to ptr1
// Assign the value of ptr1 ( the address not what it points to to ptr2 )
int* ptr1 = &x1 ;
int* ptr2 = ptr1 ;
//Print the contents of x1 x2 and what ptr1 ptr2 point to
printf( "x1: %d x2: %d *ptr1: %d *ptr2: %d\n" ,
x1 , x2 , *ptr1 , *ptr2 ) ;
//Assign the address of x2 to ptr1
ptr1 = &x2 ;
//Increment the values pointed to by ptr1 and ptr2
*ptr1 = *ptr1 + 1 ;
*ptr2 = *ptr2 + 1 ;
//Print the contents of x1 x2 and what ptr1 ptr2 point to
printf( "x1: %d x2: %d *ptr1: %d *ptr2: %d\n" ,
x1 , x2 , *ptr1 , *ptr2 ) ;
}
4)
#include <iostream>
using namespace std ;
int main()
{
int i1 = 10 ;
int *pi = &i1 ;
double d1 = 12.5 ;
double *pd = &d1 ;
cout << ++i1 << endl ;
cout << ++(*pi) << endl ;
cout << --(*pd) << endl ;
}
11
12
11.5