the compiler sorts out how to set aside some memory to store the integer. More impressive is the way that

int a[50]

sets aside enough storage for 50 ints and sets the name a to point to the first element. Clever though this may be it is just static storage. That is the storage is allocated by the compiler before the program is run - but what can you do if you need or want to create new variables as your program is running? The answer is to use pointers and the malloc function. The statement:

ptr=malloc(size);

reserves size bytes of storage and sets the pointer ptr to point to the start of it. This sounds excessively primitive - who wants a few bytes of storage and a pointer to it? You can make malloc look a little more appealing with a few cosmetic changes. The first is that you can use the sizeof function to allocate storage in multiples of a given type. For example:

sizeof(int)

returns a number that specifies the number of bytes needed to store an int. Using sizeof you can allocate storage using malloc as:

ptr= malloc(sizeof(int)*N)

where N is the number of ints you want to create. The only problem is what does ptr point at? The compiler needs to know what the pointer points at so that it can do pointer arithmetic correctly. In other words, the compiler can only interpret ptr++ or ptr=ptr+1 as an instruction to move on to the next int if it knows that the ptr is a pointer to an int. This works as long as you define the ptr to be a pointer to the type of variable that you want to work with. Unfortunately this raises the question of how malloc knows what the type of the pointer variable is - unfortunately it doesn't.

To solve this problem you can use a TYPE cast. This C play on words is a mechanism to force a value to a specific type. All you have to do is write the TYPE specifier in brackets before the value. So:

ptr = (*int) malloc(sizeof(int)*N)

forces the value returned by malloc to be a pointer to int. Now you can see how a simple idea ends up looking complicated. OK, so now we can acquire some memory while the program is running, but how can we use it? There are some simple ways of using it and some very subtle mistakes that you can make in trying to use it! For example, suppose during a program you suddenly decide that you need an int array with 50 elements. You didn't know this before the program started, perhaps because the information has just been typed in by the user. The easiest solution is to use:

int *ptr;

and then later on:

ptr = (*int) malloc(sizeof(int)*N)

where N is the number of elements that you need. After this definition you can use ptr as if it was a conventional array. For example:

ptr[i]

is the ith element of the array. The trap waiting for you to make a mistake is when you need a few more elements of the array. You can't simply use malloc again to get the extra elements because the block of memory that the next malloc allocates isn't necessarily next to the last lot. In other words, it might not simply tag on to the end of the first array and any assumption that it does might end in the program simply overwriting areas of memory that it doesn't own.

Another fun error that you are not protected against is losing an area of memory. If you use malloc to reserve memory it is vital that you don't lose the pointer to it. If you do then that particular chunk of memory isn't available for your program to use until it is restarted.


 

 


Structures and Linked Lists

You may be wondering why malloc has been introduced right after the structure. The answer is that the dynamic allocation of memory and the struct go together a bit like the array and the for loop. The best way to explain how this all fits together is via a simple example. You can use malloc to create as many variables as you want as the program runs, but how do you keep track of them? For every new variable you create you also need an extra pointer to keep track of it. The solution to this otherwise tricky problem is to define a struct which has a pointer as one of its components. For example:

 

struct list

 {

  int data;

  struct list *ptr;

 };

 

This defines a structure which contains a single int and - something that looks almost paradoxical - a pointer to the structure that is being defined. All you really need to know is that this is reasonable and it works. Now if you use malloc to create a new struct you also automatically get a new pointer to the struct. The final part of the solution is how to make use of the pointers. If you start off with a single 'starter' pointer to the struct you can create the first new struct using malloc as:

 

struct list *star;

start = (*struct list) malloc(sizeof(list))

 

After this start points to the first and only example of the struct. You can store data in the struct using statements like:

start->data=value;

The next step is to create a second example of the struct:

start = (*struct list) malloc(sizeof(list));

This does indeed give us a new struct but we have now lost the original because the pointer to it has been overwritten by the pointer to the new struct. To avoid losing the original the simplest solution is to use:

 

struct list *start,newitem;

newitem = (*struct list) malloc(sizeof(list));

start->prt=start;

start=newitem;

 

This stores the location of the new struct in newitem. Then it stores the pointer to the existing struct into the newitem's pointer and sets the start of the list to be the newitem. Finally the start of the list is set to point at the new struct. This procedure is repeated each time a new structure is created with the result that a linked list of structures is created. The pointer start always points to the first struct in the list and the prt component of this struct points to the next and so on. You should be able to see how to write a program that examines or prints the data in each of the structures. For example:

 

thisptr=start;

while (1==1)

 {

  printf("%d",thisprt-> data);

  thisprt=thisprt->prt;

 }

 

This first sets thisptr to the start of the list, prints the data in the first element and then gets the pointer to the next struct in the list and so on. How does the program know it has reached the end of the list? At the moment it just keeps going into the deep and uncharted regions of your machine's memory! To stop it we have to mark the end of the list using a null pointer. Usually a pointer value of 0 is special in that it never occurs in a pointer pointing at a valid area of memory. You can use 0 to initialise a pointer so that you know it isn't pointing at anything real. So all we have to do is set the last pointer in the list to 0 and then test for it That is:

 

thisptr=start;

while (thisptr!=0)

 {

  printf("%d",thisprt->data);

  thisprt=thisprt-> prt;

 }