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