Memory

In C, all variables are automatic by default. When a variable declaration is encounter during program execution, the appropriate amount of memory is automatically allocated to contain the variable's value. When the program exits the code block containing the variable declaration, the memory is automatically deallocated.

Dynamic memory allocation is used when the programmer needs to control when memory allocation and deallocation occur. Memory leaks are the bane of dynamic memory allocation. All blocks of memory allocated by a program must be deallocated by the program before the program ends or else the program will be leaking memory which is a very bad thing.

The malloc function is used to allocate memory and the free function is used to deallocate memory. The malloc function returns a pointer to the allocated memory block. The free function takes 1 parameter, a pointer to a previously allocated memory block.

The malloc and free functions are described in the integers topic. The automatic and sizeof topics lay the ground work to understanding the appropriate use and coding of the malloc function.

Automatic  |  Size of  |  Integers  |  Characters  |  Structures  |  Manual

Automatic

Programmers expect variables to be created when a program begins execution and for variables to be unavailable after a program ends. But, what is sometimes surprising is that C does this within each code block. Each declaration of int AnInteger; in this program is a totally different variable that comes into existence within a code block and is removed from existence when the code block is exited.

In order to describe what is happening to the various AnInteger variables, they are named:

  • AnInteger main()
  • AnInteger DemoAuto()
  • AnInteger if()

Following each of these AnInteger variables through the code reveals this sequence:

Enter main() AnInteger main() is created and set to 1
Enter DemoAuto() AnInteger DemoAuto() is created set to 2
Enter if() AnInteger DemoAuto() is printed
AnInteger if() is created and set to 3
AnInteger if() is printed
Exit if() AnInteger if() is destroyed
AnInteger DemoAuto() is printed
Exit DemoAuto() AnInteger DemoAuto() is destroyed
AnInteger main() is printed
Exit main() AnInteger main() is destroyed

C automatically handles allocating and deallocating the memory for each variable declared. The programmer is free from worry about memory leaks!

#include <stdio.h>

void DemoAuto(void);

int main(int argc, char *argv[])
{
  int AnInteger;

  AnInteger = 1;
  DemoAuto();
  printf("AnInteger in main() = %d\n", AnInteger);
  return 0;
}

void DemoAuto()
{
  int AnInteger;

  AnInteger = 2;
  if (AnInteger == 2)
  {
    int AnInteger;

    AnInteger = 3;
    printf("AnInteger in an if statement = %d\n", AnInteger);
  }
  printf("AnInteger in DemoAuto() = %d\n", AnInteger);
}
Top

Size of

It is common for a programmer to need to allocate enough memory for a specific number of variables of a specific data type. For example, enough memory for a hundred integers. On most computers, an integer is 4 bytes and so malloc(400) would work just fine. Although, on some computers an integer is not 4 bytes, now what? The sizeof operator solves that problem.

The sizeof operator works with any data type or variable. For example, the expression sizeof(int), yields the number of bytes required to store an integer. Likewise, sizeof(AnInteger) where AnInteger is an integer variable, also yeilds the same answer. Lastly, when specifiying a variable the parentheses are not required. So sizeof(AnInteger) and sizeof AnInteger are both valid.

#include <stdio.h>

int main(int argc, char *argv[])
{
  int AnInteger;

  AnInteger = sizeof(int);
  printf("sizeof(int) = %d\n", AnInteger);

  AnInteger = sizeof(AnInteger);
  printf("sizeof(int) = %d\n", AnInteger);

  AnInteger = sizeof AnInteger;
  printf("sizeof AnInteger = %d\n", AnInteger);

  return 0;
}
Top

Integers

The malloc function takes 1 parameter, the number of bytes of memory to allocate. For example, malloc(4096) will allocate 4096 bytes of memory and return a pointer to that block of memory. It is critical to remember that malloc() returns a pointer to the block of memory it allocated. The statement malloc(100); will promptly cause 100 bytes of memory to be lost, because there is no assignment to a pointer variable and therfore, no pointer to pass to the free function. The result is a memory leak, a very bad thing. Forgeting to call free() and pass the pointer also results in a memory leak.

The malloc function returns a general purpose pointer that is called a void pointer. This is a pointer that does not know the data type to which it points. In order to effectively use the pointer returned, the data type of pointer must be known. This is accomplished using a technique called casting. For example, (int *)malloc(sizeof(int) * 100) casts the pointer returned as a pointer to an integer. Remember, a pointer is a variable that can contain a single memory address and knows the associated data type. Review the pointers topic if necessary before continuing.

Here's a program that demostrates malloc() using the familiar int data type as a guinea pig. The malloc and free functions are located in the C Standard Library, so malloc.h must be included. The malloc function can be changed to allocate memory for any number variables by changing the 1 and the data type can be changed by simply changing int to any other data type. The 1 can be an integer variable for added flexibility.

