Skip to main content

Pointers

c++ Programming  

 Introducing Pointers


In order to understand pointers we must first start with an understanding of how computers handle the data for you application.  When you declare a variable in your code, you are providing key pieces of information to the compiler for handling the data stored as a part of that variable.  The data type determines how much memory is requested for that variable.  Once the variable is declared, you don't need to worry about where in memory it is stored.  Simply referring to the variable name in code is sufficient for gaining access to the value stored there.

But let's take this concept a little further and explore memory locations in your code.  Consider the following code:

    int num = 3;

This is a simple variable declaration of type int, called num, that stores the value 3.  The variable num actually references a location in the computer's memory.  The memory location is what gives num an identity.  During an execution of the application code, while num is in scope, it will have one memory address for the entirety of its scope. Most objects in C++ have an identity, which means that they have a unique memory address during their lifetime (scope).

A pointer is simply a variable that holds the memory address of an object in C++.  To use pointers, you create a pointer variable.  As an example, if we want to, we can create a variable to point to our num variable's address in memory by using the following line of code:

    int* pNum = #

In this line of code, int* is referred to as a pointer to an int.  In other words, it will contain the memory address of an int data type.  The variable that will actually hold that memory address is called pNum.   After the assignment operator, we see the use of the & symbol.  the & symbol, in this instance, is known as the  address-of operator and it allows us to get at the memory address for the num variable.

Depending on the documentation you read, you may also find the * used in this manner:

    int *pNum;

In this case, the author of the text is trying to show that pNum is the actual pointer.  Regardless, the concept is the same.  pNum is a pointer an int.

If you compile and run the following code, you will notice the memory address of num output to the console window.  On my first run, the address for num was 001CF79C and on the second run it was 0018FD38.  Subsequent runs will offer up different memory addresses showing how the "identity" of the variable is only good for the duration of its lifetime.  The values you see may be different that these addresses returned for this execution of the program.


    #include "stdafx.h"
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
    
        int num = 3;
        int *pNum = &num;

        cout << pNum << endl;

        return 0;
    }

NOTE: A word of caution when using pointers.  You should always initialize a pointer variable to NULL, nullptr, 0, or with a memory address.   Leaving a pointer variable uninitialized is an error that can result in difficult to find bugs and create security issues for your code.

    int *pNum;  // not recommended
    
    cout << pNum << endl;

Referring to the preceding code, some compilers will not compile the code and will issue a warning or raise an error because of the pointer not being initialized.  For compilers that do not catch this error, you could see a memory address output to the console but you will have no idea what data resides at that memory address.

One final note, when declaring a pointer, ensure that the variable you will be pointing to is the same data type as the pointer you are declaring.  This is important because of the difference in the number of bytes stored in the various data types.  
For more information , you can see:
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
In this video you will briefly understand  pointers..

 The Dereference Operator

This operator is another source of confusion for those who are new to pointers.  The reason is because we are "overloading" the use of the * symbol.  It is used as the pointer symbol and also as the dereference symbol.   So what is a dereference?  Consider the following code sample.

    int num = 3;            // a simple variable holding the value 3
    int *pNum = &num;        // a pointer holding the address of num
    cout << pNum << endl;    // output the memory address of num
    cout << *pNum << endl;    // output the value 3

In the first line, we declare a variable called num and assign it the value 3.  Next we create a pointer *pNum and assign it the memory address of the variable num.  The first cout statement outputs the address of num, because that is what the pointer variable pNum holds.   But the last line outputs the value 3.  Why?  Here is where the power and the danger of pointers starts to become apparent.

Using the dereference operator, you can gain direct access to the underlying value in the variable num.  If you are unsure what this means, lets add another code segment to help clarify.  Copy and paste this code into a C++ app to see it working on your system if you want to test it.

    int num = 3;
    int *pNum = &num;
    cout << pNum << endl;
    cout << *pNum << endl;

    *pNum = 45;
    cout << *pNum << endl;
    cout << num << endl;

What is being demonstrated here is that we are using the dereference operator to change the underlying value of num, indirectly.  The two last cout lines demonstrate that the value of num and *pNum are precisely the same value. You might be thinking at this point that we could simply have changed the value of num directly and you would be correct. But hold that thought for a moment and  we'll revisit the importance of this later in the lesson when we discuss the purpose of pointers and their uses.
  

Why Pointers?


Now that you know what a pointer is, you may be wondering why you need them. One reason for using pointers is application performance.  In our simple examples thus far, the amount of data that we have passed into functions has been small.  But if we were using large data structures, as class files can sometimes be, or if need to perform repetitive operations on lookup tables, then it is far more efficient to pass a pointer, which is just the memory address, than it is to pass the entire data structure.

