C - From The Top (part 15)

In this final part of the series, I'll deal with various bits and bobs - the left-overs.

1) Variable modifiers

A variable modifier adjusts the variable type.

A const variable (const is short for constant) is one which cannot be changed by your program. For instance, if you had a line in a program which read something like:

const int a_number=10;

and somewhere else you had the line:

a_number++;

the compiler would spit this out and complain violently because consts can also be used in function protocols.

void some_fn(const char *p);

This sets the pointer parameter to be such that it cannot be changed in the function, only accessed - it's very similar to making a floppy disc read only!

A variable type preceded by volatile tells the compiler that the variable can be altered by the program and also by other means (for instance, the variable may contain the results of a signal sent by a device through the serial port).

volatile is important in this respect. When you compile your C application, the compiler will try to optimise the code in such a way as to make the final application run as quickly as possible. If the volatile variable type modifier (which both volatile and const are really) hasn't been used, the program may not examine the contents of the variable each time it is accessed - upshot, you get something which wouldn't look out of place in some "off the shelf" PC software.

An example of using volatile would be something like this

volatile unsigned var;
some_interrupt_address(&var);
for (;;)
 printf("%d",u);

In this example, if var hadn't been declared volatile, the compiler would optimise to the printf, so that var didn't always get checked. However, it was declared volatile and so is checked whenever it is referenced.

BOOL (short for BOOLean - a form of mathematics) is simple. It's either true or false. Most commonly, BOOL is used as a function return variable e.g.

BOOL some_function(params)
{
 :
}

this would test whatever is inside the braces and return either a TRUE or FALSE value.

2) Storage class specifiers

There are four of these, one of which is never used: auto (never used), extern, register and static.

While what we have done so far has been very small scale (well, the tutorial code has been - the tasks may not have been!), if you looked at the source code for something like Impression, you would see that the source would be huge and split into a large number of files. Why should this be? Surely it would be simpler to have one large file?

Wrong. Try finding a bug in 30,000 lines of source code when it's all in one file! Using small files actually helps in developing software - it makes debugging simpler (there is less to plough through for a start), cuts down on compiling time and makes expansion easier.

When using multiple files, it is usual to set up something known as Global Variables - these are variables which are used throughout the application. They are defined once in one file. To access the global variable from another file, the extern keyword is used. Consider this:

File 1

#include <stdio.h>
int counter; // global var
void func_1(void);
int main(void)
{
 int loop;
 func_1();
 for (loop=0;loop<counter;loop++)
  printf("%d",loop);
 return 0;
}

File 2

#include <stdlib.h>
void func_1(void)
{
 counter=rand();
}

File 1 will compile with no problems. File 2 won't compile as counter hasn't been initialised.

What's the answer? Would you add the line int counter; after the #include line? No, because adding the int counter; will just make the linker complain that you've defined counter twice. This is where you would use extern.

If the second file has the line

extern int counter;

added after the #include statement, the compiler will compile and link the code happily.

Register now!

If you define a variable as a register variable, you're telling the compiler that you want this variable to be accessed more quickly than a normal variable. The fastest way to do this is to store it in a CPU register (which is what happens with int and char variable types - the other types are held in such a way as to reduce the access time - say at the top of the heap).

As with any method of storage though, there are only a certain number of registers to go around - when they're all used up, they're gone. It is therefore a good idea only to use them for frequently used variables.

An important thing to remember though - as a register variable may be held in a register of the CPU - is that it may not have a memory address and so the & cannot be used.

Finally, we come to the static modifier. These are initialised once, but have the advantage of preserving the contents of the local variable between function calls. The following example shows this nicely.

#include <stdio.h>
void func(void);
int main(void)
{
 int loop;
 for (loop=0;loop<10;loop++)
 func();
 return 0;
}
void func(void)
{
 static int counter=0;
 counter++;
 printf("Counter = %d",counter);
}

this will display

count = 1
count = 2
:
count = 10

and count has kept its value between the function calls.

static can also be used on global variables. When this happens, it causes the variable to be known to, and accessed by, only the functions in the file in which it was declared. Any other files can't access this variable and so won't know of its existence - the upshot is that file #1 can have a variable called fred and file #2 can also have a variable called fred, only in the case of #2, fred was declared using the static modifier.

3) sprintf()

The world has really been spoiled by Basic. I've not come across a language yet with such a super method of dynamic allocation of space for a string or for string handling in general.

Take for instance

a$=mid$(var$,start%,len%)

There is nothing like that in C - the nearest you'd get would be something like

strncpy(string_1,string_2,no_of_bytes)

which copies n bytes from string 2 to string 1, but even this doesn't do the same. I've included on the cover disc possible ways of doing left$, right$ and mid$, but none are that good!

Face it, in what other language can you just say

a$=""
IF MID$(r$,3,LEN b$)="fred" a$=MID$(r$,3,LEN b$) ELSE a$="Billy Bob"

a$ started out life 0 characters long and could end up being nine characters long (or longer).

Another nice one in Basic is a line such as

a$="I am "+STR$(age%)+" years old"

but here C has a response - sprintf(). This allows you create a variable such as the one above. Here is the format:

sprintf(newname,"%delimiters", delimited_variables_list)
newname has to be defined as a pointer array capable of holding the contents of the delimited variable list. %delimiters are the likes of %d %c and %s. The delimited variables list contains variables associated with the %delimiters.

For example:

char *newname=malloc(40*sizeof(char));
char text[][20]={"The answer is "," and PI = "};
int number1=4,number2=2;
float pi=3.141593;
sprintf(newname,"%s%d%d%s%f",text[0],number1,number2,text[1],pi);
printf("%s",newname);

will output

The answer is 42 and PI = 3.141593

As you can see, sprintf() is a very powerful function.

That about wraps up the non-WIMP side of things and is the end of C from the Top.


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