Memory Management in C

Programs, program variables and dynamically allocated blocks of data occupy memory within
a computer.  There must be careful management or memory to prevent such things as program
code being over-written by variables which would result in some bizarre behaviour of the program.
This management task is usually undertaken by a combination of the computer operating system, the
compiler/linker and hardware features within the system processor.

Compilers and  memory allocation - a rough guide.



 

PC based compiler/linkers often build memory images of programs which are segmented in
a manner similar to that shown above.  The program's machine code goes in its own segment(s)
of memoery, global data and static data go in a separate data segment.  The stack segment is
used for storage of return addresses during calls to functions as well as local (temporary) variables.
Finally the heap (often the remainder of memory in the PC) is available for use by dynamically
created variables.
 

Global variables.

Global variables are called such because they are available to all functions within a program.  They
come into being (space is allocated to them) when the program is loaded from disk and they are
destroyed when (space is freed) when the program terminates.  Global variables are said to have
global scope  - a term which we will see more of later.  You can create a global variable as follows:
 

int i;    /* i is a global variable */
int main() {
    .
    .
    i = 20;
    .
}
void SomeFunction() {
    i = 21; /* global variables are visible to all functions */
}
 

 Static variables

Static variables are declared within functions.  They are not visible to other functions within
the program.  Memory space is alloacted for static variables when a program is loaded from
disk and is only freed when the program terminates.  If a function stores some data in a
static variable, this data will be available the next time the function is called.  This is in contrast
to the behavior of local variables.  Static variables are declared by preceding the declaration of
the variable with the keyword static as follows:

void SomeFunction() {
    static int i;
    .
    .
    .
}
 

Local (temporary) variables

Local variables are stored on the program stack and are declared as follows:

void SomeFunction() {
    int i;
    .
    .
    .
}
Local variables are declared within a function and are not visible to other functions.
Local variables are assigned memory space when the function is called.  This space
is freed-up when the function terminates.  This is in contrast to the behavior of global
and static variables.  As a consequence of this, you can not use local variables to
store information which may be needed the next time the function is executed.  There
are other consequences also.  Suppose you have a function such as the following:

char *MakeHello() {
    char HelloString[6];
    strcpy(HelloString,"Hello");
    return(HelloString);
}

This function simply creates a string which contains the word "Hello" and returns its
address.  The address returned points to a variable which is stored on the program stack.
The problem with this is that when the function returns, the area occupied by the string
is freed-up and so may be used for storage of return addresses or other local data.  You
can not therefore guarantee that the memory pointed to by the returned address will continue
to contain the characters which make up "Hello".  One way to ensure correct behaviour is
to declare "HelloString" as a static (or even global) variable.  This will ensure that the space
alotted to the string is used by the string and nothing else.  The downside of this?  Global
and static data make poor use of memory.  You might declare a large number of variable
as global which are only used by the program during its initialisation.  These variables, though
unused for the remainder of the program's operation will continue to occupy memory space.
Global variable present a special difficulty in that they become a management headache for the
programmer.  Suppose you create a global variable called i which is used throughout the program
in various loops or other purposes.  How can you be sure of the meaning of the data in this
variable at any one time?  Word of advice: Create global variables sparingly.
 

Dynamically created variables.

Dynamically created variables are stored in the heap: a pool of memory which may be  shared
by all programs running in the computer.  Dynamic variable are useful in circumstances where for
example, you don't know the number of different variables your program might want to manipulate
when you are writing it.  In such circumstance, you insert code into your program which creates
(and destroys) variables on an as-needed basis.  This often causes programmer great difficulty and
it is fair to say that it has been one of the reasons why people dislike C/C++ programming.  Programs
create dynamic program variables by asking the operating system to set aside a portion of memory
for their exclusive use.   If the memory is available, the operating system marks this block of memory
as being "in-use" and owned by the program concerned.  Any other program which either accidentally
or maliciously attempts to read or write to this block of memory without suitable security clearance
will be prevented from doing so by the operating system and the computer processor's  built-in (hardware)
memory protection sub-system.  The picture below illustrates how two programs might access various data
objects within the computer's memory.

Each program has exclusive access to its own dynamically created data which exists inside the
computer's memory heap.  (Note: programs may also decide to share memory objects but this
is a topic for later).

There are various dynamic memory management functions available to the C-programmer.  Microsoft
prototypes these functions in the file "malloc.h".  We will only deal with two of them here: malloc and
free.
malloc is used as follows:

    address of allocated data = malloc(size of data block required expressed in bytes);

Its purpose is to ask the operating system to set aside a block of memory of a particular size.  If the
memory is allocated successfully, the function returns the address of the data block, otherwise it returns
null (or zero).
free operates as follows:
      free(address of block of data which was returned by a previous call to malloc);
Free tells the operating system that the program is finished with the block of memory and that it is ok to release
it for use by other programs.  It is important that your program frees-up all blocks of memory which it
has allocated for itself when it is finished.  Otherwise, you run the risk of using up all of the memory withiin
your computer necessitating a reboot.

Example:

void SomeFunction(void)
{
    int *MyDynamicArray;

    .
    .
    .
    /* create an array of 2000 integers by requesting a block of data
        which is 2000 * the number of bytes in an integer long */
    MyDynamicArray = (int *)malloc(2000*sizeof(int));
    if (MyDynamicArray != NULL)  /* if allocation was successful */
    {
        /* do something with the array of integers */
            .
            .
            .
        /* and don't forget to release the memory when done */
        free(MyDynamicArray);
    }

}