pointers

Pointers, New, and Delete

A data type has a set of values and a set of operations that can be performed on these values. Data types also typically have a name, such as double or int. The pointer data type, however, does not have a name; instead, it is represented by an asterisk. The values that belong to a pointer data type are the memory addresses of the computer. A pointer variable is thus a variable whose content is a memory address.

In C++, we declare a pointer variable by placing the asterisk symbol between the data type and the variable name. The value of a pointer variable is an address of another memory space; when we declare a pointer variable, we also specify the data type of the value stored in this other memory space.

int main()
{
	//pointer to an address storing an int
	int * iPtr;
	//pointer to an address storing a char
	char* chPtr;
	//pointer to an address storing a double
	double *dPtr;

    return 0;
}

Note that the asterisk can appear anywhere between the data type name and the variable name.

The asterisk symbol does double duty as both the stand-in for the pointer data type name, as well as the dereferencing operator (it is also the symbol for multiplication as well). The address of operator is represented by the & symbol.

The address of operator, &, is used to get the memory address of a variable. The dereferencing operator, *, is used to retrieve the value stored at the memory address that the pointer points to. The derefencing operator can also be used to store a new value in the memory address that is pointed to by the pointer.

using namespace std;

int main()
{
	int i = 42;
	int *iPtr = &i;

	cout << i << " is at " << &i << endl;
	cout << iPtr << " is at " << &iPtr << endl;

	cout << "The pointer stored at " << &iPtr << " stores the memory address " << iPtr << endl;
	cout << " which stores the value " << *iPtr << endl;

    return 0;
}

It is of course possible to have multiple pointer variables point to the same memory location. Likewise, we can even have pointers that point to other pointers!

using namespace std;

int main()
{

	double dOne = 19.99;
	
	double *dPtrOne = &dOne;
	double *dPtrTwo = &dOne;

	double **doubledPtr = &dPtrOne;

	cout << "dPtrOne is at " << &dPtrOne << " and stores the memory address " << dPtrOne << " which stores the value " << *dPtrOne << endl;
	cout << "dPtrTwo is at " << &dPtrTwo << " and stores the memory address " << dPtrTwo << " which stores the value " << *dPtrTwo << endl << endl;

	//we can change the value pointed to using either pointer
	*dPtrOne = 3.14159;
	//derefencing either pointer yields the changed value
	cout << "dPtrTwo points to the value " << *dPtrTwo << endl;

	cout << "doubledPtr is at " << &doubledPtr << " and stores the value " << doubledPtr << " which pointers to the value " << *doubledPtr <<  endl << endl;

	if (*doubledPtr == dPtrOne) {
		cout << "*doubledPtr and dPtrOne are the same" << endl;
	}

	if (*doubledPtr == &dOne) {
		cout << "*doubledPtr and &dOne are the same" << endl;
	}

	if (**doubledPtr == *dPtrOne) {
		cout << "**doubledPtr and *dPtrOne are the same" << endl;
	}
    return 0;
}

We can also create pointers that point to other data types like structures or classes.

int main()
{

	struct exampleStruct {
		int a;
		char b;
		double c;
	};

	struct exampleStruct exmp;
	struct exampleStruct *exmpPtr = &exmp;

	exmp.a = 73;
	exmp.b = 'z';
	exmp.c = 25.806975;

	cout << (*exmpPtr).a << endl;
	cout << (*exmpPtr).b << endl;
	cout << (*exmpPtr).c << endl;

    return 0;
}

Note that in C++ the dot operator has a higher precedence than the dereferencing operator. Thus, when dereferencing pointers to structs or class objects, we need to use parentheses to ensure that the pointer is dereferenced before the dot operator, or we can use the alternative arrow operator, ->, officially called the member access operator arrow.

We can allocate  and deallocate memory during the program’s execution using pointers. C++ provides two operators, new and delete, to create and destroy dynamically allocated variables. In C++, new and delete are reserved words.

The operator new can both allocate single variables as well as arrays of variables. The operator new allocates the memory for the designated type and returns a pointer to this memory.  Because dynamic variables can’t be accessed directly as they are not named, they must instead be accessed via the pointer that references the memory location where the dynamic variable is.

using namespace std;

int main()
{

	int *iPtr;
	double *dPtr;
	char *chPtr;

	iPtr = new int;
	dPtr = new double;
	chPtr = new char[64];

	*iPtr = 404;
	*dPtr = 169.254;

	strcpy_s(chPtr, 64, "Saluton Mundo!");

	cout << chPtr << endl;
	//note: not the same as
	cout << *chPtr << endl;

	cout << *iPtr << " " << *dPtr << endl;


    return 0;
}

