Download doc - Malloc

Transcript
Page 1: Malloc

1Dynamic Data Structures: Malloc and Free

Let's say that you would like to allocate a certain amount of memory during the execution of your application. You can call the malloc function at any time, and it will request a block of memory from the heap. The operating system will reserve a block of memory for your program, and you can use it in any way you like. When you are done with the block, you return it to the operating system for recycling by calling the free function. Then other applications can reserve it later for their own use.

For example, the following code demonstrates the simplest possible use of the heap:

int main(){

int *p;

p = (int *)malloc(sizeof(int));if (p == 0){

printf("ERROR: Out of memory\n");return 1;

}*p = 5;printf("&d\n", *p);free(p);return 0;

}

The first line in this program calls the malloc function. This function does three things:

1. The malloc statement first looks at the amount of memory available on the heap and asks, "Is there enough memory available to allocate a block of memory of the size requested?" The amount of memory needed for the block is known from the parameter passed into malloc -- in this case, sizeof(int) is 4 bytes. If there is not enough memory available, the malloc function returns the address zero to indicate the error (another name for zero is NULL and you will see it used throughout C code). Otherwise malloc proceeds.

2. If memory is available on the heap, the system "allocates" or "reserves" a block from the heap of the size specified. The system reserves the block of memory so that it isn't accidentally used by more than one malloc statement.

3. The system then places into the pointer variable (p, in this case) the address of the reserved block. The pointer variable itself contains an address. The allocated block is able to hold a value of the type specified, and the pointer points to it.

The following diagram shows the state of memory after calling malloc:

The block on the right is the block of memory malloc allocated.

The program next checks the pointer p to make sure that the allocation request succeeded with the line if (p == 0) (which could have also been written as if (p == NULL) or even if (!p). If the allocation fails (if p is zero), the program terminates. If the allocation is successful, the program then initializes the block to the value 5, prints out the value, and calls the free function to return the memory to the heap before the program terminates.

Page 2: Malloc

2There is really no difference between this code and previous code that sets p equal to the address of an existing integer i. The only distinction is that, in the case of the variable i, the memory existed as part of the program's pre-allocated memory space and had the two names: i and *p. In the case of memory allocated from the heap, the block has the single name *p and is allocated during the program's execution. Two common questions:

Is it really important to check that the pointer is zero after each allocation? Yes. Since the heap varies in size constantly depending on which programs are running, how much memory they have allocated, etc., there is never any guarantee that a call to malloc will succeed. You should check the pointer after any call to malloc to make sure the pointer is valid.

What happens if I forget to delete a block of memory before the program terminates? When a program terminates, the operating system "cleans up after it," releasing its executable code space, stack, global memory space and any heap allocations for recycling. Therefore, there are no long-term consequences to leaving allocations pending at program termination. However, it is considered bad form, and "memory leaks" during the execution of a program are harmful, as discussed below.

The following two programs show two different valid uses of pointers, and try to distinguish between the use of a pointer and of the pointer's value: void main(){

int *p, *q;

p = (int *)malloc(sizeof(int));q = p;*p = 10;printf("%d\n", *q);*q = 20;printf("%d\n", *q);

}

The final output of this code would be 10 from line 4 and 20 from line 6. Here's a diagram:

The following code is slightly different:

void main(){

int *p, *q;

p = (int *)malloc(sizeof(int));q = (int *)malloc(sizeof(int));*p = 10;

Page 3: Malloc

3*q = 20;*p = *q;printf("%d\n", *p);

}

The final output from this code would be 20 from line 6. Here's a diagram:

Notice that the compiler will allow *p = *q, because *p and *q are both integers. This statement says, "Move the integer value pointed to by q into the integer value pointed to by p." The statement moves the values. The compiler will also allow p = q, because p and q are both pointers, and both point to the same type (if s is a pointer to a character, p = s is not allowed because they point to different types). The statement p = q says, "Point p to the same block q points to." In other words, the address pointed to by q is moved into p, so they both point to the same block. This statement moves the addresses.

From all of these examples, you can see that there are four different ways to initialize a pointer. When a pointer is declared, as in int *p, it starts out in the program in an uninitialized state. It may point anywhere, and therefore to dereference it is an error. Initialization of a pointer variable involves pointing it to a known location in memory.

1. One way, as seen already, is to use the malloc statement. This statement allocates a block of memory from the heap and then points the pointer at the block. This initializes the pointer, because it now points to a known location. The pointer is initialized because it has been filled with a valid address -- the address of the new block.

2. The second way, as seen just a moment ago, is to use a statement such as p = q so that p points to the same place as q. If q is pointing at a valid block, then p is initialized. The pointer p is loaded with the valid address that q contains. However, if q is uninitialized or invalid, p will pick up the same useless address.

3. The third way is to point the pointer to a known address, such as a global variable's address. For example, if i is an integer and p is a pointer to an integer, then the statement p=&i initializes p by pointing it to i.

4. The fourth way to initialize the pointer is to use the value zero. Zero is a special values used with pointers, as shown here:

5. p = 0;

or:

p = NULL;

What this does physically is to place a zero into p. The pointer p's address is zero. This is normally diagrammed as:

