This section covers the pointer concept in C/C++ and related topics such as references. 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 .
Type casting refers to forcing a variable to be considered as another type from it's original type.
Ex:
#include<stdio.h>
int main(void)
{
int x1 = 100 ;
char ch1 = 'a' ;
//Explicit Cast
ch1 = (char) x1 ;
//Implicit cast
x1 = ch1 ;
return 0;
}
In the above we have the integer variable that we are casting to a character type. We can implicit casts also as with the statement:
x1 = ch1
Casting is necessary when using the "malloc" function as the "malloc" function returns the "void*" .
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" or "new" 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.
/* What is a pointer */
#include<stdio.h>
int 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.
Exercises
1)
File: d1.cpp
Below is a skeleton program:
#include <iostream>
using namespace std ;
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
cout << & ( &ptr1 ) << endl ;
mean ? Try putting the statement in your program and compile your program.
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 <iostream>
using namespace std ;
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 <iostream>using namespace std ;int main(){ int x1 = 100 ; int x2 = 200 ; //Create two pointers "ptr1" and "ptr2" that point to an integer. //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 cout << "x1: " << x1 << " x2: " << x2 << " *ptr1:" << *ptr1 << " *ptr2:" << *ptr2 << endl ; //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 cout << "x1: " << x1 << " x2: " << x2 << " *ptr1:" << *ptr1 << " *ptr2:" << *ptr2 << endl ;}
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 <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 ;}
Solutions
1)
#include <iostream>
using namespace std ;
int main()
{
int x1 = 100 ;
int* ptr1 ;
ptr1 = &x1 ;
cout << "x1:" << x1 << " *ptr1: " << *ptr1 << endl ;
*ptr1 = *ptr1 + 10 ;
cout << "x1:" << x1 << " *ptr1: " << *ptr1 << endl ;
cout << "ptr1:" << ptr1 << " Address of ptr1: " << &ptr1 << endl ;
//cout << & ( &ptr1 ) << endl ;
}
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
cout << & ( &ptr1 ) << endl ;
2)
#include <iostream>
using namespace std ;
int main()
{
char ch = 'a' ;
char* ptr1 = &ch ;
*ptr1 = 'b' ;
cout << ch << endl ;
//Explain the output of the below line
cout << &ch << endl ;
}
3)
#include <iostream>
using namespace std ;
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
cout << "x1: " << x1 << " x2: " << x2 << " *ptr1:" << *ptr1 << " *ptr2:" << *ptr2 << endl ;
//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
cout << "x1: " << x1 << " x2: " << x2 << " *ptr1:" << *ptr1 << " *ptr2:" << *ptr2 << endl ;
}
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
Let us study the below example:
/* Double pointer program */
#include<stdio.h>
#include <stdlib.h>
#include <iostream>
using namespace std ;
int main(int argc , char** args)
{
int x1 = 100 ;
int x2 = 200 ;
int* ptr1 ;
int** doublePtr ;
ptr1 = &x1 ;
doublePtr = &ptr1 ;
cout << *ptr1 << endl ;
//2 Levels of indirection
cout << **doublePtr << endl ;
*doublePtr = &x2 ;
cout << **doublePtr << endl ;
cout << *ptr1 << endl ;
return(1) ;
}
/*
RAM
x1 0 100
x2 5 200
ptr1 10 0 --> 5
doublePtr 15 10
*/
Output:
100
100
200
200
In the above example we have a pointer "ptr1" . A pointer variable will always hold an address. This is true regardless of what the pointer type will be. It is always helpful to draw a diagram in the RAM to understand pointers. We do not need the exact RAM address that will be assigned when the program is run. We need a conceptual understanding and the diagram can help with that. Initially the pointer "ptr1" holds the address of the variable "x1" . We define the double pointer with the declaration:
int** doublePtr ;
What is the above stating ? The "doublePtr" is also a pointer. It occupies space in the memory and it's value is an address.
ptr1 = & x1
We have the "ptr1 " holding the address of the variable x1 . In our conceptual diagram x1 is at the address 0 holding the value 100 and x2 is at the address 5 holding the value 200 . So the "ptr1" variable is now going to hold the address 0 . Remember "ptr1" is also a variable and thus resides at an address . In our example "ptr1" is at the address 10 .
doublePtr = &ptr1 ;
In the above line we are taking the address of "ptr1" and assigning it to "doublePtr". The "doublePtr" is also a pointer and it contains an address .
However when we apply the operation of "*doublePtr" We do not get an integer value. Instead we get another address. In the above diagram if we do "*doublePtr" we follow the "10" to the pointer variable "ptr1" where we get the value "0" . This is yet another address and we could follow it to where x1 is by another "*" . Using the notation "**doublePtr" we can obtain the value of the "x1" variable which is 100 .
Next we have the statement
cout << *ptr1 << endl ;
The above statement states that we look up the address in the "ptr1" variable which is "0" and follow that to obtain the value of "x1" which is 100 .
cout << **doublePtr << endl ;
What the "**" does is 2 levels of indirection. The first "*doublePtr" gives us "0" which is the address of x1 that ptr1 stores. Recall that "doublePtr" itself stores the address 10 ( address of ptr1 ) and with "*" we are following that. Now that we have 0 we do another "*" ( we have 2 stars in **doublePtr ) . Following 0 leads us to the value of x1 which is 100 and that gets printed out.
now we have the value of "x2" printed out and that is 200 .
Exercises
1) In the example above what do &doublePtr, doublePtr and *doublePtr have for values in the conceptual diagram ?
2a)
Run the following program on your computer and draw the RAM diagram to understand the output:
2b) Change the value of "num" by using the "ptr1" and also by using "double_ptr" .
#include <iostream>
using namespace std ;
int main()
{
int num = 100 ;
//A normal pointer ptr1
int *ptr1;
//This pointer ptr1 is a double pointer
int **double_ptr;
/* Assigning the address of variable num to the * pointer ptr1 */
ptr1 = &num ;
/* Assigning the address of pointer ptr1 to the * pointer-to-pointer double_ptr */
double_ptr = &ptr1; /* Possible ways to find value of variable num*/
printf("\n Value of num is: %d", num);
printf("\n Value of num using ptr1 is: %d", *ptr1);
printf("\n Value of num using double_ptr is: %d", **double_ptr);
/*Possible ways to find address of num*/
printf("\n Address of num is: %p", &num);
printf("\n Address of num using ptr1 is: %p", ptr1);
printf("\n Address of num using double_ptr is: %p", *double_ptr);
/*Find value of pointer*/
printf("\n Value of Pointer ptr1 is: %p", ptr1);
printf("\n Value of Pointer ptr1 using double_ptr is: %p", *double_ptr);
/*Ways to find address of pointer*/
printf("\n Address of Pointer ptr1 is:%p",&ptr1);
printf("\n Address of Pointer ptr1 using double_ptr is:%p",double_ptr);
/*Double pointer value and address*/
printf("\n Value of Pointer double_ptr is:%p",double_ptr);
printf("\n Address of Pointer double_ptr is:%p",&double_ptr);
return 0;
}
Pointer as parameters to functions
We have seen how declaring a reference in the function argument lets us change the variable inside the function. Pointers also allow for doing something similar. Ex:
/* Double pointer program */
#include<stdio.h>
int global1 = 300 ;
//---------------------------------------------------------------------------------------------------
void swap3( int** param1 )
{
*param1 = &global1 ;
}
//---------------------------------------------------------------------------------------------------
void swap2( int* param1 )
{
*param1 = 200 ;
}
//---------------------------------------------------------------------------------------------------
void swap1( int param1 ){ param1 = 200 ;}
//---------------------------------------------------------------------------------------------------
int main()
{
int x1 = 100 ;
int* ptr1 ;
int** doublePointer ;
printf("%d\n" , x1 );
swap1( x1 ) ;
printf("%d\n" , x1 );
ptr1 = &x1 ;
swap2( &x1 ) ;
printf("%d\n" , x1 );
//Change the value of a pointer.
doublePointer = &ptr1 ;
swap3( doublePointer ) ;
printf("%d\n" , **doublePointer ); printf("%d\n" , *ptr1 );
return(0) ;
}
/*
RAM
global1 0 300
x1 1 100
2
3
ptr1 4 1 -> 0
ptr2 100 4
*/
In the above program we have declared three functions "swap1" , "swap2" and "swap3" . The function "swap1" shows that if we pass an integer from the main function to the function "swap1" it's value in the main function does not get changed since we are passing by value. In the function "swap2" we pass the address of the variable "x1" and the function swap2 takes a pointer as it's argument. Using the pointer it changes the value of x1. The last function "swap3" changes the value of the pointer ptr1 by making it point to the address of "global1" instead of "x1" . To change the value of an integer we passed in it's address. To change the value of a pointer "ptr1" we take the address of "ptr1" and that makes it a double pointer. The argument to the function "swap3" is a double pointer.
Allocating memory for a single element
In the above examples a pointer held the address of an existing variable. We can also allocate memory dynamically ( that is at run time when the program is running ) and assign the address to a pointer. The size of the memory could be for say a single integer or even a block of 10 integers. In either case the pointer holds the address. In the case of a block the starting address of the block is stored in the pointer.
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
//Allocating space a single int
//Old style C way
ptr1 = (int*)malloc( sizeof(int) * 1 ) ;
*ptr1 = 100 ;
cout << *ptr1 << endl ;
free( ptr1 ) ;
//new style of C++
ptr1 = new int ;
*ptr1 = 100 ;
cout << *ptr1 << endl ;
delete ptr1 ;
return(0) ;
}
The above code shows how memory for a single int can be allocated on the heap dynamically.
ptr1 = (int*)malloc( sizeof(int) * 1 ) ;
We want to allocate memory for a single int . Remember "C++" is a super set of "C" and almost all of the "C" features are supported by "C++" . With "malloc" we have to specify the exact number of bytes that need to be allocated. The function
"sizeof(int)"
tells us the size of integer on this machine. Remember the sizes of a data type might be different depending on the computer hardware and the operating system. We are multiplying by 1 to explicitly show that we are only interested in 1 integer. We also
need to type cast with "(int*)" telling us what sort of memory has been allocated. In this case memory for a single int has been allocated. The type returned by "malloc" will always be a pointer holding an address. We may allocate space for a single integer or allocate space for a block of integers or even allocate space for a block of pointers.
Once we get the address we can place a value at the address with the statement:
*ptr1 = 100 ;
Once we are done with the pointer then we can free the memory that got allocated with the statement:
free( ptr1 ) ;
With "C++" we have similar calls but the syntax is much cleaner.
ptr1 = new int ;
The "new" system function call create a space for a single integer. In front of new we specify the type that we want to create the space for. There is no need to type cast it to a pointer. To free the memory we use:
delete ptr1 ;
Exercises
Ex 1:
What is wrong with the following program ?
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
//new style of C++
ptr1 = new int ;
*ptr1 = 100 ;
cout << *ptr1 << endl ;
ptr1 = ptr1 + 1 ;
cout << *ptr1 << endl ;
delete ptr1 ;
}
Allocating a block of Memory
Let's take a look at the below program:
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
//Allocate space for 10 integers
ptr1 = (int*)malloc( sizeof(int) * 10 ) ;
ptr1[0] = 100 ;
ptr1[1] = 200 ;
cout << ptr1[0] << ":" << ptr1[1] << endl ;
cout << *ptr1 << ":" << *(ptr1+1 ) << endl ;
free( ptr1 ) ;
//Allocating space the C++ way for 10 integers
ptr1 = new int[10] ;
ptr1[0] = 101 ;
ptr1[1] = 201 ;
cout << ptr1[0] << ":" << ptr1[1] << endl ;
cout << *ptr1 << ":" << *(ptr1+1 ) << endl ;
delete[] ptr1 ;
//int arr1[10 ] ;
return(0) ;
}
Output:
100:200
100:200
101:201
101:201
The lines :
//Allocate space for 10 integers
ptr1 = (int*)malloc( sizeof(int) * 10 ) ;
ptr1[0] = 100 ;
ptr1[1] = 200 ;
cout << ptr1[0] << ":" << ptr1[1] << endl ;
cout << *ptr1 << ":" << *(ptr1+1 ) << endl ;
allocate a block of 10 integers . Conceptually our RAM diagram looks like:
RAM
0
1
ptr1 2 6
3
4
5
6 ^
| Block of
| 10 integers
|
Our pointer "ptr1" still holds as address . However this address now is the starting address of the block. We know we can access the value using "*ptr1" location. However we can also access the value using the array index notation.
cout << ptr1[0] << ":" << ptr1[1] << endl ;
cout << *ptr1 << ":" << *(ptr1+1 ) << endl ;
If we say "ptr1[0]" then we go to the address of "ptr1" and fetch the value there. It's the same exact thing as "*ptr" . Similarly if we say "ptr1[1]" that means go to where the address "ptr1" is and move 1 element down. It is the same as "*(ptr1+1)" . In fact arrays and pointers are almost the same thing in C++.
Exercises
Ex 1
Explain what the below program does:
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
//Allocating space the C++ way for 10 integers
ptr1 = new int[10] ;
for( int i1=0 ; i1<10 ; i1++ )
{
ptr1[i1] = i1 ;
*( ptr1 + i1 ) += i1 ;
} //for
for( int i1=0 ; i1<10 ; i1++ )
{
cout << ptr1[i1] << endl ;
} //for
delete[] ptr1 ;
return(0) ;
}
As we saw arrays and pointers are very similar. Just as a block of memory can be allocated for a pointer and the starting address assigned to a pointer, a block of memory is allocated for an array in the same fashion.
If we say
int array1[10] ;
Then a block of 10 integers is allocated in RAM and the starting address is assigned to the variable "array1" . The key difference between array variables and pointer variables is that a pointer variable's value can be changed but an array variable's value can never be changed. We can access elements in an array using pointer notation and vice versa.
We can obtain the address of the array variable in many ways. Ex:
/* Array address*/
#include<stdio.h>
#include <stdlib.h>
int main()
{
int array1[] = {100,200,300} ;
int* ptr1 ;
printf( "%p\n" , array1) ;
printf( "%p\n" , &array1) ;
printf( "%p\n" , &(array1[0]) ) ;
return 0 ;
}
The above program has an array declared with the name "array1" . A block of 3 elements is allocated and the address assigned to "array1" . There are many different ways of getting the address. We can just use the array name "array1" and that is probably the easiest way to obtain the address. We can use "&array1" that does the same thing. Since the first element is at the starting address and if we take the address of that using the notation "&(array1[0])" then we have the same address also.
Next example will show how an array can be treated as a pointer and vice-versa.
/* Array and pointers */
#include<stdio.h>
#include <stdlib.h>
int main()
{
int array1[] = {100,200,300} ;
int* ptr1 ;
//An array can be treated as a pointer
//array1 = ptr1 ;
ptr1 = array1 ;
printf( "%d\n" , *ptr1 ) ;
printf( "%d\n" , *(ptr1+1) ) ;
printf( "%d\n" , *array1 ) ;
printf( "%d\n" , *(array1+1) ) ;
// A pointer can be treated as an array.
ptr1 = (int*)malloc( sizeof(int) * 10 ) ;
*ptr1 = 10 ; *(ptr1+1) = 20 ;
printf( "%d\n" , ptr1[0] ) ;
printf( "%d\n" , ptr1[1] ) ;
free ( ptr1) ;
}
As the above example shows we can have an array variable "array1" . We can access elements using "*array1" and "*(array1+1)" .
As we stated earlier a pointer can hold the address of a single element or a block. When it holds the address of the block then it acts like an array. We can also think of a pointer as an array. What about an array of pointers ? Each element is a pointer and if we think of that as an array , then we have a array of arrays or in other words a 2 dimensional array.
Cout and Addresses
File: c1.cpp
#include <iostream>
using namespace std ;
int main()
{
char* str1 = "string1" ;
char str2[] = "string2" ;
char ch = 'A' ;
int x1 = 100 ;
cout << str1 << endl ;
cout << str2 << endl ;
cout << ch << endl ;
cout << &ch << endl ;
cout << "Address of str1:" << (void*)str1 << endl ;
cout << "Address of str2:" << (void*)str2 << endl ;
cout << "Address of ch:" << (void*)&ch << endl ;
cout << "Address of x1:" << &x1 << endl ;
}
Output:
string1
string2
A
Astring2
Address of str1:0x400ac5
Address of str2:0x7ffe61baa0d0
Address of ch:0x7ffe61baa0cf
Address of ch:0x7ffe61baa0c8
The above picture shows a double pointer being used as a 2 dimensional array. We have a double pointer that can be declared as:
int** DoublePointer ;
Now if we do a *DoublePointer we will end up at the beginning of the block with address 100 and a value inside it. In this case we have a block so "*(DoublePointer+1)" will lead us to the element of 101. We can also use array notation and get at the values using :
DoublePointer[0] and DobulePointer[1]
These are the rows of our 2 dimensional array. Now each pointer points to a block of memory and if we follow the "DoublePointer[0]" and say
*(DoublePointer[0])
That will give us the first element of the block in RAM . We can also write the above as:
DoublePointer[0][0]
This is how we can construct a 2 dimensional array dynamically. Ex:
#include <iostream>
using namespace std ;
int main()
{
int** TwoDimArray ;
const int ROW_COUNT = 2 ;
const int COL_COUNT = 3 ;
TwoDimArray = new int*[ROW_COUNT] ;
for(int i1=0 , k1=1 ; i1<ROW_COUNT ; i1++ )
{
TwoDimArray[i1] = new int[COL_COUNT] ;
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
TwoDimArray[i1][j1] = k1++ ;
}
} //for
//print contents using array notation
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
cout << TwoDimArray[i1][j1] << " " ;
}
cout << endl ;
} //for
cout << endl << endl ;
//print contents using pointer notation
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
cout << ( *( (*(TwoDimArray+i1)) + j1 ) ) << " " ;
}
cout << endl ;
} //for
cout << endl << endl ;
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
delete[] TwoDimArray[i1] ;
}
delete[] TwoDimArray ;
return 0 ;
}
Output:
1 2 3
4 5 6
1 2 3
4 5 6
Constructing a 2 dimensional array using the c notation:
#include <iostream>
using namespace std ;
int main()
{
int** TwoDimArray ;
const int ROW_COUNT = 2 ;
const int COL_COUNT = 3 ;
TwoDimArray = (int**)malloc(sizeof(int*)*ROW_COUNT) ;
for(int i1=0 , k1=1 ; i1<ROW_COUNT ; i1++ )
{
TwoDimArray[i1] = (int*) malloc(sizeof(int)*COL_COUNT) ;
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
TwoDimArray[i1][j1] = k1++ ;
}
} //for
//print contents using array notation
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
cout << TwoDimArray[i1][j1] << " " ;
}
cout << endl ;
} //for
cout << endl << endl ;
//print contents using pointer notation
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
cout << ( *( (*(TwoDimArray+i1)) + j1 ) ) << " " ;
}
cout << endl ;
} //for
cout << endl << endl ;
for(int i1=0 ; i1<ROW_COUNT ; i1++ )
{
free( TwoDimArray[i1] ) ;
}
free( TwoDimArray ) ;
}
Output:
1 2 3
4 5 6
Exercises
1) What's wrong with the following program:
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
int x1 = 100 ;
ptr1 = &x1 ;
delete ptr1 ;
return(0) ;
}
2) What's wrong with the following program ?
#include <iostream>
using namespace std ;
int main()
{
int* ptr1 ;
int* ptr2 ;
int x1 = 100 ;
ptr1 = new int ;
*ptr1 = 20 ;
ptr2 = ptr1 ;
delete ptr1 ;
delete ptr2 ;
return(0) ;
}
3) Complete the to do sections.
#include <iostream>
using namespace std ;
int main()
{
int* arrayOfGrades ;
int numberOfStudents = 0 ;
while ( numberOfStudents < 1 || numberOfStudents > 10 )
{
cout << "Enter the number of students:" ;
cin >> numberOfStudents ;
}
//To do
//Create a dynamic array with the size as numberOfStudents
//and assign the address to arrayOfGrades
//Run a loop asking the user to enter a grade for each student
//Print the contents of the array
//Free the memory
return(0) ;
}
Sample Output
$ g++ ex3.cpp ; ./a.exe
Enter the number of students:2
Enter the grade for student 1:54
Enter the grade for student 2:25
Grades entered.
54 25
4)
#include <iostream>
using namespace std ;
int main()
{
int arr1[3][3] = { {1,2,3} , {4,5,6 }, {7,8,9 } } ;
int ROWS = 3 ;
int COLS = 3 ;
int i1 = 0 ;
int i2 = ROWS-1 ;
//TO DO
//Write code to reverse the elements in the 2 dim array
//so that the final values are
// 9 8 7
// 6 5 4
// 3 2 1
//Do not create any additional arrays. Your solution should be generic.
//Do not hardcode any values and do not assume the sizes are 3 by 3 .
//Printing the array
for( int i1=0 ; i1<ROWS ; i1++ )
{
for( int j1=0 ; j1<COLS ; j1++ )
{
cout << arr1[ i1 ] [ j1] << " " ;
}
cout << endl ;
}
return(0) ;
}
Solutions
1)
We can only delete memory that has been allocated with new. The "ptr1" variable has the memory address that is of "x1". We must have allocated the memory with "new" in order to "delete" it.
2)
The pointers "ptr1" and ptr2" are pointing to the same place. We need to only delete it once and not delete it twice.
This works.
delete ptr1 ;
// delete ptr2 ;
The below also works because both "ptr1" and "ptr2" contain the same address and the "delete" function works with the value in the variable and that is an address .
//delete ptr1 ;
delete ptr2 ;
3)
1 2 3
4 5 6
3)
#include <iostream>
using namespace std ;
int main()
{
int* arrayOfGrades ;
int numberOfStudents = 0 ;
while ( numberOfStudents < 1 || numberOfStudents > 10 )
{
cout << "Enter the number of students:" ;
cin >> numberOfStudents ;
}
//To do
//Create a dynamic array with the size as numberOfStudents
//and assign the address to arrayOfGrades
arrayOfGrades = new int[ numberOfStudents ] ;
//Run a loop asking the user to enter a grade for each student
for( int i1=1 ; i1<=numberOfStudents ; i1++ )
{
cout << "Enter the grade for student " << i1 << ":" ;
cin >> arrayOfGrades[ i1-1 ] ;
}
//Print the contents of the array
cout << "Grades entered." << endl ;
for( int i1=0 ; i1<numberOfStudents ; i1++ )
{
cout << arrayOfGrades[ i1 ] << " " ;
}
//Free the memory
delete[] arrayOfGrades ;
return(0) ;
}
4)
#include <iostream>
using namespace std ;
int main()
{
int arr1[3][3] = { {1,2,3} , {4,5,6 }, {7,8,9 } } ;
int ROWS = 3 ;
int COLS = 3 ;
int i1 = 0 ;
int i2 = ROWS-1 ;
while ( i1 < i2 )
{
//Exchange rows i1 and i2
//cout << "i1:" << i1 << " i2:" << i2 << endl ;
for(int j1=0 ; j1 < COLS ; j1++ )
{
int temp = arr1[i2][COLS-j1-1] ;
//cout << "temp:" << temp << " " ;
arr1[i2][COLS-j1-1] = arr1[i1][j1] ;
arr1[i1][j1] = temp ;
} //for
i1++ ; i2-- ;
} //while
/*
if ( i1 == i2 )
{
int k1=0 , k2=COLS-1 ;
while ( k1 < k2 )
{
int temp ;
temp = arr1[i1][k1] ;
arr1[ i1 ][k1] = arr1[ i1 ][k2] ;
arr1[ i1 ][k2] = temp ;
k1++ ; k2-- ;
} //while
} //if */
for( int i1=0 ; i1<ROWS ; i1++ )
{
for( int j1=0 ; j1<COLS ; j1++ )
{
cout << arr1[ i1 ] [ j1] << " " ;
}
cout << endl ;
}
return(0) ;
}
Exercises
Ex1:
Explain the below program:
/* What is a pointer */
#include<stdio.h>
#include <iostream>
using namespace std ;
main()
{
//Double ptr or an array of pointers
const char *colors[] = { "Red", "Green", "Blue" } ;
cout << colors[1] << endl ;
cout << ( colors + 1 ) << endl ;
cout << *( colors + 1 ) << endl ;
cout << *( colors[1] ) << endl ;
cout << "----------------" << endl ;
char** colors1 = new char*[3] ;
colors1[0] = "Red" ;
colors1[1] = "Green" ;
colors1[2] = "Blue" ;
cout << colors1[1] << endl ;
cout << ( colors1 + 1 ) << endl ;
cout << *( colors1 + 1 ) << endl ;
cout << *( colors1[1] ) << endl ;
}
Output:
Green
0x7ffe25a3ff18
Green
G
----------------
Green
0x12edc28
Green
G
File: "ptr6.cpp"
#include <iostream>
using namespace std ;
void function1( int arr2[] )
{
arr2[0] = 100 ;
}
void printArray( int arr1[] , int size )
{
for( int i1=0 ; i1<size ; i1++ )
{
cout << arr1[i1] << " " ;
}
cout << endl ;
}
int main()
{
int arr1[] = { 10, 20, 30 } ;
function1( arr1 ) ;
printArray( arr1, 3 ) ;
return 0 ;
}
Array and pointers are almost the same in that they both contain an address. If we pass an array as an argument to a function and the function changes something in the array then that change is reflected in the calling function. Same behavior is exhibited when we pass a pointer to a function and the function manipulates the value the pointer points to.
Ex:
#include <iostream>
using namespace std ;
void function1( int arr1[] )
{
cout << "function1:" << arr1[0] << endl ;
}
void function2( int* arr1 )
{
cout << "function2:" << arr1[0] << endl ;
}
void function3( int arr1[][3] )
{
cout << "function3:" << arr1[0][0] << endl ;
}
void function4( int** arr1 )
{
cout << "function4:" << arr1[0][0] << endl ;
}
int main()
{
//Single Dimensional Arrays
int array1[10] = { 1,2,3,4,5,6,7,8,9,10} ;
function1( array1 ) ;
//Ok to pass an array to a pointer
function2( array1 ) ;
int* ptr1 = new int[10] ;
ptr1[0] = 1 ; ptr1[1] = 2 ;
//Ok to pass a pointer to an array
function1( ptr1 ) ;
function2( ptr1 ) ;
//Two Dimensional Arrays
int array2[2][3] = { {1,2,3} , {4,5,6} } ;
int** TwoDimArray ;
const int ROW_COUNT = 2 ;
const int COL_COUNT = 3 ;
TwoDimArray = (int**)malloc(sizeof(int*)*ROW_COUNT) ;
for(int i1=0 , k1=1 ; i1<ROW_COUNT ; i1++ )
{
TwoDimArray[i1] = (int*) malloc(sizeof(int)*COL_COUNT) ;
for( int j1=0 ; j1<COL_COUNT ; j1++ )
{
TwoDimArray[i1][j1] = k1++ ;
}
} //for
function3( array2 ) ;
// Compiler error //
function4( array2 ) ;
// Compiler error
//function3( TwoDimArray ) ;
function4( TwoDimArray ) ;
return 0 ;
}
Output:
function1:1
function2:1
function1:1
function2:1
function3:1
function4:1
The above code illustrates how single and two dimensional arrays can be passed to functions. With a single dimensional array we can pass an array variable to a function taking an array argument or a pointer argument. After all arrays and pointers hold addresses and we should be able to use either one.
The declarations for "functions1" and "function2" are:
void function1( int arr1[] )
void function2( int* arr1 )
We are passing the below to both the functions:
int array1[10] = { 1,2,3,4,5,6,7,8,9,10} ;
function1( array1 ) ;
//Ok to pass an array to a pointer
function2( array1 ) ;
int* ptr1 = new int[10] ;
ptr1[0] = 1 ; ptr1[1] = 2 ;
//Ok to pass a pointer to an array
function1( ptr1 ) ;
function2( ptr1 ) ;
In the first case we have an array variable and we can pass it to both the functions and in the second case we have a pointer that we can pass to both functions . The code compiles and the first element in the block is printed in both cases.
Now we have the 2 dimensional array case:
void function3( int arr1[][3] )
{
cout << "function3:" << arr1[0][0] << endl ;
}
void function4( int** arr1 )
{
cout << "function4:" << arr1[0][0] << endl ;
}
We can see that the notation to access the 2 dimensional arrays is the same "arr1[0][0]" . So we expect the functions to take either a 2 dimensional array or a double pointer. But that is not the case.
function3( array2 ) ;
// Compiler error
// function4( array2 ) ;
// Compiler error
//function3( TwoDimArray ) ;
function4( TwoDimArray ) ;
If we try to pass a 2 dimensional array to a double pointer then we receive a compiler error and if we try to pass a double pointer to a 2 dimensional array then also we receive a compiler error. Why does the compiler not allow the conversion. To understand this we need to understand how the 2 types are stored in RAM.
int array2[2][3] = { {1,2,3} , {4,5,6} } ;
int** TwoDimArray ;
RAM
array2 0 100
100 1
2
3
4
6
RAM
TwoDimArray 0 100
100 Address of first row
200 Address of second row
If we have a 2 dimensional array then we the elements are stored in a linear fashion. We must know the number of columns in order to find out when the rows start. However with a double pointer we store a block of addresses and not the actual elements .
We follow the address to the block and then grab the elements from the block. If
function4( array2 ) ;
did compile then things will not work as expected. Remember "array2" is actually a 2 dimensional array.
Then function4 converts it to a double pointer and then we do arr1[0][0] .
Now the first "arr1[0]" gives us the element "1" but the argument was a double pointer. Now the compiler is going to go to the block with the memory address of "1" . However "1" is the actual element and not an address. We cannot convert between a legitimate 2 dimensional array and a double pointer. Compiler is going to get confused because they store things in a different fashion in the RAM.
The "typedef" keyword can be used to assign another name for a type declaration. This is often used in "C++" to make declarations easier to use. Ex:
#include<stdio.h>int main(){ int x1 ; int* ptr1 ; x1 = 100 ; printf("x1 value is %d\n" , x1 ); ptr1 = &x1 ; printf("%d \n" , *ptr1 ); return 0 ;
}
Output:
100
100
In the above example we are using an integer type and a pointer to an integer type. We can rewrite the above program as:
#include<stdio.h>int main(){ typedef int INTEGER ; typedef int* POINTER_TO_INTEGER ; INTEGER x1 ; POINTER_TO_INTEGER ptr1 ; x1 = 100 ; printf("x1 value is %d\n" , x1 ); ptr1 = &x1 ; printf("%d \n" , *ptr1 ); return 0 ;
}
The above example shows the declaration of "typedef" and it's usage. It's as if we had new types.
We have seen examples of a pointer pointing to an integer but we can also have a pointer pointing to a function.
Ex:
#include<stdio.h>void function1( int param1 ){ printf( "Am inside function1: %d \n" , param1) ;}void function2( int param1 ){ printf( "Am inside function2: %d \n" , param1) ;}int main(){ void (*POINTER_FUNCTION)(int a) ; POINTER_FUNCTION = function1 ; POINTER_FUNCTION(100) ; POINTER_FUNCTION = function2 ; POINTER_FUNCTION(200) ; return 0 ;
}
Output:
Am inside function1: 100
Am inside function2: 200
The declaration:
void (*POINTER_FUNCTION)(int a) ;
states that the "POINTER_FUNCTION" is a pointer to a function. However it is not just any function but a function with a particular signature. The function must take an int as an argument and return void.
The "POINTER_FUNCTION" is actually a variable and we can assign the address using the form:
POINTER_FUNCTION = function1 ;
and then invoke the pointer using :
POINTER_FUNCTION(100) ;
This ends up calling the function with an argument value of 100 . We can change the value that the pointer holds and assign the value of function 2 to it .
Ex:
#include<stdio.h>void function1( int param1 ){ printf( "Am inside function1: %d \n" , param1) ;}void function2( int param1 ){ printf( "Am inside function2: %d \n" , param1) ;}int main(){ void (*POINTER_FUNCTION)(int a) ; POINTER_FUNCTION = function1 ; (*POINTER_FUNCTION)(100) ; POINTER_FUNCTION = function2 ; (*POINTER_FUNCTION)(200) ; return 0 ;
}
The above shows the syntax "(*POINTER_FUNCTION)(100)" that can be used to call the function also .
Ex:
#include<stdio.h>void function1( int param1 ){ printf( "Am inside function1: %d \n" , param1) ;}void function2( int param1 ){ printf( "Am inside function2: %d \n" , param1) ;}int main(){ typedef void (*POINTER_FUNCTION_TYPE)(int a) ; POINTER_FUNCTION_TYPE ptr = function1 ; ptr(100) ; ptr = function2 ; ptr(200) ; return 0 ;}
The notation
void (*POINTER_FUNCTION)(int a) ;
is kind of confusing . We are used to saying "int x1" where we have a type and then a variable of that type. We can use "typedef" to rewrite out program.
Ex:
#include<stdio.h>void function1( int param1 ){ printf( "Am inside function1: %d \n" , param1) ;}void function2( int param1 ){ printf( "Am inside function2: %d \n" , param1) ;}int main(){ typedef void (*POINTER_FUNCTION_TYPE)(int a) ; POINTER_FUNCTION_TYPE ptr = function1 ; ptr(100) ; ptr = function2 ; ptr(200) ; return 0 ;}
Using "typedef" above we define a type "POINTER_FUNCTION_TYPE" and then we can use this type to create a variable "ptr". This variable can hold the address of a function and is a function pointer.
Let us look at the following program:
File: const2.cpp
#include<stdio.h>int main(void){ int var1 = 0, var2 = 20; //Constant Pointer //The ptr is a constant . I cannot change the value of the pointer. int* const ptr1 = &var1; //Can't do this //ptr1 = &var2; //or this //int *const ptr2 ; //Can change the value of what the pointer points to *ptr1 = 100 ; printf("%d\n\n", *ptr1); return 0;}
We have the concept of a constant pointer. It is defined as:
int* const ptr1
What this is saying is that the variable "ptr1" is a constant and we cannot change it's value but we can change the value of what it's pointing to.
So we cannot do this:
ptr1 = &var2
but we can do
*ptr1 = 100
Let us build a RAM diagram:
RAM
var1 10 0 -->But we can change this value
ptr1 100 10 --> Can't change this value
We can't do :
int* const ptr2 ;
Since the variable ptr2 is a constant we must assign a value to it once we define it. We can also have the other case of having the ability to change a pointer but not change the value of what it is pointing to.
File: const3.cpp
#include<stdio.h>int main(void){ int var1 = 0, var2 = 20; const int* ptr1 = &var1; //Not allowed //*ptr1 = 1; //Allowed ptr1 = &var2 ; printf("%d\n", *ptr1) ; return 0;}
The const is to the left of the pointer. This means the variable "ptr1" can be changed but the contents cannot be changed. Finally we have the case where the pointer or what the pointer points to cannot be changed.
File: const4.cpp
#include<stdio.h>int main(void){ int var1 = 0, var2 = 20; const int* const ptr1 = &var1; //Not allowed //*ptr1 = 1; // Not Allowed // ptr1 = &var2 ; return 0;}
We cannot change the value of what the pointer points to :
*ptr1 = 1
and we cannot change the value of the pointer:
ptr1 = &var2
Constant pointers are usually used in arguments to functions when we do not want the function to change the original value of what the pointer points to.
Ex: "const5.cpp"
#include<stdio.h>#include<iostream>using namespace std ;void function1( const char* str1 ){ cout << str1 << endl ; *str1 = "Something Else." ;}int main(void){ char buffer[] = "Testing" ; function1( buffer ) ; return 0;}
Output:
const5.cpp: In function ‘void function1(const char*)’:
const5.cpp:9:13: error: assignment of read-only location ‘* str1’
*str1 = "Something Else." ;
^~~~~~~~~~~~~~~~~
const5.cpp:9:13: error: invalid conversion from ‘const char*’ to ‘char’ [-fpermissive]
1)
What will be the output of the following program ?
#include <iostream>using namespace std;int main(){ int var1 = 32, *ptr = &var1 ; char ch = 'A', &cho = ch ; cho += var1 ; *ptr += ch; cout << var1 << ", " << ch << endl; return 0;}
You can use the asciii chart at:
2) What is the output of the following program ? Does it compile correctly ?
#include <iostream>using namespace std;int main(){ const int i1 = 20; const int* const ptr = &i1 ; (*ptr)++; int j1 = 15; ptr = &j1 ; cout << i1 ; return 0;}
3) What is the output of the following program ?
#include <iostream>using namespace std;int main(){ int num[5]; int* ptr ; ptr = num; *ptr = 10; ptr++; *ptr = 20; ptr = &num[2]; *ptr = 30; ptr = num + 3; *ptr = 40; ptr = num; *(ptr + 4) = 50; for (int i1 = 0; i1 < 5; i1++) cout << num[i1] << ", "; return 0;}
4) What is the output of the following code ?
#include <iostream>using namespace std;int main(){ int arr[] = { 4, 5, 6, 7 } ; int* ptr = (arr + 1) ; cout << *arr + 10; return 0;}
5) What is the output of the following code ? Does it compile correctly ?
#include <iostream>using namespace std;int main(){ int a1 = 10, *pa, &ra ; pa = &a1 ; ra = a1 ; cout << "a1=" << ra ; return 0;}
6) What is the difference between
int (*ptr)[5] ;
and
int *ptr[5] ;
Show how the above are used by creating small sample programs .
7)
What is the output of the following program ?
#include <stdio.h>int main(){ int *ptr ; int x1 ; ptr = &x1 ; *ptr = 0 ; printf(" x1 = %d\n", x1) ; printf(" *ptr = %d\n", *ptr) ; *ptr += 5; printf(" x1 = %d\n", x1) ; printf(" *ptr = %d\n", *ptr) ; (*ptr)++ ; printf(" x1 = %d\n", x1) ; printf(" *ptr = %d\n", *ptr) ; return 0;}
8) What is the output of the following program ?
#include <stdio.h>int main(){ float arr[5] = {12.5, 10.0, 13.5, 90.5, 0.5}; float *ptr1 = &arr[0]; float *ptr2 = ptr1 + 3; printf("%f ", *ptr2); printf("%d", ptr2 - ptr1); return 0;}
9)
#include<stdio.h>int main(){ int arr[] = {10, 20, 30, 40, 50, 60}; int *ptr1 = arr; int *ptr2 = arr + 5; printf("Number of elements between two pointer are: %d\n", (ptr2 - ptr1)); printf("Number of bytes between two pointers are: %d\n", (char*)ptr2 - (char*) ptr1); return 0;}
10)
What would be printed from the following C++ program?
#include <iostream>using namespace std ;int main(){ int x1[5] = { 1, 2, 3, 4, 5 }; // ptr points to array x1 int* ptr = x1; int i1; // exchange values usi1ng pointer for (i1 = 0; i1 < 2; i1++) { int temp = *(ptr + i1) ; *(ptr + i1) = *(ptr + 4 - i1) ; *(ptr + 4 - i1) = temp ; } // output the array x1 for (i1 = 0; i1 < 5; i1++) cout << x1[i1] << " "; return 0;}
11) What does the following print ?
#include <iostream>using namespace std ;//-----------------------------------------------------------int main(){ char c = 'T', d = 'S'; char *p1 = &c; char *p2 = &d; char *p3; p3 = &d; cout << "*p3 = " << *p3 << endl ; p3 = p1; cout << "*p3 = " << *p3 << endl ; *p1 = *p2; cout << "*p1 = " << *p1 << endl ; return( 0 ) ;}//-----------------------------------------------------------
6)
#include <iostream>using namespace std;int main(){ //Almost same as int* ptr but can only //point to a block of 5 integers int (*ptr1)[5] ; //An array of 5 pointers int *ptr2[5] ; int x1 = 100 ; int arr1[5]={ 1, 2, 3, 4, 5 } ; ptr1 = &arr1 ; ptr2[0] = &x1 ; return 0;}
7)
$ ./a.exe
x1 = 0
*ptr = 0
x1 = 5
*ptr = 5
x1 = 6
*ptr = 6
8)
$ ./a.exe
90.500000 3