Memory and pointers

APM 50179 EP

Storage types

  • static storage
  • thread-local storage
  • automatic storage
  • dynamic storage

Static storage

  • fixed address determined at compile time
  • global static variables are
    • constructed before execution enters main
    • deleted after execution leaves main
  • static variables in a scope are constructed before the execution of this scope.
  • the order of construction is complex
  • no runtime cost to create the storage

appropriate for data that will be used for the whole life of the program

Automatic storage

  • memory reserved by the compiler (stack)
  • exists into their scope
  • deleted when they leave the scope
  • limit to the total amount of memory in the stack

Dynamic storage

  • memory requested at runtime (heap)
  • must be explicitly requested (new)
  • must be explicitly destroyed (delete)
  • the address is determined at runtime
  • no limit to the total amount of memory

static pitfalls

static pitfalls: step 0

Open the static directory.

What kind of variable is r ?

static pitfalls: step 1

Make r static.

What do you observe ?

static pitfalls: step 2

Initialize m_count to 10 in the resource class.

Make m static and r a static member of manager class.

Create a print() function in manager class which prints Hello manager and acquire the resource.

What do you observe ?

static pitfalls: step 3

Remove the static member to the resource.

Create a function class which returns a reference to a static resource created in this function.

Use this function when you want to acquire the resource.

Pointer

A pointer variable is a variable which stores a memory address.

A pointer variable must be assigned to be used.

Pointer: example

                  
double d = 0.5;
double* p = &d;
float* p_f = nullptr;

std::cout << d << std::endl;
std::cout << *p << std::endl;
std::cout << p << std::endl;

std::cout << p_f << std::endl;
std::cout << *p_f << std::endl; // Error: segfault

int i = 4;
double* p2 = &i; // Error: cannot convert from int* to double*
                  
                

Pointer: cast

                  
int i = 4;
int* pi = &i;
// double* d = (double*)pi;
double* d = reinterpret_cast<double*>(pi);
std::cout << i << std::endl;
std::cout << pi << std::endl;
std::cout << d << std::endl;
std::cout << *pi << std::endl;
std::cout << *d << std::endl;
                  
                

Pointer: new and delete

                  
int* i = new int;     // allocates an uninitialized integer
int* i2 = new int(5); // allocates an integer initialized to 5
int* i3 = new int[5]; // allocates an array of 5 integers

delete[] i3;          // free the memory used by the array i3
delete i2;            // free the memory used by i2
delete i;             // free the memory used by i
                  
                

How to allocate and deallocate properly a double**

with nx rows and ny columns.

Pointer: struct or class

                  
struct A
{
  double x;
  double y;
};

A* a = new A;

std::cout << (*a).x << std::endl;
// or
std::cout << a->x << std::endl;

delete a;
                  
                

Pointer: arithmetic


int* ar = new int[10];
// Or
std::vector<int> v(10);
int* ar = v.data();
                

int* ar2 = ar + 2;
std::cout << *ar2 << std::endl;
std::cout << ar[2] << std::endl;
std::cout << (ar2 - ar) << std::endl;

int* ar3 = ar2 - 1;
std::cout << *ar3 << std::endl;
std::cout << ar[1] << std::endl;
                

Implement the palindrome algortihm using two pointers.

The string will be stored in an std::string.

Exceptions


void mean(const std::vector<double>& param)
{
  if(param.size() == 0)
  {
    throw std::runtime_error("param size is 0")
  }
  else
  {
    // ...
  }
}
                

Exceptions


try
{
  std::vector<double> v(0);
  mean(v);
}
catch(std::exception& e)
{
  std::cout << "caught exception - " << e.what() << std::endl;
}
                

Try to compile the example found in the exception directory

and fix it.

Exceptions


void test_resource()
{
  resource r;
  try
  {
    r.acquire();
    if (...)
    {
      r.release();
      return;
    }
    r.print_message();
    r.release();
  }
  catch(std::exception& e)
  {
    std::cout << "exception caught: " << e.what() << std::endl;
    r.release();
  }
}
                

What do you think about this code ?

Extract from the talk of Arthur O'Dwyer at CppCon 2019

Extract from the talk of Arthur O'Dwyer at CppCon 2019

Exceptions - RAII


void test_resource()
{
  resource r;
  resource_guard g(r);
  r.print_message();
}
                

Write what resource_guard should do.

Exceptions - RAII


class resource_guard
{
  public:

    resource_guard(resource& r);
    ~resource_guard();

  private:

    resource& m_r;
};
                

Exceptions - RAII


resource_guard::resource_guard(resource& r)
: m_r(r)
{
  m_r.acquire();
}

resource::~resource_guard()
{
  m_r.release();
}
                

Exceptions - noexcept


void function() noexcept;
                

The noexcept keyword

  • is part of the signature
  • tells the compiler that the function does not raise
  • the compiler does not check the validity of noexcept
  • is mainly used for performance purpose

Memory Management

Who is the owner of this return type ?

Result* make_computation(class_A& a, class_B& b);
                

Memory Management

It could be

Result* make_computation(class_A& a, class_B& b)
{
  Result* r = new Result();
  ...
  return r;
}
                

or


Result* make_computation(class_A& a, class_B& b)
{
  ...
  return a.get_result(b);
}
                

Memory Management

The owner of a resource should be readable and obvious when you read the source code.

This makes it clear who is reponsible for the destruction of the data and to avoid potential memory leakage.

Memory Management

Smart pointers help to specify the ownership.

There are two versions:

  • std::unique_ptr: unique ownership
  • std::shared_ptr: shared ownership

std::unique_ptr


class coord
{
  public:
    coord(double x, double y)
    : m_x(x), m_y(y)
    {}

    void print() const
    {
      std::cout << "coords(" << m_x << ", " << m_y << ")" << std::endl;
    }

  private:
    double m_x, m_y;
};
                

std::unique_ptr


int main()
{
  auto p_1 = std::unique_ptr<double>(new double(4.));
  auto p_c = std::make_unique<coord>(4., 3.);
  auto p_a = std::unique_ptr<double[]>(new double[1000]);

  p_c->print();

  // auto p_2 = p_1; // Error: unique_ptr is not copyable
  auto p_2 = std::move(p_1);
  std::cout << *p_2 << std::endl;
  std::cout << p_1 << std::endl;
}
                

std::shared_ptr


std::shared_ptr<int> p = new int;
std::shared_ptr<int> p2(new int);
auto p3 = std::make_shared<int>();
                

void function()
{
  auto p = std::make_shared<int>(); // ref_count = 1;
  {
    std::shared_ptr<int> p2 = p; // ref_count = 2;
    // ...
  } // p2 destructor is called, ref_count = 1
} // p destructor is called, ref_count = 0, deletes the internal pointer
                

to be used with caution because it is expensive !