Page 4: Malloc

4

Any pointer can be set to point to zero. When p points to zero, however, it does not point to a block. The pointer simply contains the address zero, and this value is useful as a tag. You can use it in statements such as: if (p == 0){

...}

or:

while (p != 0){

...}

The system also recognizes the zero value, and will generate error messages if you happen to dereference a zero pointer. For example, in the following code:

p = 0;*p = 5;

The program will normally crash. The pointer p does not point to a block, it points to zero, so a value cannot be assigned to *p. The zero pointer will be used as a flag when we get to linked lists.

The malloc command is used to allocate a block of memory. It is also possible to deallocate a block of memory when it is no longer needed. When a block is deallocated, it can be reused by a subsequent malloc command, which allows the system to recycle memory. The command used to deallocate memory is called free, and it accepts a pointer as its parameter. The free command does two things:

1. The block of memory pointed to by the pointer is unreserved and given back to the free memory on the heap. It can then be reused by later new statements.

2. The pointer is left in an uninitialized state, and must be reinitialized before it can be used again.

The free statement simply returns a pointer to its original uninitialized state and makes the block available again on the heap.

The following example shows how to use the heap. It allocates an integer block, fills it, writes it, and disposes of it:

#include <stdio.h>

int main(){ int *p; p = (int *)malloc (sizeof(int)); *p=10; printf("%d\n",*p); free(p); return 0;}

Page 5: Malloc

5This code is really useful only for demonstrating the process of allocating, deallocating, and using a block in C. The malloc line allocates a block of memory of the size specified -- in this case, sizeof(int) bytes (4 bytes). The sizeof command in C returns the size, in bytes, of any type. The code could just as easily have said malloc(4), since sizeof(int) equals 4 bytes on most machines. Using sizeof, however, makes the code much more portable and readable.

The malloc function returns a pointer to the allocated block. This pointer is generic. Using the pointer without typecasting generally produces a type warning from the compiler. The (int *) typecast converts the generic pointer returned by malloc into a "pointer to an integer," which is what p expects. The free statement in C returns a block to the heap for reuse.

The second example illustrates the same functions as the previous example, but it uses a structure instead of an integer. In C, the code looks like this:

#include <stdio.h>

struct rec{ int i; float f; char c;};

int main(){ struct rec *p; p=(struct rec *) malloc (sizeof(struct rec)); (*p).i=10; (*p).f=3.14; (*p).c='a'; printf("%d %f %c\n",(*p).i,(*p).f,(*p).c); free(p); return 0;}

Note the following line:

(*p).i=10;

Many wonder why the following doesn't work:

*p.i=10;

The answer has to do with the precedence of operators in C. The result of the calculation 5+3*4 is 17, not 32, because the * operator has higher precedence than + in most computer languages. In C, the . operator has higher precedence than *, so parentheses force the proper precedence.

Most people tire of typing (*p).i all the time, so C provides a shorthand notation. The following two statements are exactly equivalent, but the second is easier to type:

(*p).i=10;p->i=10;

Page 6: Malloc

6You will see the second more often than the first when reading other people's code.

Example.

/* malloc example: string generator*/#include <stdio.h>#include <stdlib.h>

int main (){ int i,n; char * buffer;

printf ("How long do you want the string? "); scanf ("%d", &i);

buffer = (char*) malloc (i+1); if (buffer==NULL) exit (1);

for (n=0; n<i; n++) buffer[n]=rand()%26+'a'; buffer[i]='\0';

printf ("Random string: %s\n",buffer); free (buffer);

return 0;}

This program generates a string of the length specified by the user and fills it with alphabetic characters. The possible length of this string is only limited by the amount of memory available in the system that malloc can allocate.

Dynamic Memory Allocation and Dynamic Structures

Dynamic allocation is a pretty unique feature to C (amongst high level languages). It enables us to create data types and structures of any size and length to suit our programs need within the program.

We will look at two common applications of this:

dynamic arrays dynamic data structure e.g. linked lists

Malloc, Sizeof, and Free

The Function malloc is most commonly used to attempt to ``grab'' a continuous portion of memory. It is defined by:

   void *malloc(size_t number_of_bytes)

That is to say it returns a pointer of type void * that is the start in memory of the reserved portion of size number_of_bytes. If memory cannot be allocated a NULL pointer is returned.

Page 7: Malloc

7Since a void * is returned the C standard states that this pointer can be converted to any type. The size_t argument type is defined in stdlib.h and is an unsigned type.

So:

char *cp;

cp = malloc(100);

attempts to get 100 bytes and assigns the start address to cp.

Also it is usual to use the sizeof() function to specify the number of bytes:

    int *ip; ip = (int *) malloc(100*sizeof(int));

Some C compilers may require to cast the type of conversion. The (int *) means coercion to an integer pointer. Coercion to the correct pointer type is very important to ensure pointer arithmetic is performed correctly. I personally use it as a means of ensuring that I am totally correct in my coding and use cast all the time. It is good practice to use sizeof() even if you know the actual size you want -- it makes for device independent (portable) code. sizeof can be used to find the size of any data type, variable or structure. Simply supply one of these as an argument to the function.