Trying to allocate more memory than is available will cause malloc() to fail and return NULL.

#include <stdio.h>
#include <malloc.h>

int main(int argc, char *argv[])
{
  int *pCount;

  pCount = (int *)malloc(sizeof(int) * 1);
  if (pCount == NULL)
  {
    printf("malloc failed!\n");
    return 1;
  }
  *pCount = 7;
  printf("Count equals %d\n", *pCount);
  free(pCount);

  return 0;
}
Top

Characters

The changes required to the above program to allocate memory for characters and assign the resulting pointer to a character pointer variable are highlighted in red in this example.

It is important to understand that there is no such thing as a pointer to characters. In this example, pOneCharacter is pointer variable that simply contains the single memory address returned by malloc(). Even if memory to hold 25 characters were allocated by changing the * 1 to * 25, the result is still a single memory address. That single memory address points to the first byte of memory allocated only.

#include <stdio.h>
#include <malloc.h>

int main(int argc, char *argv[])
{
  char *pOneCharacter;

  pOneCharacter = (char *)malloc(sizeof(char) * 1);
  if (pOneCharacter == NULL)
  {
    printf("malloc failed!\n");
    return 1;
  }
  *pOneCharacter = 'k';
  printf("The one character is: %c\n", *pOneCharacter);
  free(pOneCharacter);

  return 0;
}
Top

Structures

Dynamic memory allocation is very useful when dealing with structures. Here is yet another contrived and trivial example which is very similar to the array of structures example under the structures topic. The statement struct Point creates a new data type which can be used in malloc(). This new data type is highlighted in red.

Notice that the pointer variable pOnePoint is increment(++) and decremented(--) serveral times. The end result is that pOnePoint contains the original value return from malloc(), then that value is passed to free(). A pointer MUST point to the FIRST byte of the memory block allocated by malloc() when free() is called.

#include <stdio.h>
#include <malloc.h>

struct Point
{
  int  x;
  int  y;
  char OneCharacter;
};

int main(int argc, char *argv[])
{
  int    i;
  int    x;
  int    y;
  Point *pOnePoint;

  pOnePoint = (Point *) malloc(sizeof(Point) * 2);
  if (pOnePoint == NULL)
  {
    printf("malloc failed!\n");
    return 1;
  }

  pOnePoint->x = 37;
  pOnePoint->y = 21;
  pOnePoint->OneCharacter = 'g';

  pOnePoint++;

  pOnePoint->x = 73;
  pOnePoint->y = 12;
  pOnePoint->OneCharacter = 'j';

  pOnePoint--;

  for (i = 0; i <= 1; i++)
  {
    x = pOnePoint->x;
    y = pOnePoint->y;
    printf("x is %d   y is %d\n", x, y);
    printf("and the character is %c\n", pOnePoint->OneCharacter);
    pOnePoint++;
  }

  pOnePoint--;
  pOnePoint--;
  free(pOnePoint);

  return 0;
}
Top

Manual

Manually keeping up with allocated blocks of memory can be a significant headache. Failure to keep track of and free these blocks of memory causes memory leaks. Freeing a block of memory and then trying to reference that block of memory results in the program crashing. To top it off, C offers no protection against these headaches. The programmer is empowered to shoot themself in the foot.

Any technique that helps keep track of pointers is a good thing. Here's the structure program, but with a few pointer tracking enhancements. It now has two pointers, both are initially set to the same value. Then pAddressFromMalloc is never changed, but pOnePoint changes as the program executes. The new stuff is in red.

#include <stdio.h>
#include <malloc.h>

struct Point
{
  int  x;
  int  y;
  char OneCharacter;
};

int main(int argc, char *argv[])
{
  int    i;
  int    x;
  int    y;
  Point *pOnePoint;
  Point *pAddressFromMalloc;

  pAddressFromMalloc = (Point *) malloc(sizeof(Point) * 2);
  if (pAddressFromMalloc == NULL)
  {
    printf("malloc failed!\n");
    return 1;
  }
  pOnePoint = pAddressFromMalloc;

  pOnePoint->x = 37;
  pOnePoint->y = 21;
  pOnePoint->OneCharacter = 'g';

  pOnePoint++;

  pOnePoint->x = 73;
  pOnePoint->y = 12;
  pOnePoint->OneCharacter = 'j';

  pOnePoint = pAddressFromMalloc;

  for (i = 0; i <= 1; i++)
  {
    x = pOnePoint->x;
    y = pOnePoint->y;
    printf("x is %d   y is %d\n", x, y);
    printf("and the character is %c\n", pOnePoint->OneCharacter);
    pOnePoint++;
  }

  free(pAddressFromMalloc);

  return 0;
}
Top