The delete operator is used to free up memory allocated with new. It’s very important to free up the memory we use after we are done with it. Why? Because once the pointer to a block of memory goes out of scope, or is assigned a new block of memory, we no longer have a means of accessing the previously allocated memory. But it’s still there on the heap, taking up space. Such a situation is called a memory leak.

int main()
{
	double *dPtr = new double;

	*dPtr = 1.645;

	cout << *dPtr << "\t(" << dPtr << ")" << endl;
	
	//assign it new memory
	dPtr = new double;
	*dPtr = 1.96;

	//previously allocated memory is now lost
	//note the new address
	cout << *dPtr << "\t(" << dPtr << ")" << endl;

    return 0;
}

Pointers and References in C++

When a variable is declared, three attributes are associated with it: its name, its type, and its address in memory. As an example, the declaration double dValue; associates the name dValue, the type double, and the address of a location in memory where the value is to be stored. As we can see, the declaration statement provides the type and symbolic name for the value, and it also causes memory to be allocated to store the value and to keep track of the memory location internally.

The value of a variable is accessed via its name, we can access the memory address itself by means of the address operator, &.

#include <iostream>

using namespace std;

int main(void){

    double d = 169.254;
    char c = 't';

    cout << "c = " << c;
    cout << " and is at " << &c << endl;

    cout << "d = " << d;
    cout << " and is at " << &d << endl;

    return 0;

}

We can create pointers in C++ the same as in C. A pointer is a special type of variable that holds an address of another variable. Thus, the value stored in the pointer variable is the address of another variable. We declare a pointer using the * symbol, which is also used to dereference the pointer variable to get at the data stored in the location that it is pointing to.

The pointer variable must have a declared type; this is because different value types use different numbers of bytes and different internal formats for storing numbers.

#include <iostream>

using namespace std;

int main(void){

    int iValue = 42;

    //pointer to int
    int *iPtr;
    
    //assign a memory location
    //to the pointer variable
    iPtr = &iValue;

    cout << &iValue << " " << iPtr << endl;

    return 0;

}

We can think of a pointer as a locator, it tells us where we can locate another variable.

To get the value to which the pointer points, we use the dereference operator, *. We can think of the dereference, *,  operator and the address of operator, &, as being inverses of each other.

We can use the pointer to change the value stored in the original value; in C, using pointers is the method by which we persistently modify variables in other functions. To do this, we must use the dereference operator and the assignment operator together.

#include <iostream>

using namespace std;

int main(void){

    int iValue = 404;
    //declare pointer to ivalue
    //using address of operator
    int *iptrValue = &iValue;

    cout << "iValue = " << iValue << endl;
    cout << "*iptrValue = " << *iptrValue << endl << endl;

    cout << "iValue is at " << &iValue << endl;
    cout << "iptrValue points to " << iptrValue << endl << endl;

    //increment has precedence over 
    //dereference     
    (*iptrValue)++;

    cout << "iValue = " << iValue << endl;


    *iptrValue = 4004;

    cout << "iValue = " << iValue << endl;

    return 0;

}

Array names have the unusual property of devolving into pointers. An array name without an index is, in essence, a pointer to the address of the start of the array. The index number specifies the offset from this base address, each offset is arrived at by multiplying the index number by the size of one element in the array.

#include <iostream>

using namespace std;

int main(void){

    int iArray[5] = { 4, 8, 15, 16, 23};

    cout << iArray << endl;
    cout << iArray + 1 << endl;
    cout << iArray + 2 << endl;
    cout << iArray + 3 << endl;    
    cout << iArray + 4 << endl;

    return 0;

}

Although pointers are expressed as integers (in hexadecimal form, usually), they are not integer types. We can, however, apply some integer arithmetic operators to pointers; this is known as pointer arithmetic. For instance, pointers can be incremented and decremented just as integers can. However, the increase or decrease in the pointer’s value is not necessarily one, but rather the size of the memory the pointer’s type occupies.

#include <iostream>

using namespace std;

int main(void){
    
    const int array_bounds = 7;

    int iArray[array_bounds] = {1, 5, 7, 13, 17, 19, 23};

    //end of the array
    int *iPtrArrEnd = iArray + array_bounds;
    int *iPtrArrStart = iArray;

    while(iPtrArrStart < iPtrArrEnd){
        cout << *iPtrArrStart << " at " << iPtrArrStart << endl;
        iPtrArrStart++;
    }
    return 0;

}

One of the main uses for pointers is allocating unnamed memory during runtime, where pointers are the only way to access that memory. In C++, we use the new operator to allocate memory. We tell new what data type we want memory for, new finds a block of the appropriate size, and then returns the address of the block. We then assign this address to a pointer.

The delete operator reverses the action of the new operator, it frees the allocated memory. The delete operator should only be applied to pointers that have been allocated by the new operator.

