EOSERV Wiki > Page: Simple Explanation of Pointers

Simple Explanation of Pointers

Some topics I assume you have knowledge of are simple C++ topics (conditional statements, loops, arrays, functions) and more advanced topics such as classes, the STL, and object oriented programming.

Pointers are a very important part of C++. They are a useful tool that allows you to manipulate the memory of a computer dynamically (at runtime).

This short(ish) explanation discusses the assigning memory, creating pointers, allocating new memory, and operators such as *, &, and ->.

Normal memory allocation is done with a data type and an identifier. The memory can be assigned a value at the same time as well.

Here is an example showing declaration of variables (allocation of memory) of ''int'' data types. ''double'' data types are declared and assigned in a single line, single statement format.

int x;
int y;
int w, z;

double zz = 1.05;
double yy = 5.49;
double xx = 2, ww = 6;

Pointers are declared using the * operator. When used as a unary operator (only one operand), it allows for dynamic allocation of a pointer type which is stored in a different part of the computer's memory than a standard variable. It is called a pointer because it essentially 'points' to a different location. The actual variable itself only holds a memory address of the location of that data item.

Since a pointer only holds an address to a chunk of data in memory, we need to know the '''address-of''' operator. This operator is an ampersand, and gives us the address of another variable. It can also be used in method signatures to pass a variable as a reference parameter.

If a variable int x; is stored in memory location 1000, the statement int y = &x; would store 1000 in y. If int x; is stored in memory location 10FFEAB2090774, the statment int y = &x would store 10FFEAB2090774 in y.

int x = 100;
int y = &x;
cout << x << " " << y;
//Output would be "100 100"

Since the address-of operator (the ampersand) provides a way of accessing a variable under multiple names (x and y both refer to the same location), it also allows manipulation of the same location.

int x = 100;
int y = &x;
cout << x << " ";
y = 200;
cout << x;
//Output would be "100 200"

Here is an example that uses the power of reference variables to pass three values back from a function.

#include <iostream>

using namespace std;

int myFunc(int &x, int &y)
{
x = 1;
y = 2;
return 3;
}
int main()
{
int xx, yy;
cout << myFunc(xx,yy) << xx << yy;
return 0;
}
//output is 312

Now that we have discussed references, we are ready to move on to pointers.

A pointer type variable is a regular data type that is marked with an asterisk (*) to indicate that it is a pointer type. Pointers (as previously mentioned) are variables that point to another location.

int x = 5;
int* y = &x;
cout << x << y;
//Output will be "5" and the memory location of x.

We know that a pointer stores a memory location of a data type. The way to access the data that the pointer points to is to again use the asterisk(*) operator in a process called '''dereferencing'''.

int x = 5;
int* y = &x;
cout << x << *y;
//output will be "55"

We know how to create a pointer from existing memory objects. The most confusing part is dealing with allocation of new memory, dynamically (at runtime). We will also need to make sure that memory is properly cleaned up as well. To do this we will need to use the ''new'' and ''delete'' keywords.

New memory is allocated in a space of memory called the 'heap'. Normal memory (what we have already discussed concerning allocation) is called the 'stack'. C++ does not include a garbage collector like Java or other languages that deal with most of the inner workings of dynamic allocation for you. This creates a need for the programmer to take care of deallocation themselves.

Here is an example for allocating an integer (usually 4 bytes).

int *x = new int;