SO:

   int i; struct COORD {float x,y,z}; typedef struct COORD PT;

 sizeof(int), sizeof(i),sizeof(struct COORD) andsizeof(PT) are all ACCEPTABLE

In the above we can use the link between pointers and arrays to treat the reserved memory like an array. i.e we can do things like:

   ip[0] = 100; or    for(i=0;i<100;++i) scanf("%d",ip++);

When you have finished using a portion of memory you should always free() it. This allows the memory freed to be aavailable again, possibly for further malloc() calls The function free() takes a pointer as an argument and frees the memory to which the pointer refers.

Calloc and Realloc

There are two additional memory allocation functions, Calloc() and Realloc(). Their prototypes are given below:

void *calloc(size_t num_elements, size_t element_size};

void *realloc( void *ptr, size_t new_size);

Page 8: Malloc

8Malloc does not initialise memory (to zero) in any way. If you wish to initialise memory then use calloc. Calloc

there is slightly more computationally expensive but, occasionally, more convenient than malloc. Also note the different syntax between calloc and malloc in that calloc takes the number of desired elements, num_elements, and element_size, element_size, as two individual arguments.

Thus to assign 100 integer elements that are all initially zero you would do:

int *ip;

ip = (int *) calloc(100, sizeof(int));

Realloc is a function which attempts to change the size of a previous allocated block of memory. The new size can be larger or smaller. If the block is made larger then the old contents remain unchanged and memory is added to the end of the block. If the size is made smaller then the remaining contents are unchanged.

If the original block size cannot be resized then realloc will attempt to assign a new block of memory and will copy the old block contents. Note a new pointer (of different value) will consequently be returned. You must use this new value. If new memory cannot be reallocated then realloc returns NULL. Thus to change the size of memory allocated to the *ip pointer above to an array block of 50 integers instead of 100, simply do:  ip = (int *) calloc( ip, 50);

Linked Lists

Let us now return to our linked list example: typedef struct {  int value;

ELEMENT *next; } ELEMENT;

We can now try to grow the list dynamically:

link = (ELEMENT *) malloc(sizeof(ELEMENT));

This will allocate memory for a new link.

If we want to deassign memory from a pointer use the free() function:  free(link) See Example programs (queue.c) below and try exercises for further practice.

Full Program: queue.c

A queue is basically a special case of a linked list where one data element joins the list at the left end and leaves in a ordered fashion at the other end.

The full listing for queue.c is as follows: /* queue.c */

/* Demo of dynamic data structures in C*/

#include <stdio.h>#define FALSE 0#define NULL 0typedef struct {

Page 9: Malloc

9 int dataitem;struct listelement *link;} listelement;

void Menu (int *choice);listelement * AddItem (listelement * listpointer, int data);listelement * RemoveItem (listelement * listpointer);void PrintQueue (listelement * listpointer);void ClearQueue (listelement * listpointer);

main () {listelement listmember, *listpointer; int data,choice;listpointer = NULL;do {

Menu (&choice);switch (choice) {

case 1: printf ("Enter data item value to add ");scanf ("%d", &data);listpointer = AddItem (listpointer, data);break;

case 2: if (listpointer == NULL) printf ("Queue empty!\n");else

listpointer = RemoveItem (listpointer);break;

case 3: PrintQueue (listpointer);break;

case 4: break;

default: printf ("Invalid menu choice - try again\n");

break;}

} while (choice != 4); ClearQueue (listpointer);} /* main */

void Menu (int *choice) {

char local;printf ("\nEnter\t1 to add item,\n\t2 to remove item\n\\t3 to print queue\n\t4 to quit\n");

do {local = getchar ();

if ((isdigit (local) == FALSE) && (local != '\n')) { printf ("\nyou must enter an integer.\n");

printf ("Enter 1 to add, 2 to remove, 3 to print, 4 to quit\n");}

} while (isdigit ((unsigned char) local) == FALSE); *choice = (int) local - '0';

Page 10: Malloc

10}

listelement * AddItem (listelement * listpointer, int data) {

listelement * lp = listpointer;

if (listpointer != NULL) {while (listpointer -> link != NULL) listpointer = listpointer -> link;listpointer -> link = (struct listelement *) malloc (sizeof (listelement));listpointer = listpointer -> link;listpointer -> link = NULL;listpointer -> dataitem = data;return lp;

} else {listpointer = (struct listelement *) malloc (sizeof (listelement));listpointer -> link = NULL;listpointer -> dataitem = data;return listpointer;

}}

listelement * RemoveItem (listelement * listpointer) {

listelement * tempp; printf ("Element removed is %d\n", listpointer -> dataitem); tempp = listpointer -> link; free (listpointer); return tempp;}

void PrintQueue (listelement * listpointer) {

if (listpointer == NULL)printf ("queue is empty!\n");

elsewhile (listpointer != NULL) {

printf ("%d\t", listpointer -> dataitem);listpointer = listpointer -> link;

} printf ("\n");}

void ClearQueue (listelement * listpointer) {

while (listpointer != NULL) {listpointer = RemoveItem (listpointer);

}}

Exercises

Exercise 12456

Page 11: Malloc