#include <iostream>

using namespace std;

int main(void){

    int * iPtr = new int;
    double * dPtr = new double;

    *iPtr = 1138;
    *dPtr = 255.255;

    cout << *iPtr << " " << *dPtr << endl;

    delete iPtr;
    delete dPtr;

    return 0;

}

Note that in the unlikely event that there is not enough memory for new to allocate a block of the required size, the new operator will return 0.

A reference is an alias, a synonym for another variable. A reference, unlike a pointer, must be assigned another variable when it is declared.  We use the address operator, &, to assign a reference just as with pointers; however, we append the address operator not to the original variable, but to the variable that is the referring to the original variable.The original variable and the reference thus act as two different names for the same memory location.

#include <iostream>

using namespace std;

int main(void){

    int i = 73;
    //r is a reference to i
    int &r = i;

    cout << "i = " << i << endl;
    cout << "r = " << r << endl;

    i = 47;

    cout << "i = " << i << endl;
    cout << "r = " << r << endl;

    return 0;

}

Some Particulars Concerning Functions in C++

Arguments are passed by value to functions in C. Yes, we can use the address of operator & and pass in a pointer, but note that even in this situation, we are passing the argument by value – it just happens that the value we are passing in is a pointer to the memory location we want to access!

C++ allows for the passing of pointers by value, just as in C. However, there is another method as well, we can use the address operator & to pass the argument by reference. We can also pair the address of operator with the keyword const to make the value passed in by reference immutable within the function.

#include <iostream>

using namespace std;

void exampleOne(int x);
void exampleTwo(int &x);
void exampleThree(const int &x);

int main(void){

    int x = 42;

    exampleOne(x);    
    exampleTwo(x);    
    exampleThree(x);

    return 0;
}

void exampleOne(int x){
    cout << "received :" << x << endl;
    cout << "adding five to x, ";
    x += 5;
    cout << "x now: " << x << endl;
}

void exampleTwo(int &x){
    cout << "received :" << x << endl;
    cout << "adding thirty one to x, ";
    x += 31;
    cout << "x now: " << x << endl;
}

void exampleThree(const int &x){
    cout << "received :" << x << endl;
    cout << "adding twenty seven to x, ";
    cout << "wait... nevermind!" << endl;
}

Note that the implementation of references isn’t exactly specified – it’s left up to the compiler. References are most likely implemented behind the scenes using pointers, which makes passing by reference a form of syntactic sugar. However, there are some key differences between a pointer and a reference. Unlike a C++ reference, a pointer may be null, and it may be reassigned to a new memory address. Also, references need to be initialized when they are defined.

#include <iostream>

using namespace std;

int main(void){

    int x = 404;
    int y = 1138;

    //pointer
    int *xPtr;
    //reference
    int & refX = x;

    cout << "x = " << x << endl;
    //changing the reference changes
    //the original value
    refX++;
    cout << "x = " << x << endl;
    cout << "++refX = " << ++refX << endl;

    xPtr = &x;

    cout << "*xPtr = " << *xPtr << endl;

    xPtr = &y;

    cout << "*xPtr = " << *xPtr << endl;

    return 0;

}

If we declare a function with the qualifier inline before its definition, then it is expanded at every point in the code where it is used, thereby obviating the overhead of a function call and return.

#include <iostream>

using namespace std;

class Dog{
    public:
        inline void bark();
        inline void jump();
};

inline void Dog::bark(){
    cout << "bow wow!!! ";
}

inline void Dog::jump(){
    cout << "(jumps) ";
}

int main(void){

    Dog rufus;

    for(int i = 0; i < 1000; i++){
        rufus.bark();
        rufus.jump();
    }

    return 0;

}

Friend functions are used to allow access to a class by either a different class or a function defined outside the original class. Friend functions do not require that the class using them be related via inheritance. A friend function can have access to the private and protected members of the class.

Friend functions can disrupt the modularity of a class.

Pointers in C++

Variables are like boxes that can be filled with content by the programmer. Following this analogy, the variable has two attributes: the contents of the box, and the box itself. The box itself is the location in memory where the contents are stored. It is possible, however, to make the contents of the variable “box” another variable’s “box”; taxing this metaphor a bit, this is something akin to putting a box inside another box.

A pointer is just such a box – it is a variable whose contents is the memory location of another variable. Pointers allows us to access the values of other variables indirectly, which is especially valuable in C, where all variables are passed by value. In C++, however, it is possible to pass variables directly by reference. Still, pointers play a valuable roll in C++, and are worth understanding

First things first, to access the memory address of a variable, we use the ampersand character in front of the variable’s identifier.

#include <iostream>

using namespace std;

