This lesson and the following one will examine how
to use the program structure - as opposed to data structure
- reserved words.
Lets start with the looping structures:
do repeated_statement while ( logical_expression );
repeated_statement, which may be a block of code,
will be executed repetitively until the logical_expression,
becomes false. If you have been exposed to ( corrupted
by? ) another language remember that there is no `until'
test at the end of a loop. Note that the repeated_statement
is always executed once irrespective of the state of
the logical_expression.
while ( logical_expression ) repeated_statement;
repeated_statement is executed repetitively while
the logical_expression is true. Once again statement
may be a block of code. Note that if the logical_expression
evaluates to FALSE then the repeated_statement is NEVER
executed.
Associated with the looping structures are the control
words:
break;
continue;
break; allows you to leave a loop in the middle of
a block, and continue; allows you to re-start it from
the top.
Finally we must not forget the most common and useful
looping construct:
for ( initialising statement; logical_expression;
incremental_statement )
repeated_statement;
Some further explanation is needed. The initialising
statement is executed once, but to allow for the need
to initialise several separate variables the assignment
statements may be separated by commas. The logical_expression
must be true for the loop to run, and the incremental_statement
is executed once each time the loop is run. The for
statement is completely general and may, for example,
be used to manipulate a set of pointers to operate on
a linked list.
Some examples.
A do loop program.
#ident "@(#) do_demo.c - An example of the do
loop"
#include <stdio.h>
main()
{
char character;
character = 'a';
do printf ( "%c", character ); while ( character++
< 'z' );
printf ( "\n" );
}
Fairly obviously it prints:
abcdefghijklmnopqrstuvwxyz
A while loop example.
#ident "@(#) while_demo.c - An example of the
while loop"
#include <stdio.h>
main()
{
char character;
character = 'a';
while ( character <= 'z' ) printf ( "%c",
character++ );
printf ( "\n" );
}
Its output is exactly the same as the previous example:
abcdefghijklmnopqrstuvwxyz
In this totally trivial case it is irrelevant which
program structure you use, however you should note that
in the `do' program structure the repeated statement
is always executed at least once.
A for loop example.
The `for' looping structure.
#ident "@(#) for_demo.c - An example of the for
loop"
#include <stdio.h>
main()
{
char character;
for ( character = 'a'; character <= 'z' ; character++
)
{
printf ( "%c", character );
}
printf ( "\n" );
}
Surprise, Surprise!
abcdefghijklmnopqrstuvwxyz
You should be aware that in all the looping program
structures, the repeated statement can be a null statement
( either just a `;' or the reserved word `continue;'
). This means that it is possible to - for example -
position a pointer, or count up some items of something
or other.
It isn't particularly easy to think up a trivial little
program which demonstrates this concept, however the
two `for' loops give some indication of the idea.
#ident "@(#) pointer_demo.c - Pointer operations
with the for loop"
#include <stdio.h>
main()
{
char character, *character_pointer, alphabets [ 53 ];
for ( character = 'a', character_pointer = alphabets;
/* Start conditions */
character <= 'z'; /* Run while true */
*character_pointer++ = character++ /* All the work */
)TRUE continue;
for ( character = 'A'; /* character_pointer is at
the right place already */
character <= 'Z';
*character_pointer++ = character++
) continue;
*character_pointer = (char) '\000'; /* NULL character
to terminate string. */
printf ( "%s\n\n", alphabets );
}
Another Surprise!
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
So much for the looping structures provided by the
`C' language. The other main structures required to
program a computer are the ones which alter the program
flow. These are the switch, and the if and its extension
the if ... else combination. More demo programs are
much the best way of getting the message across to you,
so here they are, first the if construct.
#ident "if_demo.c"
#include <stdio.h>
main(argc, argv)
int argc;
char **argv;
{
if ( argc > 1 ) printf ( "You have initiated
execution with arguments."};
}
And the if ... else demo.
#ident "if_else_demo.c"
/*
** The Language #define could go in the compiler invocation
line if desired.
*/
#define ENGLISH
#include <stdio.h>
/*
** The message and text fragments output by the program.
*/
char *messages[] =
{
#if defined( ENGLISH )
#ident "@(#)ENGLISH Version"
"\nUsage: if_else_demo <numeric argument 1>
<numeric argument 2>\n\n",
"The first argument is ",
"the second",
"equal to ",
"bigger than ",
"smaller than "
#endif
#if defined( FRANCAIS )
#ident "@(#)FRENCH Version"
put the French translation in here so that we are
ready to export to French speaking Countries. I'd be
grateful if a French speaker could make the translation
for me.
#endif
};
/*
** Meaningful words defined to constants
*/
#define USAGE 0
#define FIRST 1
#define SECOND 2
#define EQUAL 3
#define BIGGER 4
#define SMALLER 5
#define SUCCESS 0
#define FAILURE 1
/*
** We need this more than once so it can be put in a
function.
*/
void usage()
{
printf ( messages[USAGE]);
exit ( FAILURE );
}
/*
** Main program function starts here. ( At the top of
a page no less! )
*/
int main ( argc, argv )
int argc;
char **argv;
{
int message_index;
double i, j, strtod();
char *ptr;
if ( argc != 3 ) usage(); /* have we been given the
right */
/* number of arguments. */
i = strtod ( argv[1], &ptr); /* Convert to a double
float. */
if ( ptr == argv[1] ) usage(); /* Successful conversion?
*/
j = strtod ( argv[2], &ptr); /* Convert to a double
float. */
if ( ptr == argv[2] ) usage(); /* Successful conversion?
*/
/*
** This statement uses the "ternary conditional
assignment" language
** construction to assign the value required to the
message indexing variable.
** Note that this concept is efficient in both the generation
of machine code
** output ( compile the program with a -S switch and
have a look ) and in the
** ease with which it can be understood. The assignment
is obvious instead of
** being buried under a litter of `if' and `else' keywords.
*/
message_index = ( i == j ) ? EQUAL : ( i > j )
? BIGGER : SMALLER;
/*
** Now print the message.
*/
(void) printf ( "\n%s%s%s\n\n", /* Format
string specifying 3 strings. */
messages[ FIRST ], /* Address of string. */
messages[ message_index ], /* ditto. */
messages[ SECOND ] /* ditto. */
);
return ( SUCCESS );
}
Well as you can no doubt gather it simply compares
two numbers on the command line and ejects a little
message depending on the relative magnitude of the numbers.
In the UNIX tradition the help message is perhaps somewhat
terse, but it serves the purpose of getting you - the
student - to think about the importance of creating
programs which always cope with nonsensical input in
a civilised way. Here are the lines of output.
Usage: if_else_demo <numeric argument 1> <numeric
argument 2>
The first argument is equal to the second
The first argument is smaller than the second
The first argument is bigger than the second
Now that the international community is shrinking
with vastly improved telecommunications, it is perhaps
a good idea to think carefully about creating programs
which can talk in many languages to the users. The method
of choice is - I believe - that presented above. The
#if defined( LANGUAGE ) gives us an easy method of changing
the source code to suit the new sales area. Another
possibility is to put all the text output needed from
a program
into a file. The file would have to have a defined layout
and some consistent
way of `getting at' the message strings.
From a commercial point of view this may or may not
be a good business plan. Quite definitely it is an absolute
no no to scatter a mass of string literals containing
the messages and message fragments all over your program
script.
There are two more methods of altering the program
flow.
1 ) The goto a label.
2 ) The setjump / longjmp library routines.
The concept of the go to a label construction has
had reams of literary verbiage written about it and
this author does not intend to add to the pile. Suffice
it to say that a goto is a necessary language construct.
There are a few situations which require the language
to have ( in practice ) some form of unconditional jump.
Treat this statement with great caution if you wish
your
code to be readable by others. An example of legitimate
use.
for ( a = 0; a < MATRIX_SIZE; a++ )
{
for ( b = 0; b < MATRIX_SIZE; b++ )
{
if ( process ( matrix, a, b )) goto bad_matrix;
}
}
return ( OK );
bad_matrix:
perror ( progname, "The data in the matrix seems
to have been corrupted" );
return ( BAD );
This is one of the very few "legitimate"
uses of goto, as there is no
"break_to_outer_loop" in `C'. Note that some
compilers complain if the label is not immediately followed
by a statement. If your compiler is one of these naughty
ones, you can put either a `;' or a pair of braces `{}'
after the
`:' as a null statement.
An example of a program package which makes extensive
use of the goto is the rz and sz modem communications
protocol implementation by Chuck Forsberg of Omen Technology.
You should download it and study the code, but do remember
that the proof of the pudding argument must apply as
the rz & sz system has become extremely popular
in its application because it works so well.
The other method of changing program flow is the setjump
and longjmp pair of library functions. The idea is to
provide a method of recovery from errors which might
be detected anywhere within a large program - perhaps
a compiler, interpreter or large data acquisition system.
Here is the trivial example:
#ident "set_jmp_demo.c"
#include <stdio.h>
#include <setjmp.h>
jmp_buf save;
main()
{
char c;
for ( ;; ) /* This is how you set up a continuous
loop.
*/
{
switch ( setjmp( save ))
{
case 0:
printf ( "We get a zero returned from setjmp on
setup.\n\n");
break; /* This is the result from setting up. */
case 1:
printf ( "NORMAL PROGRAM OPERATION\n\n" );
break;
case 2:
printf ( "WARNING\n\n" );
break;
case 3:
printf ( "FATAL ERROR PROGRAM TERMINATED\n\nReally
Terminate? y/n: " );
fflush ( stdout );
scanf ( "%1s", &c );
c = tolower ( c );
if ( c == 'y' ) return ( 1 );
printf ( "\n" );
break;
default:
printf ( "Should never return here.\n" );
break;
}
process ();
}
}
process ()
{
int i;
printf ( "Input a number to simulate an error
condition: " );
fflush ( stdout );
scanf ( "%d", &i );
i %= 3;
i++; /* So that we call longjmp with 0 < i < 4
*/
longjmp ( save, i);
}
Although in this silly little demo the call to longjmp
is in the same file as the call to setjmp, this does
not have to be the case, and in the practical situation
the call to longjmp will be a long way from the call
to setjmp. The mechanism is that setjmp saves the entire
state of the computer's CPU in a buffer declared in
the jmp_buf save; statement and longjmp restores it
exactly
with the exception of the register which carries the
return value from longjmp. This value is the same as
the second argument in the longjmp call - i in our little
demo. This means, of course, that the stack and frame
pointer registers are reset to the old values and all
the local variables being used at the time of the longjmp
call are going to be lost forever. One consequence of
this is that any pointer to memory allocated from the
heap will also be lost, and you will be unable to access
the data stored in the buffer. This is what the jargonauts
call "memory leakage", and is really very
difficult bug to find. Your program runs out of dynamic
memory long before it should. Take care. So you have
to keep a record of the buffers' addresses and free
them before the call to longjmp.
Lesson # 1, #
2, # 3, #
4, # 5, #
6, # 7, #
8
|