11Write a program that reads a number that says how many integer numbers are to be stored in an array, creates an array to fit the exact size of the data and then reads in that many numbers into the array.

Exercise 12457

Write a program to implement the linked list as described in the notes above.

Exercise 12458

Write a program to sort a sequence of numbers using a binary tree (Using Pointers). A binary tree is a tree structure with only two (possible) branches from each node (Fig. 10.1). Each branch then represents a false or true decision. To sort numbers simply assign the left branch to take numbers less than the node number and the right branch any other number (greater than or equal to). To obtain a sorted list simply search the tree in a depth first fashion.

 

Fig. 10.1 Example of a binary tree sort Your program should: Create a binary tree structure. Create routines for loading the tree appropriately. Read in integer numbers terminated by a zero. Sort numbers into numeric ascending order. Print out the resulting ordered values, printing ten numbers per line as far as possible.

Typical output should be

The sorted values are:

2 4 6 6 7 9 10 11 11 11 15 16 17 18 20 20 21 21 23 24 27 28 29 30

Avoid Using malloc() and free() in C++

The use of malloc() and free() functions in a C++ file is not recommended and is even dangerous:

1. malloc() requires the exact number of bytes as an argument whereas new calculates the size of the allocated object automatically. By using new, silly mistakes such as the following are avoided:

long * p = malloc(sizeof(short)); //p originally pointed to a short;//changed later (but malloc's arg was not)

Page 12: Malloc

122. malloc() does not handle allocation failures, so you have to test the return value of malloc() on each and every call. This tedious and dangerous function imposes performance penalty and bloats your .exe files. On the other hand, new throws an exception of type std::bad_alloc when it fails, so your code may contain only one catch(std::bad_alloc) clause to handle such exceptions.

3. As opposed to new, malloc() does not invoke the object's constructor. It only allocates uninitialized memory. The use of objects allocated this way is undefined and should never occur. Similarly, free() does not invoke its object's destructor.

object

In object-oriented programming (OOP), objects are the things you think about first in designing a program and they are also the units of code that are eventually derived from the process. In between, each object is made into a generic class of object and even more generic classes are defined so that objects can share models and reuse the class definitions in their code. Each object is an instance of a particular class or subclass with the class's own methods or procedures and data variables. An object is what actually runs in the computer.

class

In object-oriented programming, a class is a template definition of the methods and variables in a particular kind of object. Thus, an object is a specific instance of a class; it contains real values instead of variables.

The class is one of the defining ideas of object-oriented programming. Among the important ideas about classes are:

A class can have subclasses that can inherit all or some of the characteristics of the class. In relation to each subclass, the class becomes the superclass.

Subclasses can also define their own methods and variables that are not part of their superclass. The structure of a class and its subclasses is called the class hierarchy.

In object-oriented programming, a method is a programmed procedure that is defined as part of a class and included in any object of that class. A class (and thus an object) can have more than one method. A method in an object can only have access to the data known to that object, which ensures data integrity among the set of objects in an application. A method can be re-used in multiple objects.

Object-oriented programming (OOP) is a programming language model organized around "objects" rather than "actions" and data rather than logic. Historically, a program has been viewed as a logical procedure that takes input data, processes it, and produces output data. The programming challenge was seen as how to write the logic, not how to define the data. Object-oriented programming takes the view that what we really care about are the objects we want to manipulate rather than the logic required to manipulate them. Examples of objects range from human beings (described by name, address, and so forth) to buildings and floors (whose properties can be described and managed) down to the little widgets on your computer desktop (such as buttons and scroll bars).

The first step in OOP is to identify all the objects you want to manipulate and how they relate to each other, an exercise often known as data modeling. Once you've identified an object, you generalize it as a class of objects (think of Plato's concept of the "ideal" chair that stands for all chairs) and define the kind of data it contains and any logic sequences that can manipulate it. Each distinct logic sequence is known as a method. A real instance of a class is called (no surprise here) an "object" or, in some environments, an "instance of a class." The object or class instance is what you run in the computer. Its methods provide computer instructions and the class object characteristics provide relevant data. You communicate with objects - and they communicate with each other - with well-defined interfaces called messages.

The concepts and rules used in object-oriented programming provide these important benefits:

Page 13: Malloc

13 The concept of a data class makes it possible to define subclasses of data objects that share some or all of

the main class characteristics. Called inheritance, this property of OOP forces a more thorough data analysis, reduces development time, and ensures more accurate coding.

Since a class defines only the data it needs to be concerned with, when an instance of that class (an object) is run, the code will not be able to accidentally access other program data. This characteristic of data hiding provides greater system security and avoids unintended data corruption.

The definition of a class is reuseable not only by the program for which it is initially created but also by other object-oriented programs (and, for this reason, can be more easily distributed for use in networks).

The concept of data classes allows a programmer to create any new data type that is not already defined in the language itself.

One of the first object-oriented computer languages was called Smalltalk. C++ and Java are the most popular object-oriented languages today. The Java programming language is designed especially for use in distributed applications on corporate networks and the Internet.

variable