int main(void){

    int x = 73;
    int y = 47;


    cout  <<  "x = "  <<  x  <<  " and is stored at "  <<  &x  <<  endl;
    cout  <<  "y = "  <<  y  <<  " and is stored at "  <<  &y  <<  endl;


    return 0;

}

To create a pointer variable, we place an asterisk in front of the variable name. To assign a variable’s memory location to the pointer, we use the regular assignment operator, aka the equals sign, and place an ampersand in front of the variable we wish to get the address of.

#include <iostream>

using namespace std;

int main(void){

int x;
int *xPtr;

x = 42;
xPtr = &x;

cout << x << " is stored at" << &x << endl;
cout << xPtr << " is stored at " << &xPtr << endl;


return 0;

}

The asterisk does double duty with pointers – first, it is used in the pointer declaration, and second, it is used as an indirection operator to access the value stored in the variable the pointer is pointing too. We can even use the indirection operator to assign a new valuable to a variable via a pointer that points to it.

#include <iostream>

using namespace std;

int main(void){

    int i = 404;
    int *p;

    p = &i;

    cout  <<  *p  <<  " at "  <<  p  <<  endl;
    cout  <<  i  <<  " at "  <<  &i  <<  endl;

    *p = 90210;

    cout  <<  i  <<  " at "  <<  &i  <<  endl;
        
    return 0;

}

The asterisk operator when used as an indirection operator forces the system to first retrieve the contents of the pointer, then access the location whose address it just got from the pointer, and then in some way utilize the data stored at that location.

To dynamically allocate and deallocate memory in C++, we use the new and delete operators. The new operator requests enough memory from the memory manager to store the indicated type. If we want to release the dynamically allocated memory so it can return to the memory pool, we use delete.

#include <iostream>

using namespace std;

int main(void){

    int *a;

    a = new int;

    *a = 1999;

    cout << *a << endl;

    delete a;

    return 0;

}

One of the problems with arrays is that they have a fixed size, which means we must specify the right size for the array in advance. If the size is too big, then the array wastes memory. If the size is too small, the array can overflow, which is real mess. One option is to dynamically allocate the array using pointers and the new operator. We can this use pointer notation to access individual elements in the array.

#include <iostream>

using namespace std;

int main(void){

    int n = 0;
    int i;

    cout << “Enter the size of the array: “;
    cin >> n;

    int *array = new int[n];

    for(i = 0; i < n; i++){
        cout << “Enter the value for element ” << i + 1 << “: “;
        cin >> *(array + i);
    }

    cout << “Printing out the values: ” << endl;

    for(i = 0; i < n; i++){
        cout << *(array + i) << ” “;
    }

    cout << endl;
    
    return 0;

}

Reference variables are used in passing arguments by reference in function calls. A reference variable is implemented as a constant pointer to a variable. Thus, a reference variable must be initialized in its declaration as a reference to a particular variable, and this reference cannot be amended.

Passing by reference is required if we want any changes made to the parameter to persist beyond the lifetime of the called function. We can accomplish this by either using a pointer or with reference variables.

#include <iostream>

void printFunction(int i, int j);
void functionOne(int i, int &j);
void functionTwo(int i, int *const j);

using namespace std;

int main(void){

    int i = 8088;
    int j = 8086;

    printFunction(i, j);

    functionOne(i, j);

    printFunction(i, j);

    functionTwo(i, &j);

    printFunction(i, j);    

    return 0;

}

void printFunction(int i, int j){
    cout << "First parameter = " << i << endl;
    cout << "Second parameter = " << j << endl;
}

void functionOne(int i, int &j){
    i = 42;
    j = 73;
}

void functionTwo(int i, int *const j){
    i = 1861;
    *j = 1865;
}

It is possible to use pointers to functions, as a function, like a variable, has an address in memory. Function pointers are useful in implementing functions that take other functions as arguments.

#include <iostream>

using namespace std;

int square(int n);
int doublePlus(int n);
int sigma(int (*f)(int), int m, int n);


int main(void){

    int (*func)(int);

    int returnValue;

    func = &square;
        
    returnValue = sigma(func, 10, 1);

    cout  <<  "sigma(func, 10, 1) = "  <<  returnValue  <<  endl;
    

    returnValue = sigma(&doublePlus, 10, 1);
    
    cout  <<  "sigma(&doublePlus, 10, 1) = "  <<  returnValue  <<  endl;    

    return 0;

}

int square(int n){
    return n * n;
}

int doublePlus(int n){
    return 2 * n + 1;
}


int sigma(int (*f)(int), int m, int n){
    int returnVal = 0;
    for(; n <= m; n++){
        returnVal += f(n);
    }
    return returnVal;
}

If you can, please take a look at my author’s webpage at www.amazon.com/Al-Jensen/e/B008MN382O/