C - From The Top (part 14)

Welcome to the penultimate non-WIMP instalment of the C from the Top series - this time, we'll look at memory.

There are four functions in the <stdlib.h> header file to deal with dynamic memory allocation (for those using pre-RISC OS 3.5 machines, don't worry - this isn't the same as the dynamic memory areas on OS 3.5 and above). These functions are (including the prototypes):

void *calloc(size_t num, size_t size);
void free(void *pointer);
void *malloc(size_t size);
void *realloc(void *pointer, size_t size);

The important aspect to remember is that all (with the exception of free) are pointers - thinking about it, you would expect it, as to use one of these you use a pointer variable which, as you know, points to a block of memory!

Another good point about the three alloc functions is that they return the first byte of the allocated area. If this returns NULL, we know the machine doesn't have enough memory.

This requires four lines of code to check if NULL has been returned

if (!pointer) {
printf("Allocate failed\n");
exit(1);
}

These four lines can help avoid the dreaded fatal error of not having enough memory on the stack.

The heap?

The heap can be thought of as a large pool of water which the program 'drinks' from. When the pool is empty, the program can drink no more and goes thirsty. The thirsty program then dies or tries to grab memory from elsewhere (which is what happens in Windows giving you either the blue screen of death or General Protection Fault).

Due to hardware restrictions on all current RISC OS machines (e.g. Acorn and RiscStation), we are limited to 28Mb - hopefully RISCOS Ltd will have a hardware independent version in due course; until then we have to make do with 28Mb.

What the functions do

calloc allocates a block of memory equal to num. This allocation is sufficient for an array of num objects, size long, where size is the sizeof(var_type).
free frees up the stack which pointer has had allocated to it. It is vitally important that, after a block of memory has been finished with, the free function is called. This is one of the most common methods of memory leak encountered. Unchecked, this can lead to the program ultimately failing.
malloc allocates a block of memory of size size. malloc is very useful in the WIMP system. For example, below would be how to use one of the swi commands as a struct - it is then allocated to a block of memory within the main part of the program. This is actually a very good example of how to use and create your own header files.

struct {
        char log2sectorsize;
        unsigned char sectors_per_track;
/* I've snipped a large chunk out here as it's not really needed to demonstrate the point!*/
       }  disc_record,*block;

then in the main program:

block=malloc(sizeof(disc_record));

That's nice and simple, but how on earth do you either read or write to the block of memory used?

You would do it in exactly the same way as you would with any normal structure, by using

struct_block.var_name =

to allocate the value or

struct_block->var_name

to read the variable name - for instance...

printf("log2sectorsize : %d\n",block->log2sectorsize);
printf("sectors per track : %d\n",block->sectors_per_track);

and

regs.r[4]=block->disc_size;

realloc changes the size of the allocated memory (from pointer) to the new size, size. A pointer to the memory block is returned as it is possible for realloc to move the block for the size increase to occur. If there is enough memory, everything is transferred, otherwise a NULL is returned. This function must be checked for the return value.

Strictly speaking, whenever you create a string, you should dynamically allocate the space to the array rather than have the computer do it for you.

This would be carried out thus

char *name1, *name2;
name1=(char*) malloc(9);
name2=(char*) malloc(9);

name1 and name2 are then treated as any other string variable, except that only 10 bytes have been assigned (0 to 9 inclusive) instead of assigning a large array to cope with the input.

From what I can see, there is only one advantage of using a system such as this - you are assured of allocating the memory to the variable (of course, this supposes the memory is there in the first place).

Command me...

So far, when we have created a program, we've not had any arguments to pass to the main() function. Sometimes though, command line programs can come in very useful (for example, on the cover disc is a program called arcfixer which requires command lines arguments - CLA).

While one may argue that, in the days of the WIMP, CLA programs shouldn't need to be used, it can also be argued that, for personal use, or when the program is not that complex, a CLA has a larger number of attractors than detractors (such as speed of writing and speed of both execution and debugging).

How to do it

The normal protocol for main() is

int main(void)

However, for a CLA, this has to be changed to

int main(int argc, char *argv[]}

When this is called, argc returns the number of arguments given. In a non-CLA program, this is 1 (the command itself). However, in a CLA program, argc still reports the number of arguments given, but remember that argv is a pointer to an array; therefore the first argument (the command) is argv[0] with the final one being argv[n].

The program below demonstrates this

#include <stdio.h>
#include <stdlib.h>

int main(int argc,char *argv[])
{
 int loop;
 if (argc==1)
 {
  printf("You have given me %d argument\n",argc);
  printf("That is %s",argv[argc-1]);
  exit(1);
 }
 else
 {
  printf("You have given me %d arguments\n",argc);
  printf("They are : \n");
  for (loop=0;loop<argc;loop++)
  printf("%s\n",argv[loop]);
 }
 return 0;
}

This can get a tad confusing - just remember that if you have a program needing three arguments, the last argument (argc==3) will be the last argument - 1 in argv (argv[2]).

You should also check within the program to ensure that the correct number of arguments have been passed over in the command line. It would be of no use to run a JPEG viewer without telling the program which JPEG to view, or a copier without telling the copier what to copy.

Suppose you had a program which required the following arguments

file_copy <source> <dest.>

you need to check firstly that the correct number of arguments have been passed before you check if <source> and <dest.> actually exists.

As you know, argc holds the number of arguments passed in the command line; therefore you should use code such as:

if (argc!=3)
{
 printf("Usage\n");
 printf("file_copy <source> <destination>\n");
 exit (1);
}

Next month

The final tutorial, before hitting the WIMP, will be on some bits and pieces which I have left out until now for a good reason - they didn't quite fit in anywhere else!

Until then, keep on writing your C programs. Remember, they may be all CLI material, but the more you write, the better you will become.


Source: Archive Magazine 14.2
Publication: Archive Magazine
Contributor: Paul Johnson