In programming, a variable is a value that can change, depending on conditions or on information passed to the program. Typically, a program consists of instructions that tell the computer what to do and data that the program uses when it is running. The data consists of constants or fixed values that never change and variable values (which are usually initialized to "0" or some default value because the actual values will be supplied by a program's user). Usually, both constants and variables are defined as certain data types. Each data type prescribes and limits the form of the data. Examples of data types include: an integer expressed as a decimal number, or a string of text characters, usually limited in length.

In object-oriented programming, each object contains the data variables of the class it is an instance of. The object's methods are designed to handle the actual values that are supplied to the object when the object is being used.

CALLOC:

void *  calloc ( size_t num, size_t size );

Allocate array in memory.

 Dynamically allocates a block of memory for an array of num elements of size bytes each one. The block is initialized with zeros.The effective result is the allocation of an initialized memory block of num * size bytes.

Parameters.

num number of elements to be allocated.

size size of elements.

Return Value.  A pointer to the allocated space.  The type of this pointer is void*. A type cast to the desired type of data pointer should be performed on this returned pointer in order to be used as an ordinary array of a concrete type.

Page 14: Malloc

14If the system could not allocate the requested block of memory or if any of the parameters was 0 a NULL pointer is returned.

Portability.  Defined in ANSI-C.  Some systems may apply restrictions on the maximum size for a memory block.

Example.

/* calloc example: rememb-o-matic */#include <stdio.h>#include <stdlib.h>

int main (){ int i,n; int * pData; printf ("Enter number of items to be remembered: "); scanf ("%d",&i); pData = (int*) calloc (i,sizeof(int)); if (pData==NULL) exit (1); for (n=0;n<i;n++) { printf ("Enter number #%d: ",n); scanf ("%d",&pData[n]); } printf ("You have entered: "); for (n=0;n<i;n++) printf ("%d ",pData[n]); free (pData); return 0;}

This program only stores numbers and then prints them out. But the number of items it can remember is not limited because it allocates as much dynamic memory as needed to store the number of values that the user wants to enter.

Using of Memory allocation functions

  1. Basic Ideas

  2. Programming Examples

  3. De-allocation and memory leakage

  4. Debugging

  5. Meaning of Addresses

1. Basic Ideas

ANSI-C defines a group of four functions to allocate and de-allocate blocks of memory on request. These are to be found in the standard libraries, the prototypes are all in <stdlib.h>. They are very widely used

Page 15: Malloc

15by applications either directly or indrectly to create buffers for objects of inherently unpredictable sizes. The functions are.

Function Operation

void *calloc(size_t n, size_t s)

Allocates space for n objects of size s. The space is initialised to all bits zero.

void free(void *p)

De-allocates the space pointed to by p. This must have a value returned by a previous call to calloc, malloc or realloc.

void *malloc(size_t s)

Allocates a space of size s. The initial value is indeterminate.

void *realloc(void *p, size_t s)

Changes the size of space indicated by p by the amount s. If the new area is larger the contents of the new portion are indeterminate.

All these functions collectively administer a set of memory blocks sometimes called the arena or heap. Internally there is information specifying how large each block is. Freeing blocks may result in amalgamation of smaller blocks. A request for a larger block may result in an underlying software request for allocation of more real memory to the process by the host operating system.

If a program is using these calls it is important to ensure that code never writes beyond the end of a memory block, this will overwrite other blocks and may corrupt the arena administration data, it will almost certainly result in an obscure program crash. It also important to ensure that all memory blocks obtained using one of the "alloc" calls is released via a "free" call. Failure to do this will result in the program gradually (or rapidly) accumulating an ever larger amount of memory until the system runs out, this is known as a memory leak.

Under Solaris 2, confusingly, there are several sets of the basic functions, which you get depends on which library you link against. They are the bsdmalloc routines, the malloc(3C) functions and the malloc(3X) functions. They are all ANSI compliant, the 3X routines are space efficient, the bsdmalloc routines are fast and the 3C routines are a compromise. Each group includes some extra non-ANSI functionality that may be of interest. The 3C group is the default.

2. Programming Examples

Here's an example of a program that creates an array and fills it with the character 'x'. The program also reports how much CPU time the various operations took.

#include <stdlib.h>#include <time.h>main(){ int i,size; char *base; clock_t start,end; printf("Enter size of array to create "); scanf("%d",&size);

Page 16: Malloc

16 start = clock(); base = (char *)calloc(size,sizeof (char)); end = clock(); if(base != NULL) {

printf("Array of size %d created OK at address %p\n",size,base); printf("Allocation took %d milliseconds\n",

(int)((end-start)*1E3/CLOCKS_PER_SEC)); } else { printf("Not enough memory\n"); exit(1); } start = clock(); for(i=0;i<size;i++) base[i] = 'x'; end = clock(); printf("Filling the array took %d milliseconds\n", (int)((end-start)*1E3/CLOCKS_PER_SEC)); }

There are several intersting points to note.

o The value returned from the function calloc() is cast to a suitable type. This is normal practice when using this function since it's default return value is a generic pointer and there are no default conversions between pointer types in C.

o The value returned by calloc() is stored in the variable base. The value will, of course, be the start or base address of the allocated area of memory. The relationship between pointers and array notation in C allows the use of array notation when the array is filled.