As you saw in the code example that passed a variable by reference, pointers can also be used to modify the value in the variable passed in.  Once again, the pointer provides a direct link to the underlying value of the variable allowing you to change the value without the overhead of copying the value into the function.

Pointers also allow you to dynamically allocate memory in your application.  You may run across a requirement for this when creating arrays and objects in your C++ code.  Dynamically allocating means that you do not need to know the size of memory that will be needed for an object at compile time but rather that the size will be allocated during runtime of the application. 


 Introducing Reference Types

C++ includes a type known as a reference.  A reference type is simply an alias for another type.  It "refers to" or "references" another type in your code.  A reference type overloads the use of the & operator.  So far you have seen the & operator used to represent the address-of operator for obtaining the address of a variable.  References also use this operator to denote a reference to another variable.

You declare a reference type using a syntax similar to declaring a pointer variable.  That is, you declare the data type of the C++ variable or object that will be referred to, then you use the & character followed immediately by the reference type name.  Finally, it's important to note that when declaring a reference, you must assign it at that time.  It behaves similar to a constant in this sense.  An example demonstrates the declaration.

    int num = 3;
    int &refNum = num;
    int &refNum2;
    cout << refNum << endl;

In the code sample above we create an integer variable called num and assign it a value of 3.  Next we declare a reference called refNum.  The & character tells us this is a reference value.  Note that we immediately assign it to the num variable.  This binds refNum to num.  This reference cannot   be reassigned later in program code.

The third line in the code sample will cause an error because it has not been initialized.  The last line will output the value 3, which is correct because refNum is an alias or reference to num, which holds the value 3.

To see how this affects the original value of num, let's create another small code sample that displays the value for num, modifies it through refNum, then outputs the memory addresses of num and refNum.

    int num = 3;
    int &refNum = num;

    cout << "num contains " << num << endl;
    cout << "refNum contains " << refNum << endl;

    refNum++;    // increment refNum by 1

    cout << "num contains " << num << endl;
    cout << "refNum contains " << refNum << endl;
    cout << "refNum is located at " << &refNum << " and num is located at " << &num << endl;


On my computer, this code produces the following output.

    *num contains 3
    refNum contains 3
    num contains 4
    refNum contains 4
    refNum is located at 0018FE14 and num is located at 0018FE14
    Press any key to continue . . .*

Lines 1 and 2 are now familiar to you as we created and assign num and a reference refNum.  The first two cout lines output the value of num and refNum and you can see that both contain the value 3.  On line 5 we increment refNum, which will also increment num due to refNum being an alias for num.

The remaining lines show us that num was indeed incremented to 4 as was refNum.  The last line outputs the memory address of num and refNum to show that they both point to the same memory location.   As a result, any changes made to refNum affect num.

References are commonly used with parameters to functions.  The default mechanism used by C++ when calling a function, is to pass arguments into the parameters of that function, by value.  This means that only the value is passed.  This has one effect in a program as we see here.

    using namespace std;

    void passByValue(int);

    int main()
    {

        int num = 3;
        cout << "In main()" << endl;
        cout << "Value of num is " << num << endl;

        passByValue(num);

        cout << "Back in main and the value of num is  " << num << endl;


        return 0;
    }

    void passByValue(int num1)
    {
        cout << "In passByValue()" << endl;
        cout << "Value of num1 is " << num1 << endl;

        // modify num1, won't impact num
        num1++;

        cout << "num1 is now " << num1 << endl;
    }

In this sample code, we declare our function prototype before main().  Inside main(), we declare and assign the variable num.  We output a line indicating which function we are in (main()) and then what the value of num is (3).

Next we call the passByValue(int num1) function and pass num as the argument to the num1 parameter.  We print a line indicating that we are inside the passByValue() function and then output the value of num1 to show that indeed has the same value as num (3).

Inside passByValue, we increment num1 and output its new value, which in this case is 4.

Once passByValue() is finished executing, control returns back to main() where we indicate we are back inside the main function and we output the value for num, which is still 3, not 4.

The pass by value behavior shows that we only passed in a copy of the value that is held in num and not a reference to num itself.  Therefore any changes inside the passByValue() function only impact the local variable num1 and not the original variable num.   If we wanted to modify num inside our passByValue function, we would need to pass in a reference, not a value.  The following code sample changes the function name, only to make it logical what the function is doing, and the way the parameter is passed in.

    using namespace std;

    void passByRef(int &num1);

    int main()
    {

        int num = 3;
        cout << "In main()" << endl;
        cout << "Value of num is " << num << endl;

        passByRef(num);

        cout << "Back in main and the value of num is  " << num << endl;


        return 0;
    }

    void passByRef(int &num1)
    {
        cout << "In passByRef()" << endl;
        cout << "Value of num1 is " << num1 << endl;

        // modify num1 which will now change num
        num1++;

        cout << "num1 is now " << num1 << endl;
    }

