Introduction
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 .
File: cast1.cpp
x1 = ch1
Casting is necessary when using the "malloc" function as the "malloc" function returns the "void*" .
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.
File: ptr1.cpp
See full image
The above is a conceptual diagram. I like to call them RAM diagrams. 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:
*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: ex1.cpp
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.
File: ex2.cpp
3)
Use the code below to complete the sections in comments.
File: ex3.cpp
4)
What does the following print ?
File: ex4.cpp
Casting
Type casting refers to forcing a variable to be considered as another type from it's original type.File: cast1.cpp
#include<stdio.h> #include <iostream> using namespace std ; int main(void) { int x1 = 100 ; char ch1 = 'a' ; //Explicit Cast ch1 = (char) x1 ; //Implicit cast x1 = ch1 ; cout << "ch1" << ch1 << endl ; cout << "x1" << x1 << endl ; 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*" .
Pointers Basics
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.
File: ptr1.cpp
/* 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 + 1 ; *ptr1 = 300 ; }
See full image
The above is a conceptual diagram. I like to call them RAM diagrams. 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: ex1.cpp
#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.
File: ex2.cpp
#include <iostream> using namespace std ; int main() { char ch = 'a' ; }
3)
Use the code below to complete the sections in comments.
File: ex3.cpp
#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 //by using the addresses at 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 ?
File: ex4.cpp
#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 ; }