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

Lesson 6 - Dynamic Memory Allocation

Step 4 - More DMA Functions

Although we will primarily use malloc() and free() to allocate and free our memory, respectively, there are other functions for allocating memory. The other functions we will use are calloc() and realloc().

While realloc() is probably self-explanatory, we will go over it first. The realloc() function is used to reallocate a block of memory from a pointer we already have. But in this case reallocate does not mean to give us the memory again, but rather to resize the block of memory we already allocated, either larger or smaller. This is good for when you do not know the exact size of what you need upfront. You can allocate a large block, and then resize it down to a smaller block once you know how much space you will actually need. For example, if you want to ask the user to enter his or her name, you could allocate a 80 character string to start, and if the person's name only takes 25 characters, you can resize it down to 25 characters once the user has finished entering their name.

It is to your advantage to use only as much memory as you need. This will make sure you have as much memory as you need for other parts of your program to use. If you allocate a big block of memory up front, then there is no more memory for other purposes in the program. This is an especially important concept in a platform that has so little available memory.

The syntax for realloc() is very simple, void *realloc(void *ptr, long int newSize). So, we supply the pointer to the current memory block we have, and the size we want our new block to me. Now, because we are resizing the block, our pointer may change. This means we have to reassign the pointer to a new address returned by the realloc function. So, to give a short example of how it would be used:

char *str = (char *)malloc(100 * sizeof(char));	// allocate space for a 100 character string
    
if (str != NULL) {
	str = (char *)realloc(str, 30 * sizeof(char));	// reallocate string for 30 characters
}

Okay. We allocated a 100 character string, then we resized it down to 30 characters. So, now we have 70 bytes more memory to use in our program elsewhere. However, do not forget that we still need to free our pointer before we exit the program, but we do not need to free the pointer before reallocation.

I think the use of realloc() is pretty self evident. Remember that an 80 char array embedded into a program takes 80 bytes of memory, but a pointer to a null value which will be allocated an 80 character array from dynamic memory takes only 4 bytes (pointers for 32-bit systems are 4 bytes). And the best part is that, if we only need 30 characters later, we can just resize it.

