Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

       Part I

       Part II

     Slider Puzzle 2

     Structures

     Bit Manipulation

     Advanced Pointers

     File I/O

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

TIGCC Programming Lessons

The following is part of a series of lessons designed to help teach people to program in C for the TI-89, 92+, and V200 calculators using the TIGCC development environment.

If you wish, you can download the program source code, project files, and binaries here.

Lesson 6: Dynamic Memory Allocation

Step 1 - An Introduction to Dynamic Memory Allocation

Up until this time, we have used what is referred to as static memory. Static memory means we reserve a certain amount of memory by default inside our program to use for variables and such. While there is nothing wrong with this, it means that once we reserve this memory, no other program can use it, even if we are not using it at the time. So, if we have two programs that reserve 1000 bytes of memory each, but neither program is running, then we have 2000 bytes of memory that is being completely wasted. Suppose we only have 3000 bytes of memory, but we already have our two programs that take 1000 bytes each. Now we want to load a program that needs 1500 bytes of memory. Well, we just hit a wall, because we only have 3000 bytes of memory and 2000 bytes are already reserved. We can't load our third program even though we have 2000 bytes of memory that isn't even being used. How could we possibly remedy this situation?

If you said Dynamic Memory Allocation, then you must have read the title of this lesson. That's right. We are going to use dynamic memory to share the memory among all three programs.

Dynamic memory won't fix everything. We will always need an amount of finite static memory, but this amount is usually much less than we need. This is why we still have static memory.

So, let us imagine a new scenario. We have changed our first two programs to use dynamic memory allocation. Now they only need to reserve 100 bytes of memory each. This means we are now only using 200 bytes of our 3000 total memory bytes available. Our third program, which requires 1500 bytes of memory can now run fine.

Step 2 - The Basics of Dynamic Memory Allocation

Now that we have covered why we would want to use dynamic memory allocation, how do we go about doing it? Well, that's easy! We have predefined functions that let us perform these tasks.

The two functions we will be employing in our dynamic memory allocation tasks are malloc() and free(). malloc() meaning memory allocation, and free, well, that should be obvious. Our malloc() function returns a pointer to the memory block we requested. Remember pointers from the last lesson? I told you we weren't done with them yet.

Let's discuss the syntax of malloc(). It takes a single argument, a long integer representing the number of bytes we want to allocate from the heap. (Note: the heap is what we call all the memory we don't reserve by default) So, for example, to allocate memory for an array of characters for a 40 character string, we would do malloc(40); There's more to it, obviously, but we'll cover that.

To allocate memory though, we must have a pointer so we can know where the memory will be at when it gets allocated. Let's look at a small code example:

char *str = (char *)malloc(40);	// allocate memory for a 40 character string

This may look a little weird, because it uses a concept we haven't worked with before called "type casting". C has the ability to convert from one type of variable to another by using what are called type cast operators. The syntax is simple, put the variable type you want to convert to inside parentheses. See, it's easy. So, because we want to convert the memory into a character pointer, which is specified by the type char *, we put char * in parentheses. Then C will give us a character pointer to the memory. This is very easy to do when a character pointer is involved. The important thing to note here is what malloc() returns to us. Because malloc() doesn't care how the memory we allocate will be used, malloc() just returns a pointer to a series of bytes. It does this by using a void pointer. Void pointers are just like character pointers, or integer pointers, or any other kind of pointer with one special difference: we do not know what the size of the void pointer is. There is no way to increment or decrement a void pointer. In fact, we can't do much of anything with a void pointer except acknowledge its existence until we convert (type cast) it to another variable type.

Characters are by definition 1 byte, and malloc always allocates in multiples of a single byte. Other types are more than 1 byte. Rather than remembering how many bytes an int variable takes on a system, we can use the sizeof operator. Let's see an example.

char *str = (char *)malloc(40 * sizeof(char));	// allocate memory for a 40 character string