o Timing information is here determined using the ANSI C function clock(), this does not have particularly fine resolution. You may find higher resolution timers available on your system.

Here are some results from running the program to create arrays of 1, 10, 100 and 1000 Megabytes. You may find it interesting to repeat these tests on different systems.

bash$ a.outEnter size of array to create 1000000Array of size 1000000 created OK at address 20be0Allocation took 70 millisecondsFilling the array took 130 millisecondsbash$ a.outEnter size of array to create 10000000Array of size 10000000 created OK at address 20be0Allocation took 700 millisecondsFilling the array took 1390 millisecondsbash$ a.outEnter size of array to create 100000000Array of size 100000000 created OK at address 20be0Allocation took 7020 millisecondsFilling the array took 13900 millisecondsEnter size of array to create 1000000000Not enough memory

Page 17: Malloc

17The overheads involved in creation of the arrays should be noted as should the time taken to fill the arrays.

It is tempting when using calloc() to create instances of structure one by one as required by the program logic, this may not be wise as the overheads involved are significant. A useful alternative is to write some sort of wrapper function that maintains a "pool" of such structures and calls calloc() only when the "pool" is empty. Here's some typical code.

#define POOLSIZE 100struct x *new(){

static int nfree = 0;static struct x *base;if(!nfree){

base = (struct x *)calloc(POOLSIZE,sizeof(struct x));if(base == NULL) return NULL;nfree = POOLSIZE;

}return base + POOLSIZE - nfre++;}

A routine that creates new instances of memory consuming objects on demand in this fashion is often called a constructor.

A common practice is to use structures to store information about objects, this will typically include descriptive text. The descriptive text needs to be stored somewhere and it is very common practice to calloc() a suitable area of memory. Here's a modified version of the "new" function shown above that does this. This time we're not using a pool of structures but allocating them one by one, this may be worth the overheads if they are going to be subsequently released.

struct x{

char *text;int value;

};struct x *new(char *s){

static int nfree = 0;static struct x *base,*xptr;char *sptr;sptr = (char *)calloc(strlen(s)+1,sizeof(char));if(sptr == NULL) return NULL;strcpy(sptr,s);xptr = (struct x *)calloc(1,sizeof(struct x));if(xptr == NULL){

free(sptr);return NULL;

}xptr -> text = sptr;return xptr;

}Again there are several interesting points here.

Page 18: Malloc

18o The allocated space for the copy of the value is its string length plus one to allow for the string

terminating NUL. o The value is copied to its final storage place using the strcpy() function. o If there is insufficient memory for the struct x the already allocated memory for the value is

released before returning from the constructor. 3. Releasing memory space, leaks and garbage collection

If instances of struct x are being created by the code shown in the previous section, it is very tempting to dispose of them thus.

struct x *instance;

instance = new("hello world");

free(instance);

This is wrong as, although it does release the memory occupied by the instance of a struct x it does not release the memory used to store a copy of the associated text. The address of this memory location is lost and it is no longer accessible, however the memory management routines have no way of knowing this. This is a classic example of a memory leak. The programmer should have written :-

free(instance->text);free(instance);

It is probably better practice to code a specific destructor function to go with the constructor function here called new(). This does not, of course, deal with the type of memory leak caused by creating an instance of something to deal with an input or some part of processing an input and then not destroying it, perhaps because the processing went through some unusual path through the program. Some C++ implementations, apparently, automatically destroy objects when they pass out of scope, this is of limited use if a pointer to an object is being used.

In some programming languages, such as Java, the memory management facilities include a facility known as garbage collection. This requires that the memory management facilities periodically examine the whole data space of the program looking for pointers into the arena and checking that all objects in the arena are actually targets of pointers. This further requires that the language supports comparatively strong typing so that such pointers can be readily identified. The overheads involved are significant although Java implementations multi-thread garbage collection with normal activity so garbage collection can proceed while waiting for input. The normal memory management functions supplied with C compilers do not provide garbage collection.

4. Debugging

Debugging a program that uses calloc() and free() extensively can be difficult. There are two types of error that need to be tracked down.

1. Those caused by over-writing the bounds of an allocated memory block. This is almost identical to the well-known C problem of "running off" the end of an array. The resultant damage to the arena will often cause an almost immediate crash.

2. Memory leaks. This is a longer term problem that simple testing with a small amount of data is unlikely to reveal. Testing against large volumes of data is important.

Under Solaris 2, dbx has some facilities to deal with such problems although they require linkage against a special set of allocation routines. They can be used periodically to display information about the arena. Solaris also provides a library function mallinfo that provides access to the arena information, this can be wrapped in a simple print function and included at suitable points in the program, or used in conjunction with assertions about the

Page 19: Malloc

19various values (i.e. that the values at the end of execution of a piece of code are the same as those at the start). Here's an example of the sort of code that might be used.

struct mallinfo old,new;..

old = mallinfo();...

new = mallinfo();assert(old.uordblks == new.uordblks);

.

If the number of uordblks has changed, probably indicating that some memory has been allocated but not released the failed assertion will cause a core dump which can then be examined using the debugger. The usefulness of this approach is slightly restricted by lack of information about the significance of the various items of arena information reported by mallinfo().