This produces the following output:

    In main()
    Value of num is 3
    In passByRef()
    Value of num1 is 3
    num1 is now 4
    Back in main and the value of num is  4
    Press any key to continue . . .


Because we passed num as a reference, C++ was able to access the contents of num directly in memory rather than a copy of the value held by num as in the passByValue() example.
For more information , you can see:
C and C++ with Visual Studio: https://aka.ms/edx-dev210.2x-vs02
By this video you will  clearly understand Reference.

 Introducing Memory Management in C++

You should be aware that a computer contains a finite amount of physical memory.  Operating systems manage that memory and are responsible for allocating memory to applications that are currently executing.  Within certain limits, the operating system (OS) can control the amount of memory but at the same time, it still attempts to provide memory to an application when it requests more.  The OS can use various methods such as swapping, where it uses a portion of storage space on the fixed disk to move data and programs out of main memory and to the local disk, in order to permit a running application to have memory resources.   There are limits.
Applications can become memory hogs and cause issues for computer performance when they request and use large amounts of memory.  It takes considerable resources to swap data and programs to the local disk so making an application as efficient as possible in terms of memory usage is an important consideration when coding your app.
Every variable that you create in your code, every object that you instantiate, and every resource that you open, has an impact on computer memory usage and application performance.   Some aspects of your code, such as non-object data types, like int, double, bool, are scoped in a specific way and stored in the logical division of memory known as the stack.  When these variables go out of scope in your code, the memory on the stack is reclaimed automatically.   Not so much with objects.
Each time you instantiate an object in your code, you request memory to be set aside to store that object and all its data, which may include other objects.  Without going into specific details, the system uses a tracking mechanism for objects that are in use in an application.  It does so in order to ensure that the objects are available to your code when needed.  Only when you specifically indicate that an object is no longer required (deleting it in code), does the system release this hold on the memory used for that object.   As a result, if you forget to release object resources in our code, they will continue to use the memory allocated and not release it back to the system. 
A common occurrence, unfortunately, in unmanaged code, is something known as a memory leak.  This is where an application instantiates objects, doesn't release the object when finished, and continues to request memory for more object creation.  Eventually, the system will run out of memory and the OS will halt the application, or the OS may even crash.
This section looks at methods for managing the allocation of memory consuming resources in your code and explains the steps necessary to ensure you are deallocating memory that is no longer required by your application.

Allocating Memory

Allocating memory during runtime of an application is a common requirement.  For example, you may not know how many objects your application will need until the application is running.  Allocating memory to newly created objects at runtime makes use of pointers.  This is the only way to gain access to memory for new objects when your application is running.  A small code sample will serve to illustrate this.

    // declare a pointer to int and allocate space for it
    // with the keyword new
    int *pInt = new int; 

    // declare a pointer to double and allocate space for it
    // with the keyword new
    double * pDouble = new double;

    // store the value 3 in the memory location
    // pointed to by pInt
    *pInt = 3; 

    // store the value 5.0 in the memory location
    // pointed to by pDouble
    *pDouble = 5.0;

In this code sample, we see the use of the keyword new.  We also notice that, unlike in previous code samples with pointers, we didn't assign a variable to the pointer.  Instead, we told the compiler that we wanted to create a pointer to an int and then, when the program runs, to dynamically assign some memory to hold an int value.   We won't know what the memory address is until run time.  We also did the same thing with a pointer to double.

We use the dereference operator to assign a value to the memory location pointed at by pInt and pDouble.  Again, we have no idea what these memory addresses are because they are allocated dynamically during runtime.   The application requests some memory large enough to store an int value in and a double value.   Because an int, at least on the computer used for this course authoring, is 4 bytes and a double is 8 bytes, the operating system allocates 4 bytes for a value to be stored, starting at the memory address in pInt, and 8 bytes for the double value, to be stored in the memory of the computer, starting at the address in pDouble.

It's very important to note, at this point, that pInt and pDouble are both the same size, in this case 4 bytes, but the memory space storing the values are 4 bytes and 8 bytes respectively.  But how can that be when one is an int and the other is a double?

