Fundamentals, part 3

Memory management

Understanding memory is perhaps one of the most important topics in computer science

Unfortunately, it is also at the root of many common programming mistakes

In C/C++, understanding memory management is critical to writing efficient programs

Often the amount of memory needed is unknown at “compile-time” (when the program is written/compiled)

Rather memory must be allocated as the program is executing or during “run-time”

Many examples of programs exist where memory must be allocated while the program is running

Consider, for example, a reservation system that must create memory for ticketed passengers

When the system was written, the number of reservations to be made was unknown

Instead for every new passenger, a new ticketed reservation must be allocated and stored in memory

Consider another simple example writing a program that prompts a user for a number of integers to create. We do not know beforehand how many integers will be requested. It could be 1 or 1,000,000 integers after prompting the user. One method would be to create an array to store some maximum number of integers

#define MAX_INT   1000

main()

{

      int iarray[MAX_INT];

     

      ...

}

Although this program would work, it would be an inefficient use of memory if there were only a few integers requested. On the other hand, if 5000 integers were requested, there wouldn’t be enough memory.

 

How can we reserve the exact amount of memory required at runtime?

A program can reserve (or allocate) memory when needed during execution (runtime)

This form of memory allocation is referred to as dynamic memory allocation

Dynamic memory is allocated in a special area of memory called the heap

After allocation, dynamic memory is accessed using pointer variables

In C/C++, system functions are used to allocate memory on the heap

Consider the following example using C system functions

#include <stdio.h>

#include <stdlib.h>   /* contains declaration for malloc */

 

main()

{

  int i, count;

  int *ptr;

 

  /* Prompt user for integer count */

  printf( "Enter desired number lines: " );

  scanf( "%d", &count );

 

  /* Dynamic memory allocation */

  ptr = (int *)malloc( sizeof(int) * count );

 

  /* Do something with the memory */

  for( i = 0; i < count; ++i )

    ptr[i] = i;

 

  /* Print it out */

  for( i = 0; i < count; ++i )

    printf( "ptr[%d]: %d\n", i, ptr[i] );

 

  /* Free the dynamic memory */

  free(ptr);

}

A pointer variable is used to store the address of the memory allocated

  int *ptr;

Memory is allocated on the heap with the following statement

ptr = (int *)malloc( sizeof(int) * count );

malloc allocates a number of bytes on the heap and returns its address

The number of bytes to allocate is passed as the argument to malloc

ptr = (int *)malloc( sizeof(int) * count );

Different machines use a different number of bytes to store variable types

The function sizeof() can be used to return this number for the type passed

In our example, sizeof(int) returns the number of bytes used to store an integer

We then multiply this by the amount requested by the user

sizeof(int) * count

The return address is type-casted to indicate the type the pointer is pointing to

ptr = (int *)malloc( sizeof(int) * count );

We can access this memory with the pointer in much the same way as an array

for( i = 0; i < count; ++i )

    ptr[i] = i;

Pointer arithmetic can also be used to access memory through a pointer (see readings)

Finally, dynamic memory is freed when no longer needed with the function free()

free(ptr);

Some languages, like Java, do not require programmers to free dynamic memory

Also called garbage collection, Java takes care of freeing memory when out of scope

In C and C++, however, we must take care of such garbage collection ourselves

In C++, dynamic memory is managed with the new and delete operators

Consider an example (from Data Modeling w/ structures) defining a data model for a salt solution

Below we dynamically allocate, initialize, print, and delete a single salt solution “object”

By using the term “object”, we refer to memory that has been allocated from our user-defined data type

In other words, an instantiation of our data type has been created and “lives” in memory

#include <iostream>

using namespace std;

 

struct Solution

{

  float vol;                    // volume

  float per;                    // percentage

};

void InitSolution( Solution *m, float vol, float per )

{

  m->vol = vol;

  m->per = per;

}

void PrintSolution( Solution *m )

{

  cout << "Solution:" << endl;

  cout << "  volume: " << m->vol << endl;

  cout << "  percentage: " << m ->per << endl;

}

 

main()

{

  // Dynamically allocate a solution object on the heap

  // Use a pointer to store the address of this object

  Solution *s = new Solution;

 

  // Load the solution object with values

  InitSolution( s, 60, .3 );

 

  // Print the solution object

  PrintSolution( s );

 

  // Delete the solution object

  delete s;

}

We can also re-write our previous example above using the C++ operators:

#include <iostream>

 

main()

{

  int i, count;

  int *ptr;

 

  // Number of lines at runtime

  cout << "Enter desired number lines: ";

  cin >> count;

 

  // Dynamic memory allocation

  ptr = new int[ count ];

 

  // Do something with the memory

  for( i = 0; i < count; ++i )

    ptr[i] = i;

 

  // Print it out

  for( i = 0; i < count; ++i )

    cout << “ptr[“ << i << “]: “ << ptr[i] << endl;

 

  // Free the dynamic memory

  delete[] ptr;

}

We’ll be looking at dynamic memory in more detail in the notes on C++ dynamic memory allocation

 

 

Putting it all together

Let's look at a C/C++ example applying what we've learned about structures, pointers, functions, and memory

Example

 

#include <iostream>

using namespace std;

 

struct Particle

{

  int id;

  float pos[3];

};

 

void InitParticle( Particle *, int, float, float, float );

void UpdateParticle( Particle *, float, float, float );

void PrintParticle( Particle * );

 

main()

{

  int count;

  Particle *p;

 

  cout << "Enter desired number of particles: ";

  cin >> count;

 

  // Dynamically create particles

  p = new Particle[ count ];

 

  // For each particle

  for( int i = 0; i < count; ++i )

    {

      // initialize, print, update, print

      InitParticle( &p[i], i, 0.0, 0.0, 0.0 );

      PrintParticle( &p[i] );

      UpdateParticle( &p[i], float(i), float(i), float(i) );

      PrintParticle( &p[i] );

    }

 

  // Delete particles

  if( p ) delete[] p;

}

 

void InitParticle( Particle *p, int id, float x, float y, float z )

{

  p->id = id;

  p->pos[0] = x;

  p->pos[1] = y;

  p->pos[2] = z;

}

void UpdateParticle( Particle *p, float x, float y, float z )

{

  p->pos[0] += x;

  p->pos[1] += y;

  p->pos[2] += z;

}

void PrintParticle( Particle *p )

{

  cout << "Particle:" << endl;

  cout << "  id: " << p->id << endl;

  cout << "  position: ";

  cout << p->pos[0] << ", " << p->pos[1] << ", " << p->pos[2] << endl;

}

Output

Enter desired number of particles: 3

Particle:

  id: 0

  position: 0, 0, 0

Particle:

  id: 0

  position: 0, 0, 0

Particle:

  id: 1

  position: 0, 0, 0

Particle:

  id: 1

  position: 1, 1, 1

Particle:

  id: 2

  position: 0, 0, 0

Particle:

  id: 2

  position: 2, 2, 2