2. Addresses

The addresses returned by calloc() are fairly arbitrary. If two, or more, objects are created by successive calls to calloc() they will not, in general, occupy successive locations as this example shows.

#include<time.h>main(){

struct tm *t[3];int i,s = sizeof (struct tm);for(i=0;i<3;i++){

t[i] = (struct tm*)calloc(1,s);}printf("Size of struct tm = %x\n",s);for(i=0;i<3;i++)

printf("Instance %d base address %x\n",i,t[i]);}

The program allocates memory for three instances of a struct tm and reports the addresses. Here is the output.

bash$ a.outSize of struct tm = 24Instance 0 base address 209e8Instance 1 base address 20a18Instance 2 base address 20a48

This shows that the size of a struct tm is 24x whereas the difference between the addresses of the successive locations is 30x, this suggests an overhead of 1210 bytes per allocated piece of memory.

It is also important to realise that the addresses associated with allocated memory objects are absolute within the program's address space. This means that the following practices are likely to cause severe problems.

o Linked lists within arrays

Page 20: Malloc

20It is tempting to create and array (or "pool") of objects via a single call to calloc() for reasons of efficiency, in preference to multiple single calls and then build a linked list (or other data structure) within the array. This is perfectly safe until all the objects of the array have been used, there is a temptation to use the function realloc() to expand the array. realloc() will preserve the contents of the first part of the array and attach extra space at the end, however, as the ANSI standard makes quite clear the allocated space may have been moved. Moving the array without changing the pointers that define the linked list destroys the linked list as the poointers no longer point to the correct place.

o Saving data structures

Having constructed a possibly elaborate and expensive to build linked data structure in calloc'd space, it is tempting to write it to a file for future use by the program. Since there is no control over the absolute positioning of calloc'd space this will not work unless, by chance, the new area is positioned in the same place as the original. Similar constraints would apply if the structure were being created in ordinary "compile time allocated" memory and the program were still being developed. The best way of creating such persistent structures is to use the mmap() function to map a file into memory space at a specific location. This also has the advantage that there is no need to perform file operations to read in the persistent structure.

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.

Page 21: Malloc

21Let'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. You may think, well that's stupid. Why have void pointers at all then? Well, there is a very important reason: portability. You see, C was made with the concept of portability in mind. And on different machines, variable types take up different amounts of memory. For example, on the 68000, a word is 32 bits long (speaking from a machine architecture standpoint). For a IBM 286, the word size is 16-bits. If you have an old Nintendo or Sega Master System, the word size is 8 bits. If you have a Nintendo 64, it's word size is 64-bits. There are many other word sizes and many different variable sizes based on those word sizes, but the important thing to know is, we have no idea how big a certain kind of variable will be on a specific system. So, although integers are two bytes for our system, maybe they are 4 bytes on other systems. The only way to be sure how much memory we want to allocate is to have a void pointer, and just allocate bytes. This lets us have a non-architecture specific pointer to allocate bytes of memory with. However, when we go to actually use it, we need to convert it to a type whose size we know. This is what we have done above.

This works fine for characters, but it is not a good idea to actually allocate memory in this way. The reason is simple. We are assuming right off that characters take 1 byte to store. Although I think this implementation is universal, we shouldn't assume it, because when we start assuming characters are one byte, we start assuming things about the other variable types as well. So, to combat this problem, we should do something like this instead:

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, and although there is generally no difference, there will be a difference between 40 integers and 40 bytes, or 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.

Page 22: Malloc

22free(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 operating 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. The operating system is a wonderful thing. Just trust that it does its job and you won't have to worry about the how.

Step 3 - The Many Uses of malloc()

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

// C Source File// Created 3/5/01; 9:04:49 PM// Updated 8/23/2002; 12:52:59 AM

#include <tigcclib.h>

// Main Functionvoid _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 errorif ((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 0array[0] = 1;array[1] = 1;

// setup the array with the first 45 numbers in the sequencefor (loop = 2; loop < 45; loop++) {

array[loop] = array[loop - 1] + array[loop - 2];}

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

// loop through the array and print out the numbers in the sequencefor (loop = 0; loop < 45; loop++) {

// print the number - lu = long unsignedprintf("%lu\n",array[loop]);

Page 23: Malloc

23// if we've printed 8 numbers, pause to see the resultif (((loop % 8) == 0) && loop != 0) {

printf("Press any key...\n");ngetchx();

}}

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

// wait for user input before exiting the programngetchx();

}

Step 3a - Compile and Run the Program

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.

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

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

// allocate the array or exit with an errorif ((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 a 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 0array[0] = 1;array[1] = 1;

Page 24: Malloc

24Since 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 I feel its easier to use array notation, so that's what I generally use.

// setup the array with the first 45 numbers in the sequencefor (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 messageprintf("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 sequencefor (loop = 0; loop < 45; loop++) {

// print the number - lu = long unsignedprintf("%lu\n",array[loop]);

// if we've printed 8 numbers, pause to see the resultif (((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. In case you have forgotten, the % operator is called the modulo operator. It 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

Page 25: Malloc