Quite simply, a memory address is a fixed size on a specific computer architecture.  In this case it takes 4 bytes to hold a memory address.  The start of that memory address is what pInt and pDouble point to.  The computer must know the data type in order to work out how much total memory is assigned to that location.

As a concrete example, when this code was executed, the memory address for pInt was set to 00F1FBA8 and the ending memory address was 00F1FBAC and the starting address for pDouble was 00F21330 with an ending address of 00F21338.  Doing a little hexadecimal math we can see that pInt indeed spans 4 bytes and pDouble spans 8 bytes.  Recall that in hexadecimal, we only go as high as 9 then move on to letters so that we would count from the ending 8 in pInt to C as in 9, A, B, C.  That's 4 positions. (in order to get to the ending memory address for these pointers, simply increment the pointer as in pInt++, and the computer will increase the memory address by the number of byes indicated by the data type pointed to).

Releasing Memory (Deallocation)


One final point that we need to make in your use of pointers and dynamic memory allocation is releasing memory.   Each time you allocate memory in your application, it is reserved by the operating system so that other applications cannot access that memory address.  This has security implications as well as importance for separation of application code to prevent system wide crashes if an errant application behaves badly.

If you do not release the memory in your application, the operating system will not reclaim it and this is known as a memory leak.  It is compounded if your application continues to dynamically allocate memory and doesn't release it.

The simple way of releasing your allocated memory is to use the keyword delete.  For the sample code above, you would make use of the delete keyword as demonstrated here.

    // declare a pointer to int and allocate space for it
    // with the keyword new
    int *pInt = new int; 

    // declare a pointer to double and allocate space for it
    // with the keyword new
    double * pDouble = new double;

    // store the value 3 in the memory location
    // pointed to by pInt
    *pInt = 3; 

    // store the value 5.0 in the memory location
    // pointed to by pDouble
    *pDouble = 5.0;

    delete pInt;
    delte pDouble;

We have simply issued the delete keyword in reference to each pointer we have in the application code.  The operating system will now reclaim the memory used by those pointers and our code no longer has a memory leak.   Use whatever method makes sense to you to ensure that memory is released like using a delete keyword for every new keyword.
By this video you will briefly understand  memory allocation.

Dynamic Memory Allocation 

new and delete operators in C++ for dynamic memory


Dynamic memory allocation in C/C++ refers to performing memory allocation manually by programmer. Dynamically allocated memory is allocated on Heap and non-static and local variables get memory allocated on Stack (Refer Memory Layout C Programs for details).

What are applications?

  • One use of dynamically allocated memory is to allocate memory of variable size which is not possible with compiler allocated memory except variable length arrays.
  • The most important use is flexibility provided to programmers. We are free to allocate and deallocate memory whenever we need and whenever we don’t need anymore. There are many cases where this flexibility helps. Examples of such cases are Linked ListTree, etc.

How is it different from memory allocated to normal variables?

For normal variables like “int a”, “char str[10]”, etc, memory is automatically allocated and deallocated. For dynamically allocated memory like “int *p = new int[10]”, it is programmers responsibility to deallocate memory when no longer needed. If programmer doesn’t deallocate memory, it causes memory leak (memory is not deallocated until program terminates).

How is memory allocated/deallocated in C++?

C uses malloc() and calloc() function to allocate memory dynamically at run time and uses free() function to free dynamically allocated memory. C++ supports these functions and also has two operators new and delete that perform the task of allocating and freeing the memory in a better and easier way.
This article is all about new and delete operators.

new operator

The new operator denotes a request for memory allocation on the Heap. If sufficient memory is available, new operator initializes the memory and returns the address of the newly allocated and initialized memory to the pointer variable.

  • Syntax to use new operator: To allocate memory of any data type, the syntax is:

  • pointer-variable = new data-type;
    
    Here, pointer-variable is the pointer of type data-type. Data-type could be any built-in data type including array or any user defined data types including structure and class.
    Example:
    // Pointer initialized with NULL
    // Then request memory for the variable
    int *p = NULL; 
    p = new int;   
    
                OR
    
    // Combine declaration of pointer 
    // and their assignment
    int *p = new int; 
    

  • Initialize memory: We can also initialize the memory using new operator:

  • pointer-variable = new data-type(value);
    Example:
    int *p = new int(25);
    float *q = new float(75.25);
    

  • Allocate block of memory: new operator is also used to allocate a block(an array) of memory of type data-type.

  • pointer-variable = new data-type[size];
    
    where size(a variable) specifies the number of elements in an array.
    Example:
            int *p = new int[10]
    
    Dynamically allocates memory for 10 integers continuously of type int and returns pointer to the first element of the sequence, which is assigned to p(a pointer). p[0] refers to first element, p[1] refers to second element and so on.
    dynamic