Now this memory is allocated dynamically on the heap, as opposed to on the stack. It must also be cleaned up once you are done with it, this is done using the delete keyword. If the delete keyword is not used, a '''memory leak''' occurs and can cause memory problems on low-memory systems (usually not a problem, but makes for a terrible program/programmer if you can't even solve simple memory leaks).

delete x;

Pointers can also be used with arrays to dynamically allocated the size of a simple array. They are also useful with linked-list type structures (such as PtrList and PtrVector in eoserv) in which a list is a collection of 'nodes' where each node contains a pointer to the next item in the collection.

Here is a more complete example of using pointers to allocate an array where the size of the array is input from the command line.

int arr_size;
cout << "Size of the array: ";
cin >> arr_size;
cout << endl;

int *my_array = new int[arr_size];
for(int i = 0; i < arr_size; ++i)
  my_array[i] = i + 1;

for(int i = arr_size - 1; i >= 0; ++i)
  cout << my_array[i] << " ";

delete [] my_array;
//This example prints out the contents of my_array in reverse order.

From this example, the power of using pointers to manipulate data in a completely new way. A new syntax is presented here: ''delete [] my_array'' where the brackets are between the keyword ''delete'' and the identifier that you want to deallocate. Using the [] brackets there allows us to deallocate all the memory in the array, rather than just the first cell of the array. When deallocating an array, ALWAYS use the [].

Now let's talk about pointers and class objects. Classes usually (if not always) hold instance variables; usually (if not always) under the ''private'' access specifier. When we have a pointer as an instance variable, a couple of problems arise. How do we allocate/deallocate dynamic memory? And how do we dereference the pointer?

The answers are pretty simple and you may have figured them out already. We allocate the memory for the instance variable in the class constructor, and deallocate that memory in the class destructor. As for dereferencing the memory, we simply use the asterisk(*) again to access the data. An example is shown below of a basic class header (.h) and implementation (.cpp).

//file myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
class myclass
{
public:
  myclass(int size);
  ~myclass();
  
  void printdata() const;
private:
  int *data;
  int maxSize;
};
#endif
//file myclass.cpp
#include <iostream>
#include "myclass.h"
myclass::myclass(int size)
{
  if(size < 1 || size > 10)
    size = 10;
  maxSize = size;
  data = new int[size];
  for(int i = 0; i < size; ++i)
    data[i] = size - i;
}
myclass::~myclass()
{
  delete [] data;
}
void myclass::printdata() const
{
  for(int i = 0; i < maxSize; ++i)
    cout << data[i] << endl;
}

The above example is a simple class that holds a pointer to an array where the user is able to specify the size at run time. The memory is allocated and has a method that prints the contents of the array. One thing this example does NOT do, however, is use the asterisk(*) notation for dereferencing. This is because the only pointer is to an array, and using the data[index] notation automatically dereferences the data.

Here is the above ''void printData() const;'' using dereferencing instead of the array subscript notation ([]). It uses a process called ''pointer arithmetic'' which can be dangerous and should be avoided.

void printdata() const
{
  for(int i = 0; i < maxSize; ++i)
    cout << *(data + i) << endl;  
}

What happens is the value of i is added to the memory location of data, essentially doing the same thing as an array subscript. However, it has to be dereferenced in order to not output a memory location, so the asterisk(*) is used.

Pointers to classes are possible as instance variables, they are also possible in other situations. Lets use the above class as a starting point. Here is an example of a main program that uses a pointer to a class, and calls different methods by dereferencing the class object before calling them. It uses a strange notation that combines the * with parenthesis and the member access operator (.).

#include <iostream>
#include "myclass"

using namespace std;

int main()
{
  myclass *object = new myclass(5);
  //object.printdata(); //doesn't work
  //*object.printdata(); //doesn't work because of operator precedence
  
  *(object).printdata(); //or (*object).printdata();, both work
  return 0;
}
//output is 5 4 3 2 1

The above notation is messy. Dereferencing the object every time you want to access a method makes for sloppy, hard to read code. That's where the arrow operator comes in (->). It does the exact same thing as *(object).printdata(); combining the *, (), and . all in one.

object->printdata();
//is the same as
(*object).printdata();

When working with classes, every class has a reference to itself. This is something built into c++. The object is called ''this'', and 'this' is a reserved keyword in c++. 'this' is a pointer, and needs to be dereferenced before use.

this->data[2];
//is the same as
data[2];
//when dealing with member functions in myclass.cpp

When using pointers in the eoserv source, it is important to remember a couple of things. When a pointer is a parameter of a method, DO NOT delete it. Memory will be cleaned up by the emulator in the class destructor. You can have a pointer to a character and then create a new pointer that also points to that character. If you delete one, you are deleting both of them. Let's say you are making a title command:

if (command.length() >= 5 && command.compare(0,5,"title") == 0 && arguments.size() >= 1)
{
   std::string NewTitle = "";
   Character *victim = this->player->character;
   for (int i = 0; i < arguments.size(); ++i)
   {
       NewTitle += arguments[i];
       NewTitle += " ";
   }
   if (message.length() > 32)
   {
       message = message.substr(0, 32);
   }
   victim->title = NewTitle;
   //do not delete victim!
}

EOSERV Wiki > Page: Simple Explanation of Pointers