25always 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 arrayfree(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+, 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 TIOS 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.

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 variables to use 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. The realloc() function takes care of things like this for us. That's why we like it.

Page 26: Malloc

26I 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. This is what makes C such a great language.

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) (2 bytes, or 4 possibly) 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. If something were to happen and we were to mistake one of those addresses for a pointer (this is a stretch, but it could happen), and used it outright, well, we'd get an address error and crash the calculator. However, if we used calloc(), all those memory addresses would be initialized to 0 (or NULL if you prefer -- just as a note, 0 and NULL are the same value). So, if we mistook a value from our calloc() block for a pointer, we would see that it's pointing to NULL and would know we had made a mistake.

The one big drawback of calloc() is that it is inefficient. The initialization of the memory block can be a significant drawback if you are initializing a 50,000 byte address (I'm not sure why you would do this on a calculator with such limited memory, but it's not out of the realm of possibilities.), then you would probably notice the initialization. And so long as you do your coding right, there is no real reason to initialize your memory block before you use it, at least no reason to fill it with zeros. If you are writing a program where speed is of the utmost importance, you will probably not want to use calloc(), but instead use malloc(). But, you should know how the function works anyway, so let's take a look at a small example here:

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;}

In these little examples, I haven't been checking to see if we actually got the memory we requested, but that's incredibly bad programming practice, especially so for a platform with so very limited memory. Remember, 200 K of memory is the max you will ever get from your TI-89/92+. That's less than 0.6% of what you have on a 32 MB PC system. So running out of memory is a real and very possible problem here, so it is more important than ever to always check to see that you got the memory you requested.

Now that I made my little warning, which I will probably make again so it sinks into your head, let's go over what we actually did with this code. We created an integer pointer, block, and we initialized that pointer to NULL. Since calloc() and malloc() (and realloc() for that matter) all return NULL on a failure, it is not absolutely necessary to initialize our pointer to NULL, but it is good programming practice for a very important reason. What if we forget to do our alloc function? Well, if we initialize the pointer to NULL, we have an extra layer of protection. We can always check our pointer against the NULL value to see if it is a pointer to something useful, but if we don't initialize it, and we don't make our alloc function call, then we have no way of knowing (in the context of our program) that this pointer is not the pointer that we want.

Page 27: Malloc

27The only way to check for this is to make sure our pointer is NULL first, then check it to make sure it's not when we try to use it. This is all part of good programming practice.

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 node 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. Save the project in the c:\tidev\lesson6 directory with the name alloc. Create a new C Source File named alloc.c. Modify the alloc.c file so that it looks like this:

// C Source File// Created 3/8/01; 12:48:22 AM// Updated 8/23/2002; 1:03:47 AM

#include <tigcclib.h>

// Main Functionvoid _main(void) {

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

// allocate the stringif ((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 boxif ((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 programfree(string);

return;}

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

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

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

// free the memory used by the dialog boxHeapFree(dlg);

Page 28: Malloc

28

// reallocate the string buffer for just enough characters// add one character for the string terminatorif ((string = realloc(string,(strlen(string) + 1) * sizeof(char))) == NULL) {

DlgMessage(error,"Unable to resize string!",BT_OK,BT_NONE);return;

}

// clear the screenClrScr();

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

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

// free the memory used by the stringfree(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 VTI.

Step 5b - Programmatic Analysis

Time for another program analysis, as usual.

char *string = NULL; // we should always initialize our pointers to NULLconst 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. But remember that it is always a good idea to initialize our pointers to NULL, because in a real program, you might forget to call your allocate function and would start using a pointer that is bad. So it's a good idea to initialize these things.

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+ 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

Page 29: Malloc

29why 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 (unlikely, but possible) 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 TIOS functions for added capability inside your programs. Dialogs are one of the nicest things the TIOS provides for us, and we might as well take advantage of them.

// allocate the stringif ((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 another way 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 boxif ((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 programfree(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 TIOS manages them through a system of handles rather than pointers. I am sure they had a good reason, so I will leave it at that. What I do know is that 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 certainly 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.

Page 30: Malloc

30The 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. Memory leaks are bad enough on Windows systems that have tons of memory, but on a little TI-89, it won't take long until all the memory is lost and a reset is the only way to fix the system.

// add the title of the dialog boxDialogAddTitle(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 dialogDialogAddRequest(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 always 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 TIOS 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.

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 the TIOS and the 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 boxwhile ((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 with activate closure. In this case, we are waiting for the user to press ENTER, so we use a while

Page 31: Malloc

31loop 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 TIOS 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 boxHeapFree(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 terminatorif ((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. Remember that we still need to multiply this by the size of a character to make sure we reallocate the correct number of characters rather than just the correct number of bytes. This becomes most 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 stringfree(string);

Page 32: Malloc

32The 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 cause very weird bugs in the program.

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! This is why Windows crashes so often, because programmers forget to do this, and it leads to bugs and out of memory errors. This is why it always helps to add memory to your computer, because programs are always eating it up and forgetting to free it back up, which in turn leads to big problems which are hard to trace.

I'll give you two hints about tracking down memory leaks on the TI-89/92+. 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 about "mysterious crashes" that you never seem to get.


Recommended