This looks very similar to what we had above, with one important exception, the sizeof(char) multiplier. This is a new operator, and another very important one in C. The sizeof() operator will tell us the size of any variable or structure in our program. The one thing to keep in mind when using sizeof() is that the size of a pointer is how many bytes it takes to store the pointer, not the size of the memory block the pointer is pointing at. There is no need to know that information. In allocating memory like this, we know we have 40 characters instead of 40 bytes (even though they are technically the same). There is a difference between 40 integers and 40 bytes, and 40 long integers especially.

Now that we have taken a look at how the malloc() function works, let's take a look at its companion function, free(). The free() function is basically the exact opposite of malloc(). So, instead of assigning a pointer the value returned, we give the function the pointer we got from malloc(). So, if we have the *str pointer we allocated above, to free that memory, we just need to call free with the pointer as an argument.

free(str);	// free the memory allocated by malloc()

You may be wondering how the free() function knows how much memory to free up, since we didn't tell it how much memory we allocated. Well, the short answer is: you don't need to worry about that. The system will take care of such minutia for us. The long answer is, well, long, and complicated, so I don't want to talk about it.

Most modern systems will free allocated memory at the completion of the program. The AMS is not one of these systems. If you don't free the memory you allocate, your calculator will lose memory until it is reset.

Step 3 - The Many Uses of malloc()

Start TIGCC and create a new project. Create a new C Source File called malloc. Edit the file so that it looks like this:

malloc.c


#include <tigcclib.h>

void _main(void) {
    int loop;
    unsigned long int *array;   // the integer array pointer

    // clear the screen for printf()
    clrscr();

    // allocate the array or exit with an error
    if ((array = (unsigned long int *)malloc(45 * sizeof(unsigned long int))) == NULL) {
        DlgMessage("DMA Failure","Unable to allocate integer array space",BT_OK,BT_NONE);
        return;
    }

    // get the first two numbers in the Fibonacci sequence -- I start at 1, not 0
    array[0] = 1;
    array[1] = 1;

    // setup the array with the first 45 numbers in the sequence
    for (loop = 2; loop < 45; loop++) {
        array[loop] = array[loop - 1] + array[loop - 2];
    }

    // display the intro message
    printf("The Fibonacci Sequence:\n");
    printf("Press a key to begin!\n\n");
    ngetchx();

    // loop through the array and print out the numbers in the sequence
    for (loop = 0; loop < 45; loop++) {
        // print the number - lu = long unsigned
        printf("%lu\n",array[loop]);

        // if we've printed 8 numbers, pause to see the result
        if (((loop % 8) == 0) && loop != 0) {
            printf("Press any key...\n");
            ngetchx();
        }
    }

    // free up the memory used by the integer array
    free(array);

    // wait for user input before exiting the program
    ngetchx();
}

Step 3a - Compile and Run the Program

Build the project and send it to TiEmu. It will look like this:

TI-89 AMS 2.05 malloc.89z TI-92+ AMS 2.05 malloc.9xz

Step 3b - Programmatic Analysis

Okay. I know this wasn't the best example, but I couldn't think of anything good to do. Anyway, let's try to get through it. Although thinking about how many computer science classes I took that used factorial as an example program, maybe it's not that bad.

int loop;
unsigned long int *array;	// the integer array pointer

We have our variable declarations. Remember that there is very little difference between arrays and pointers, so once we have space for our pointer, it will be an array.

// allocate the array or exit with an error
if ((array = (unsigned long int *)malloc(45 * sizeof(unsigned long int))) == NULL) {
	DlgMessage("DMA Failure","Unable to allocate integer array space",BT_OK,BT_NONE);
	return;
}

