Skip to content

Pointers ​

Thanks to pointers, it is possible to have two identifiers to the same region in the memory.

But it has consequences.

It's all about memory addresses ​

As seen quickly in primitive types, pointers are meant to hold addresses. One side-effect of it is all pointers has the same size.

As seen in functions, it's possible to get the address of a variable by using the & operator.

The following code samples how to use a pointer to bind its address to a second identifier:

cpp
// pointer-01.cc
#include <cstdio>

int main(int argc, char **argv)
{

  int a = 10;
  int b = 20;
  int *p = &a;

  printf("address of a: %p\n", &a);
  printf("address of b: %p\n", &b);
  printf("address p points to: %p\n", p);

  return 0;
}

It should output something like this:

sombriks@barbatos 0006-pointers $ ./pointer-01 
address of a: 0x7ffe01dcdbbc
address of b: 0x7ffe01dcdbb8
address p points to: 0x7ffe01dcdbbc

But what does it mean? Changes in a will reflect on p and vice versa:

cpp
// pointer-02.cc
#include <iostream>

int main(int argc, char **argv)
{

  int a = 10;
  int b = 20;
  int *p = &a; // declaration and initialization in a single line

  a = 30;
  b = a;
  *p = 11;

  std::cout << "Updated values are: " << std::endl
            << "a: " << a << std::endl
            << "b: " << b << std::endl
            << "*p: " << *p << std::endl;

  p = &b; // now p points to the address of b
  a = 15;
  b = 21;

  std::cout << "Updated values are: " << std::endl
            << "a: " << a << std::endl
            << "b: " << b << std::endl
            << "*p: " << *p << std::endl;

  return 0;
}

Watch out uninitialized and dangling pointers ​

It is unknown behavior to try use pointers which points to no variable. Also, if the variable isn't available anymore, the pointer is invalid as well.

cpp
// pointer-03.cc
#include <cstdio>

int main(int argc, char **argv)
{

  long *p;

  printf("this is illegal: %li\n", *p); // compile warnings, runtime error

  return 0;
}

Dangling pointers are similar, but for some time they are still valid. Manipulate freed memory is considered unknown behavior and might result in runtime errors.

cpp

// pointer-04.cc
#include <cstdio>
#include <cstdlib>

void cleaner(int *dynamicArray)
{
  free(dynamicArray);
}

int main(int argc, char **argv)
{

  int *someInts = (int *)malloc(10 * sizeof(int));

  for (int i = 0; i < 10; i++)
    someInts[i] = i;

  int *p;
  p = someInts;
  for (int i = 0; i < 10; i++)
    printf("%d ", p[i]);
  printf("\n");

  cleaner(someInts);

  for (int i = 0; i < 10; i++)
    printf("%d ", p[i]); // prints illegal memory
  printf("\n");

  for (int i = 0; i < 10; i++)
    p[i] = i; // changes illegal memory

  for (int i = 0; i < 10; i++)
    printf("%d ", p[i]); // prints illegal memory
  printf("\n");

  return 0;
}

Modern, safe approach ​

Modern C++ offers a way to defend the code from such nasty source of bugs. There is smart pointers.

The regular pointer for now on will be called naked-pointer.

Caution when execute the example bellow. It leaks memory on purpose and might freeze your computer!

cpp
// pointer-05.cc
#include <cstdio>
#include <cstdlib>
#include <memory>
#include <cstring>

struct Contact
{
  unsigned long id;
  char name[100];
  char address[300];
};

void nakedLeak()
{ // this leaks memory
  struct Contact *contact = (struct Contact *)malloc(sizeof(struct Contact));
  contact->id = 123;
  strcpy(contact->name, "Someone");
  strcpy(contact->address, "Somewhere");
}

void smartPointer()
{ // this does not
  std::unique_ptr<struct Contact> contact((struct Contact *)malloc(sizeof(struct Contact)));
  contact->id = 123;
  strcpy(contact->name, "Someone");
  strcpy(contact->address, "Somewhere");
}

int main(int argc, char **argv)
{
  unsigned long int i = 100000000, j = 100000000;

  while (i-- > 0)
    smartPointer();
  printf("loop allocating 100000000 times using smart pointer finished\n");

  while (j-- > 0)
    nakedLeak();
  printf("loop allocating 100000000 times using naked pointer finished\n");

  return 0;
}