Okay, we have seen the uses for the realloc() function, so what about this calloc() function? Well, it is another useful function, and it is useful for many reasons. First of all, calloc() allocates memory in block sizes. What I mean here is that instead of allocating a certain number of bytes of memory like malloc() does, we allocate a certain number of memory blocks of a certain size. So, instead of allocating 30 * sizeof(int) bytes of memory like we do with malloc(), we can allocate the block of memory that is sizeof(int) 30 times. This is sometimes easier than thinking about the multiplication we need to with malloc(), especially if we allocate space for multi-dimensional arrays (a topic we haven't covered yet, but will someday).

If you can handle the simple mathematics required by malloc(), then calloc() probably doesn't seem like it has much, if any advantage to it, but there is one more thing that calloc() does for us which is nice: initialization. When malloc() allocates a memory block for us, it just leaves whatever was in there before right there in the block. calloc() on the other hand sets all the allocated memory to 0, which is often very handy.

int *block = NULL;	// we should really always initialize our pointers to NULL
  
if ((block = (int *)calloc(50, sizeof(int))) == NULL) {
	DlgMessage("DMA Failure","Unable to allocate space for our pointer",BT_OK,BT_NONE);
	return;
}

Okay, but what does calloc() do? Well, that's simple, calloc takes the size of the integer (sizeof(int)), and gives us 50 blocks of memory that are the size of an integer. Effectively, we just allocated space for a 50 integer array. Now, we also initialized all of our integers to 0. This is part of what calloc() does for us. This is the nice thing about calloc().

Step 5 - Using calloc() and realloc()

Start TIGCC and begin a new project. Create a new C Source File named alloc. Modify the file so that it looks like this:


#include <tigcclib.h>

void _main(void) {
    char *string = NULL;    // we should always initialize our pointers to NULL
    const char *error = "DMA Failure";
    short int result = 0;
    HANDLE dlg = H_NULL;

    // allocate the string
    if ((string = (char *)calloc(120,sizeof(char))) == NULL) {
        DlgMessage(error,"Unable to allocate space for our string",BT_OK,BT_NONE);
        return;
    }

    // create a 140x45 pixel dialog box
    if ((dlg = DialogNewSimple(140,45)) == H_NULL) {
        DlgMessage(error,"Unable to create dialog input box",BT_OK,BT_NONE);

        // release memory from string before exiting the program
        free(string);

        return;
    }

    // add the title of the dialog box
    DialogAddTitle(dlg,"Enter Your Name",BT_OK,BT_NONE);

    // add the input box to the dialog
    DialogAddRequest(dlg,5,15,"Name:",0,119,14);

    // input the user's name with the dialog box
    while ((result = DialogDo(dlg, CENTER, CENTER, string, NULL)) != KEY_ENTER);

    // free the memory used by the dialog box
    HeapFree(dlg);

    // reallocate the string buffer for just enough characters
    // add one character for the string terminator
    if ((string = realloc(string,(strlen(string) + 1) * sizeof(char))) == NULL) {
        DlgMessage(error,"Unable to resize string!",BT_OK,BT_NONE);
        return;
    }

    // clear the screen
    ClrScr();

    // draw the users name
    DrawStr(0,0,"User Name: ",A_NORMAL);
    DrawStr(65,0,string,A_NORMAL);

    // print the exit message
    DrawStr(0,20,"Press any key to exit...",A_NORMAL);

    // free the memory used by the string
    free(string);

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

Step 5a - Compile and Run the Program

Save the project and the source file. Build the project and send it to TiEmu. It will look something like this:

TI-89 AMS 2.05 alloc-89z.gif TI-92+ AMS 2.05 alloc-9xz.gif

Step 5b - Program Analysis

Time for another program analysis, as usual.

char *string = NULL;	// we should always initialize our pointers to NULL
const char *error = "DMA Failure";
short int result = 0;
HANDLE dlg = H_NULL;

Our variable section has some interesting items on it this time. Our string pointer, which will be used to allocate space for our string storage later. It never hurts to initalize your variables.

We have another character pointer, this time to a string literal "DMA Failure". Since we are going to allocate more than one block of memory, and since either of the allocations could fail, we should use error messages to warn the user. But remember that when we embed a string literal inside a function, it is stored in memory for each place we use it. So, if we hadn't used the error string pointer to our string literal, but instead had just embedded them inside our dialog error messages, it would take more memory. It takes 12 bytes to store the string DMA Failure. So, to use it twice would take 24 bytes total. But to store the string once and use a pointer, it only takes 12 bytes + the 4 bytes used by the pointer, or 16 bytes. These kinds of memory savings may seem trivial in a little example program, but if you start using them from the beginning, you will remember to use them when memory savings become important. And as I have said before, and without much doubt will say again, the TI-89/92+/V200 is a very limited platform with very limited memory. It is very likely you will run out of memory some time. This is just a fact of the platform. This is why dynamic memory allocation is a good thing, so we can share the memory we have between all the programs that need it, and then return it when we are done. Cooperation is better than competition. Anyway, the basic idea here is you want to keep track of the memory you use, so you can make sure to use as little as possible wherever possible. This is what we have done here.

Now we have two other variables. The first one is a short integer which we will use to store a result. The second one is a HANDLE type variable. A HANDLE is a special name for an unsigned short defined for use with dialog boxes and other structures. C has a special way of creating new datatypes from old ones, which we will probably talk about in some future lesson. For now, it is enough to know that a HANDLE is a special data type we use to keep track of dialog boxes we create. It is not important to know that it is an unsigned short, and you really shouldn't use unsigned short as a replacement for HANDLE, because HANDLE might change its definition. This is why we have pseudo-datatypes like this. We initialize the handle to H_NULL, which is also 0, just like NULL for pointers. This is just a way of saying we don't have a dialog associated with this handle right now.

This lesson isn't really about HANDLES or dialog boxes, but I thought it would be a good idea to start using some of the AMS functions for added capability inside your programs. Dialogs are one of the nicest things the AMS provides for us, and we might as well take advantage of them.

// allocate the string
if ((string = (char *)calloc(120,sizeof(char))) == NULL) {
	DlgMessage(error,"Unable to allocate space for our string",BT_OK,BT_NONE);
	return;
}

Here is where we allocate the string, and use the calloc() function for the first time. Remember that calloc() allocates a given number of a certain size memory block, in opposition to malloc() which always allocates a certain number of bytes. So, in this example, we allocate 120 characters, which is also 120 bytes, but this is just to ensure we get the right number of variables we wanted. Remember that we need to cast our void pointer to a character pointer using the type cast operator (char *). Lastly, we always need to check to make sure we got the amount of memory we requested. So, compare the result with NULL, and if it's equal, display an error message.

// create a 140x45 pixel dialog box
if ((dlg = DialogNewSimple(140,45)) == H_NULL) {
	DlgMessage(error,"Unable to create dialog input box",BT_OK,BT_NONE);
		
	// release memory from string before exiting the program
	free(string);
		
	return;
}

Now we need to do another kind of dynamic memory allocation. All dialog boxes are blocks of memory themselves, and they do have pointers, but the AMS manages them through a system of handles rather than pointers. If we want to create a dialog box, we should also check to make sure we got the memory we needed for it. We can do that by comparing the result of our DialogNewSimple() function (create a new simple, i.e. blank, dialog box) with the H_NULL handle. If the resulting handle is the null handle, then we don't have the resources required to create the dialog. I am not sure what would happen in this case, because if we couldn't create a simple dialog, then we probably wouldn't be able to display our next message in a dialog box, so the program would probably not display the error message here, but the program should exit before anything bad happens.

The thing we need to remember here is that we already allocated space for the string, so if we exit the program now, our string buffer memory will be lost, so we need to make sure we free the memory here before we exit the program. Never forget to free every memory block you have successfully allocated before program termination.

// add the title of the dialog box
DialogAddTitle(dlg,"Enter Your Name",BT_OK,BT_NONE);

These two functions add the parts of the dialog we need before we can display the dialog box to input the user's name. DialogAddTitle()'s function should be pretty obvious, it sets the title of the dialog box. It takes the handle of the dialog box, which remember we called dlg, the title as a string literal, and also sets the buttons for the dialog. In this case, ENTER returns an OK status, and there is no other button. This is the same button pattern we use on DlgMessage() boxes.

// add the input box to the dialog
DialogAddRequest(dlg,5,15,"Name:",0,119,14);

The DialogAddRequest() function is a little more complicated. It takes several parameters, and its purpose is to put an input box (or a request box if you will) on the dialog box. It takes the handle of the dialog box (dlg in our case), the x and y coordinates (relative to the dialog box -- remember 0,0 is the upper left corner of the dialog box, not the upper left corner of the screen). Remember that our title will take up space, so we have to put the request a little bit further down than the top. I choose 15 pixels down, and 5 pixels of indentation, because it looked good to me. You'll probably have to play around with your own dialog boxes to get it to look right. The next parameter is the prompt displayed before the input field. Generally this should be what the input field is for. I put Name:, because we want the user to input his or her name. The last three parameters are the most complex.

The 0 is the buffer offset parameter. Basically, this tells us at what location relative to the start of our buffer should we start putting the input of the input box. Since we are using the entire string as our name variable, we want to start copying the input to the first position in our buffer, which would be 0. Take a look at the TIGCC documentation for an example of an offset that wouldn't be 0. I think for most purposes though, this parameter will be zero. The times when this will not be zero will usually be when we have more than one request box, which we will look at in a later lesson.

The 119 is the max length of our string. Remember that we always need one character at the end for a termination character, so our max length is never more than 1 less the size of our buffer. Since our buffer is 120 characters, 119 is the max length of a string we can store in the buffer. I think the AMS won't allow us to enter any more than 119 characters, but it may just cut off the input at 119 characters if you enter more than that. In any event, I didn't test either case, but rest assured, it will be taken care of so that only 119 characters can be used. And who has a name longer than 119 characters anyways.

The last parameter, the 14 is the width of the field. 14 in this case means 14 characters of input can be displayed inside the input box at any one time. If more than that are present, the dialog box will scroll the input for us. This is one of the nicest things about dialog box functions. It takes care of these little nuances for us. See, dialog boxes are not that hard, right?

// input the user's name with the dialog box
while ((result = DialogDo(dlg, CENTER, CENTER, string, NULL)) != KEY_ENTER);

Now that we have created our dialog box, we need to actually do something with it. This is where the DialogDo() function comes in. It creates, displays, and processes the dialog box until one of the buttons are pressed which activate closure. In this case, we are waiting for the user to press ENTER, so we use a while loop to make sure the result equals enter before we can continue. The DialogDo() function returns the status of the exit, which is equivalent to what key we assigned the OK and CANCEL functions to be. Remember that in the DialogAddTitle() function, we set the OK return value to be the ENTER key, and did not assign any value to the CANCEL return value. So, in this case, we can simply check to make sure the user pressed ENTER signifying that the input is correct.

The DialogDo() function has several parameters, but they are not complicated. The first parameter, as in all the dialog box functions, is the dialog handle. The next two arguments determine where on the screen the dialog box will be placed. For simplicity, we can use the constant CENTER to mean we want the dialog box to be placed at the CENTER of the screen. The fourth parameter is the string buffer used to store the results of the request boxes. The fifth and final parameter is the pull down menu options, which we won't get into at this time. Since we have no pull down menus, we just put NULL here to signify that we do not have any.

So, what does this all mean? Simple. We show the dialog box and ask the user to enter his name, and we don't stop doing that until the user presses ENTER to finish the dialog box input. There is a small problem here when the system is very low on memory. If there is not enough memory to display the dialog box, the DialogDo() function can return the value -1 meaning ERROR, so if this happened, we would be stuck in an infinite loop here and the calculator would appear to lock up. The correct procedure would be to get the result value, then check it against either the correct exit value KEY_ENTER, or the ERROR value -1, then take appropriate action based on what the value was. But this is just a small example program, so we won't be doing that here.

// free the memory used by the dialog box
HeapFree(dlg);

Remember that we use free() to free up memory allocated by the malloc(), calloc(), and realloc() functions? Well, to free the memory used by dialogs, because they are tracked through handles rather than pointers, we need to use a special function called HeapFree(), which frees memory associated with a handle rather than a pointer. This is just a minor distinction from how we normally free memory.

Remember that dialog boxes use dynamic memory too, so we must never forget to call HeapFree() on all the dialog box handles we allocate.

// reallocate the string buffer for just enough characters
// add one character for the string terminator
if ((string = realloc(string,(strlen(string) + 1) * sizeof(char))) == NULL) {
	DlgMessage(error,"Unable to resize string!",BT_OK,BT_NONE);
	return;
}

Now we need to illustrate the use of the realloc() function, which I believe was the point of this lesson until I introduced Dialog Boxes for some reason. So, to resize our string buffer to the size we need, rather than that massive 120 character buffer, we can use the realloc() function. Since we are using strings, it is easy to find out how long the string is using the strlen() (string length) function. The strlen() function returns the length (in characters) of the string we give it. Even though characters are always 1 byte, we will still still multiply this by the size of a character for consistency. This becomes more important as we allocate other kinds of data like integers and long integers which take more than one character to store. But the most important thing to remember is that the length of a string includes the string terminator, and a string is useless without its terminator, so we must add one to the length of the string to get the correct length. If we did not allocate that extra character, we would cut off our terminator and the string wouldn't end properly.

// free the memory used by the string
free(string);

The last thing in this program we need to address is to free up the memory used by the string. To do this, we use the free() function, as we have always done for alloc based functions. Just remember that this is the most important thing to remember in dynamic memory allocation, because memory leaks are very hard to trace, and require a complete calculator reset to recover from.

Well, that takes us past our analysis.

Step 6 - Conclusions

We learned a very powerful technique in C programming in this lesson. Dynamic Memory Allocation is one of the building blocks of good programs. Nearly all good programs need some kind of large memory block, and its best to allocate that dynamically so we can share it between the other programs on the calculator and other parts of the program itself.

We have seen three types of memory allocation (4 if you count the Dialog box memory stuff, but that's pretty limited in the broader context), and you have seen the uses for all of them. Now it's time to use them in your own programs. So, in closing, never forget the two most important principles in Dynamic Memory Allocation, how to allocate it taking into consideration the size of the variable type you need (multiplying the number of bytes by the sizeof(var type) operator), and how to free up the memory you allocate using the free() function. Never never never never never forget to FREE YOUR MEMORY!

I'll give you two hints about tracking down memory leaks on the TI-89/92+/V200. The mem screen (2nd-6) shows us all the memory used by the system, and what its being used by. The system itself requires a lot of memory. If you check the amount of system memory before you run the program, run the program, and then check the system memory usage again, you can find memory leaks. How? Simple, if the system memory used after you run the program is more than the system memory used before you ran the program, then you probably have a memory leak. The one other thing you need to check is the history screen. Because it sometimes takes up system memory having things on the history screen, so if you see something that looks like a memory leak, try clearing the history screen and seeing if the system memory usage drops back to normal. If it doesn't, then you probably have a memory leak. The only way to fix the memory leak after the memory has been reserved and the program exits is to reset the calculator, so try hard not to release programs that leak memory to the public. People will track you down and kill you probably, or at least send nasty emails.


Lesson 6: Dynamic Memory Allocation
Questions or Comments? Feel free to contact us.


 

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

Get Firefox!    Valid HTML 4.01!    Made with jEdit