Normal Array Declaration vs Using new

There is a difference between declaring a normal array and allocating a block of memory using new. The most important difference is, normal arrays are deallocated by compiler (If array is local, then deallocated when function returns or completes). However, dynamically allocated arrays always remain there until either they are deallocated by programmer or program terminates.

What if enough memory is not available during runtime?

If enough memory is not available in the heap to allocate, the new request indicates failure by throwing an exception of type std::bad_alloc and new operator returns a pointer. Therefore, it may be good idea to check for the pointer variable produced by new before using it program.
int *p = new int;
if (!p)
{
   cout << "Memory allocation failed\n";
}

delete operator

Since it is programmer’s responsibility to deallocate dynamically allocated memory, programmers are provided delete operator by C++ language.

Syntax:

// Release memory pointed by pointer-variable
delete pointer-variable;  
Here, pointer-variable is the pointer that points to the data object created by new.
Examples:
  delete p;
  delete q;
To free the dynamically allocated array pointed by pointer-variable, use following form of delete:
// Release block of memory 
// pointed by pointer-variable
delete[] pointer-variable;  

Example:
   // It will free the entire array
   // pointed by p.
   delete[] p;
// C++ program to illustrate dynamic allocation
// and deallocation of memory using new and delete
#include <iostream>
using namespace std;
int main ()
{
    // Pointer initialization to null
    int* p = NULL;
    // Request memory for the variable
    // using new operator
    p = new int;
    if (!p)
        cout << "allocation of memory failed\n";
    else
    {
        // Store value at allocated address
        *p = 29;
        cout << "Value of p: " << *p << endl;
    }
    // Request block of memory
    // using new operator
    float *r = new float(75.25);
    cout << "Value of r: " << *r << endl;
    // Request block of memory of size n
    int n = 5;
    int *q = new int[n];
    if (!q)
        cout << "allocation of memory failed\n";
    else
    {
        for (int i = 0; i < n; i++)
            q[i] = i+1;
        cout << "Value store in block of memory: ";
        for (int i = 0; i < n; i++)
            cout << q[i] << " ";
    }
    // freed the allocated memory
    delete p;
    delete r;
    // freed the block of allocated memory
    delete[] q;
    return 0;
}

Output:


Value of p: 29
Value of r: 75.25
Value store in block of memory: 1 2 3 4 5 

Comments

Post a Comment

Popular posts from this blog

Function and Objects

C++ Programming   Introducing to Functions Functions are a component of a programming language that permit you to break down the behavior of an application into discreet pieces of functionality, hence the name function.  A function is essentially a block of C++ code that you give a name and then call from other locations in your application, when you want the computer to perform the instructions contained in that function. You create a function by defining it.  The function definition may contain a return type, a function name, parameters to accept incoming arguments, and finally a body which contains the code that will execute as part of that function.  Functions may or may not return a value back to the caller and they may or may not accept arguments passed in to the function. When you move into more advanced C++ programming, you can also overload functions.  This refers to the practice of using the same function name to refer to multip...

C++ Classes

C++ Programming  Introduction to Splitting Class Files Class structure  If an aspect of object-oriented programming is encapsulation, then why would we split our classes into separate files?  Why not keep it all in one if we wish to "encapsulate" all there is about the class? Recall that a part of encapsulation is also data hiding.  Think about your car for a bit as we use it in an analogous way.  A car is a complex device that contains many different components.  You are able to operate your car without the need to understand the complexities of the internal combustion engine or the radio electronics.  You don't need that knowledge to be able to effectively operate the car. Likewise, a programmer using your class files does not need to know how you have implemented your methods to achieve the functionality.  In fact, perhaps you have a proprietary algorithm for a spell checker that is really fast and you don't want any...

Control Statements

C++  Programming  # C++ operators  Because you will start to learn about control statements in C++, it's important to understand the C++ operators that exist in the language first, as they play an important role in control statements. You will work with comparison operators to determine if values are equal, greater, or less than each other.  C++ also allows you to use mathematical operators for incrementing values to help control the number of iterations in a loop.  You can also make use of bitwise operators to speed up some operations in your code.  Operator Description + addition - subtraction * multiplication / division % modulo += (y += x) same as y = y + x -= (y -= x) same as y = y - x *= (y *= x) same as y = y * x ++ increment by 1 -- decrement by 1 == equal to != not equal to > greater than < less than >= greater than or equal to <= less than or equal to && logical AND || logical OR ! logical NOT ...