One thing I forgot to mention about Dynamic Memory Allocation is, unlike static memory which is reserved by default (so you won't be able to send the program unless you have enough memory), dynamic memory must be allocated at runtime. That being said, there may not be enough memory available to fill the request. So, we must always check that our memory was actually allocated to us by checking the pointer against a NULL value. The NULL value basically says, "I am not pointing at anything". And, if we are not pointing at anything after we finish our malloc() call, then we don't have enough memory to run this program. We should display some kind of error message and exit the program.

To exit a program from the main() method, we can use the return keyword. You have seen return when we are sending back a value from a function. The main function is no different, except that we do not return a value because the return type of the main function is void. So, we can use return without returning any specific value.

This being done, if we haven't exited our program, then we must have gotten the memory we requested. So, let us continue.

// get the first two numbers in the Fibonacci sequence -- I start at 1, not 0
array[0] = 1;
array[1] = 1;

Since this example deals with the Fibonacci Sequence, we need to initialize the base cases of that sequence. If you haven't seen the Fibonacci sequence before, it runs like this. The first two numbers are 1 and 1. Every number after the first two numbers are the sum of the previous two numbers. So we have 1, 1, 2 (1 + 1), 3 (1 + 2), 5 (2 + 3), 8 (3 + 5), 13 (5 + 8), etc. Remember that since arrays are just a special way of using pointers, we can use array notation for accessing our pointer memory. We could have done the same thing by using this code:

*array = 1;	// set the first array node to 1
*++array = 1; // set the second array node to 1

But array notation is more readable.

// setup the array with the first 45 numbers in the sequence
for (loop = 2; loop < 45; loop++) {
	array[loop] = array[loop - 1] + array[loop - 2];
}

Okay, our first big loop sets up all the values in our limited segment of the Fibonacci sequence (the first 45 values). Since we allocated space for a 45 node array, we will have no problem storing this. As per our definition of the sequence, each value is the sum of the previous two values, so we use a simple assignment statement (=).

// display the intro message
printf("The Fibonacci Sequence:\n");
printf("Press a key to begin!\n\n");
ngetchx();

You should know how printf() works by now, especially in this limited example. We are just displaying a short message on the screen.

// loop through the array and print out the numbers in the sequence
for (loop = 0; loop < 45; loop++) {
	// print the number - lu = long unsigned
	printf("%lu\n",array[loop]);
		
	// if we've printed 8 numbers, pause to see the result
	if (((loop % 8) == 0) && loop != 0) {
		printf("Press any key...\n");
		ngetchx();
	}
}

Our next big loop prints out all the values in our sequence. We could have done this while we were assigning our values to the sequence, but a real program would store the values in the array and access them later, so, this is a little closer to something real (okay, that's a stretch, but stay with me).

The loop is fairly simple, we use printf() to display our values on the screen because printf() is easy to use and will scroll the lines on the screen for us automatically. Remember that the %lu format specifier is used for printing long unsigned integers, which is what we have in our array. The \n is our key for a new line.

The last segment of this loop pauses the screen after 8 lines of output. We want to give the user a chance to see the results. We introduce another operator here, the modulo operator. Modulo gives us the remainder part of integer division. So, if a number is evenly divided by 8 (loop modulo 8), then we will enter our if-clause. But we have to take care of one more condition. Remember that 0 modulo anything is always 0, and we don't want to pause after we print the first number, so we will make the condition also require that the loop counter is not 0 (the first node in the array). So, if both of these conditions are satisfied, then we print a short pause message and wait for the user to press a key. Simple, no?

// free up the memory used by the integer array
free(array);

Okay, the last line of our program is possibly the most important when dealing with dynamic memory allocation. Never ever forget that you must always free up the memory you allocated before you exit the program. If the program exits without freeing up the memory, it will be reserved forever. Worse than that, on the TI-89/92+/V200, there are a limited number of handles available to dynamic memory allocation, and once those handles are gone, we can't allocate any more dynamic memory. The only way to get these handles and memory free if you don't free them before you exit the program is to reset the calculator, because the AMS does not do garbage collection for you.

As long as you remember that one important rule, you shouldn't have a problem with dynamic memory allocation.

Continue in Part II

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit