COmplete C
COmplete C
COmplete C
This is a C Programming Tutorial for people who have a little experience with an
interpreted programming language, such as Emacs Lisp or a GNU shell.
• Preface:
• Introduction:
• Reserved words & example:
• Operating systems:
• Libraries:
• Programming style:
• Form of a C program:
• Comments:
• Functions:
• Variables:
• Parameters:
• Scope:
• Preprocessor:
• Pointers:
• Standard Output and Standard Input:
• Assignments Expressions and Operators:
• Decisions:
• Loops:
• Arrays:
• Strings:
• Putting together a program:
• Special Library Functions and Macros:
• Hidden Operators:
• More on Data Types:
• Machine Level Operations:
• Files and Devices:
• Structures and Unions:
• Data structures:
• Recursion:
• Example Programs chapter:
• Errors and debugging:
• Summary:
• reserved words list:
• Comparisons :
• Character Conversion Table:
• Emacs style file:
• Answers to questions:
• Index:
Preface
Every program is limited by the language which is used to write it. C is a
programmer's language. Unlike BASIC or Pascal, C was not written as a teaching aid,
but as an implementation language. C is a computer language and a programming tool
which has grown popular because programmers like it! It is a tricky language but a
masterful one. Sceptics have said that it is a language in which everything which can
go wrong does go wrong. True, it does not do much hand holding, but also it does not
hold anything back. If you have come to C in the hope of finding a powerful language
for writing everyday computer programs, then you will not be disappointed. C is
ideally suited to modern computers and modern programming.
This book is a tutorial. Its aim is to teach C to a beginner, but with enough of the
details so as not be outgrown as the years go by. It presumes that you have some
previous aquaintance with programming -- you need to know what a variable is and
what a function is -- but you do not need much experience. It is not essential to follow
the order of the chapters rigorously, but if you are a beginner to C it is recommended.
When it comes down to it, most languages have basically the same kinds of features:
variables, ways of making loops, ways of making decisions, ways of accessing files
etc. If you want to plan your assault on C, think about what you already know about
programming and what you expect to look for in C. You will most likely find all of
those things and more, as you work though the chapters.
The examples programs range from quick one-function programs, which do no more
than illustrate the sole use of one simple feature, to complete application examples
occupying several pages. In places these examples make use of features before they
have properly been explained. These programs serve as a taster of what is to come.
Introduction
• Levels:
• Basic ideas:
• The compiler:
• Errors:
• Use of Upper and Lower Case:
• Questions 1:
High Levels and Low Levels
Any kind of object that is sufficiently complicated can be thought of as having levels
of detail; the amount of detail we see depends upon how closely we scrutinize it. A
computer falls definitely into the category of complex objects and it can be thought of
as working at many different levels. The terms low level and high level are often used
to describe these onion-layers of complexity in computers. Low level is perhaps the
easiest to understand: it describes a level of detail which is buried down amongst the
working parts of the machine: the low level is the level at which the computer seems
most primitive and machine-like. A higher level describes the same object, but with
the detail left out. Imagine stepping back from the complexity of the machine level
pieces and grouping together parts which work together, then covering up all the
details. (For instance, in a car, a group of nuts, bolts, pistons can be grouped together
to make up a new basic object: an engine.) At a high level a computer becomes a
group of black boxes which can then be thought of as the basic components of the
computer.
C is called a high level, compiler language. The aim of any high level computer
language is to provide an easy and natural way of giving a programme of instructions
to a computer (a computer program). The language of the raw computer is a stream of
numbers called machine code. As you might expect, the action which results from a
single machine code instruction is very primitive and many thousands of them are
required to make a program which does anything substantial. It is therefore the job of
a high level language to provide a new set of black box instructions, which can be
given to the computer without us needing to see what happens inside them - and it is
the job of a compiler to fill in the details of these "black boxes" so that the final
product is a sequence of instructions in the language of the computer.
C is one of a large number of high level languages which can be used for general
purpose programming, that is, anything from writing small programs for personal
amusement to writing complex applications. It is unusual in several ways. Before C,
high level languages were criticized by machine code programmers because they
shielded the user from the working details of the computer, with their black box
approach, to such an extent that the languages become inflexible: in other words, they
did not not allow programmers to use all the facilities which the machine has to offer.
C, on the other hand, was designed to give access to any level of the machine down to
raw machine code and because of this it is perhaps the most flexible of all high level
languages.
Another unusual feature of C is the way it can express ideas concisely. The richness
of a language shapes what it can talk about. C gives us the apparatus to build neat and
compact programs. This sounds, first of all, either like a great bonus or something a
bit suspect. Its conciseness can be a mixed blessing: the aim is to try to seek a balance
between the often conflicting interests of readability of programs and their
conciseness. Because this side of programming is so often presumed to be understood,
we shall try to develop a style which finds the right balance.
C allows things which are disallowed in other languages: this is no defect, but a very
powerful freedom which, when used with caution, opens up possibilities enormously.
It does mean however that there are aspects of C which can run away with themselves
unless some care is taken. The programmer carries an extra responsibility to write a
careful and thoughtful program. The reward for this care is that fast, efficient
programs can be produced.
C tries to make the best of a computer by linking as closely as possible to the local
environment. It is no longer necessary to have to put up with hopelessly inadequate
input/output facilities anymore (a legacy of the timesharing/mainframe computer era):
one can use everything that a computer has to offer. Above all it is flexible. Clearly no
language can guarantee intrinsically good programs: there is always a responsibility
on the programmer, personally, to ensure that a program is neat, logical and well
organized, but it can give a framework in which it is easy to do so.
The aim of this book is to convey some of the C philosophy in a practical way and to
provide a comprehensive introduction to the language by appealing to a number of
examples and by sticking to a strict structuring scheme. It is hoped that this will give a
flavour of the kind of programming which C encourages.
Using a compiler language is not the same as using an interpreted language like
BASIC or a GNU shell. It differs in a number of ways. To begin with, a C program
has to be created in two stages:
• Firstly, the program is written in the form of a number of text files using a
screen editor. This form of the program is called the source program. It is not
possible to execute this file directly.
• Secondly, the completed source file is passed to a compiler--a program which
generates a new file containing a machine code translation of the source text.
This file is called an object file or executable file. The executable file is said to
have been compiled from the source text.
Compiler languages do not usually contain their own editor, nor do they have words
like RUN with which to execute a finished program. You use a screen editor to create
the words of a program (program text) and run the final program in its compiled form
usually by simply typing the name of the executable file.
• The compiler:
• Errors:
The Compiler
A C program is made by running a compiler which takes the typed source program
and converts it into an object file that the computer can execute. A compiler usually
operates in two or more phases (and each phase may have stages within it). These
phases must be executed one after the other. As we shall see later, this approach
provides a flexible way of compiling programs which are split into many files.
To avoid the irritation of typing two or three separate commands (which are often
cumbersome) you will normally find a simple interface for executing compiler.
Traditionally this is an executable program called cc for C Compiler:
cc filename
gcc filename
On GNU systems, this results in the creation of an executable program with the
default name a.out. To tell the compiler what you would like the executable program
to be called, use the -o option for setting the name of the object code:
For example, to create a program called myprog from a file called myprog.c, write
Syntax
Errors in the syntax, or word structure of a program are caught before you run
it, at compilation time by the compiler program. They are listed all in one go,
with the line number, in the text file, at which the error occurred and a
message to say what was wrong.
(If you compile the program in Emacs, you can jump directly to the error.)
A program with syntax errors will cause a compiler program to stop trying to
generate machine code and will not create an executable. However, a compiler
will usually not stop at the first error it encounters but will attempt to continue
checking the syntax of a program right to the last line before aborting, and it is
common to submit a program for compilation only to receive a long and
ungratifying list of errors from the compiler.
It is a shock to everyone using a compiler for the first time how a single error
can throw the compiler off course and result in a huge and confusing list of
non-existent errors, following a single true culprit. The situation thus looks
much worse than it really is. You'll get used to this with experience, but it can
be very disheartening.
As a rule, look for the first error, fix that, and then recompile. Of course, after
you have become experienced, you will recognize when subsequent error
messages are due to independent problems and when they are due to a
cascade. But at the beginning, just look for and fix the first error.
Intention
Errors in goal or purpose (logical errors) occur when you write a program that
works, but does not do what you intend it to do. You intend to send a letter to
all drivers whose licenses will expire soon; instead, you send a letter to all
drivers whose licenses will expire sometime.
If the compilation of a program is successful, then a new file is created. This file will
contain machine code which can be executed according to the rules of the computer's
local operating system.
When a programmer wants to make alterations and corrections to a C program, these
have to be made in the source text file itself using an editor; the program, or the
salient parts, must then be recompiled.
Declarations
Compiler languages require us to make a list of the names and types of all variables
which are going to be used in a program and provide information about where they
are going to be used. This is called declaring variables. It serves two purposes: firstly,
it provides the compiler with a definitive list of the variables, enabling it to cross
check for errors, and secondly, it informs the compiler how much space must be
reserved for each variable when the program is run. C supports a variety of variable
types (variables which hold different kinds of data) and allows one type to be
converted into another. Consequently, the type of a variable is of great importance to
the compiler. If you fail to declare a variable, or declare it to be the wrong type, you
will see a compilation error.
Questions
1. What is a compiler?
2. How is a C program run?
3. How is a C program compiled usually?
4. Are upper and lower case equivalent in C?
5. What the two different kinds of error which can be in a program?
Words used in included libaries are also, effectively, reserved. If you use a word
which has already been adopted in a library, there will be a conflict between your
choice and the library.
Libraries provide frequently used functionality and, in practice, at least one library
must be included in every program: the so-called C library, of standard functions. For
example, the stdio library, which is part of the C library, provides standard facilities
for input to and output from a program.
In fact, most of the facilities which C offers are provided as libraries that are included
in programs as plug-in expansion units. While the features provided by libraries are
not strictly a part of the C language itself, they are essential and you will never find a
version of C without them. After a library has been included in a program, its
functions are defined and you cannot use their names.
• printf:
• Example 1:
• Output 1:
• Questions 2:
Text is easy, but we also want to be able to print out the contents of variables. These
can be inserted into a text string by using a `control sequence' inside the quotes and
listing the variables after the string which get inserted into the string in place of the
control sequence. To print out an integer, the control sequence %d is used:
The variable someinteger is printed instead of %d. The printf function is described
in full detail in the relevant chapter, but we'll need it in many places before that. The
example program below is a complete program. If you are reading this in Info, you
can copy this to a file, compile and execute it.
Example Listing
/***********************************************************/
/* Short Poem */
/***********************************************************/
#include <stdio.h>
/***********************************************************/
main () /* Poem */
{
printf ("Astronomy is %dderful \n",1);
printf ("And interesting %d \n",2);
printf ("The ear%d volves around the sun \n",3);
printf ("And makes a year %d you \n",4);
printf ("The moon affects the sur %d heard \n",5);
printf ("By law of phy%d great \n",6);
printf ("It %d when the the stars so bright \n",7);
printf ("Do nightly scintill%d \n",8);
printf ("If watchful providence be%d \n",9);
printf ("With good intentions fraught \n");
printf ("Should not keep up her watch divine \n");
printf ("We soon should come to %d \n",0);
}
Output
Astronomy is 1derful \n"
And interesting 2
The ear3 volves around the sun
And makes a year 4 you
The moon affects the sur 5 heard
By law of phy6d great
It 7 when the the stars so bright
Do nightly scintill8
If watchful providence be9
With good intentions fraught
Should not keep up her watch divine
We soon should come to 0
Questions
1. Write a command to print out the message "Wow big deal".
2. Write a command to print out the number 22?
3. Write two commands to print out "The 3 Wise Men" two different ways.
4. Why are there only a few reserved command words in C?
Operating systems vary widely but most have a command language or shell which can
be used to type in commands. Recently the tendency has been to try to eliminate
typing completely by providing graphical user interfaces (GUIs) for every purpose.
GUIs are good for carrying out simple procedures like editing, but they are not well
suited to giving complicated instructions to a computer. For that one needs a
command language. In the network version of this book we shall concentrate on Unix
shell commands since they are the most important to programmers. On
microcomputers command languages are usually very similar in concept, though more
primitive, with only slightly different words for essentially the same commands. (This
is a slightly superficial view).
When most compiler languages were developed, they were intended to be run on large
mainframe computers which operated on a multi-user, time-sharing principle and
were incapable of interactive communication with the user. Many compiler languages
still have this inadequacy when carried over to modern computers, but C is an
exception, because of its unique design. Input and output are not actually defined as a
fixed, unchanging part of the C language. Instead there is a standard file which has to
be included in programs and defines the input/output commands that are supported by
the language for a particular computer and operating system. This file is called a
standard C library. (See the next chapter for more information.) The library is
standard in the sense that C has developed a set of functions which all computers and
operating systems must implement, but which are specially adapted to your system.
• Files devices:
• Filenames:
• Command languages:
• Questions 3:
Filenames
The compiler uses a special convention for the file names, so that we do not confuse
their contents. The name of a source program (the code which you write) is
filename.c. The compiler generates a file of object code from this called
filename.o, as yet unlinked. The final program, when linked to libraries is called
filename on Unix-like operating systems, and filename.EXE on Windows derived
systems. The libraries themselves are also files of object code, typically called
liblibraryname.a or liblibraryname.so. Header files are always called
libname.h.
The endings `dot something' (called file extensions) identify the contents of files for
the compiler. The dotted endings mean that the compiler can generate an executable
file with the same name as the original source - just a different ending. The quad file
and the object file are only working files and should be deleted by the compiler at the
end of compilation. The .c suffix is to tell the compiler that the file contains a C
source program and similarly the other letters indicate non-source files in a
convenient way. To execute the compiler you type,
cc filename
For example,
cc foo.c
ls -l
less filename
emacs filename
In a large operating system (or even a relatively small one) it can be a major feat of
recollection to know all of the commands. Fortunately it is possible to get by with
knowing just handful of the most common ones and having the system manual around
to leaf through when necessary.
Another important object is the `panic button' or program interruption key. Every
system will have its own way of halting or terminating the operation of a program or
the execution of a command. Commonly this will involve two simultaneous key
presses, such as CTRL C, CTRL Z or CTRL-D etc. In GNU/Linux, CTRL-C is used.
Questions
1. What is an operating system for?
2. What is a pseudo-device name?
3. If you had a C source program which you wanted to call `accounts' what name
would you save it under?
4. What would be the name of the file produced by the compiler of the program
in 3?
5. How would this program be run?
Libraries
The core of the C language is small and simple. Special functionality is provided in
the form of libraries of ready-made functions. This is what makes C so portable. Some
libraries are provided for you, giving you access to many special abilities without
needing to reinvent the wheel. You can also make your own, but to do so you need to
know how your operating system builds libraries. We shall return to this later.
Libraries are files of ready-compiled code which we can merge with a C program at
compilation time. Each library comes with a number of associated header files which
make the functions easier to use. For example, there are libraries of mathematical
functions, string handling functions and input/output functions and graphics libraries.
It is up to every programmer to make sure that libraries are added at compilation time
by typing an optional string to the compiler. For example, to merge with the math
library libm.a you would type
when you compile the program. The -lm means: add in libm. If we wanted to add in
the socket library libsocket.a to do some network programming as well, we would
type
Why are these libraries not just included automatically? Because it would be a waste
for the compiler to add on lots of code for maths functions, say, if they weren't
needed. When library functions are used in programs, the appropriate library code is
included by the compiler, making the resulting object code often much longer.
Libraries are supplemented by header files which define macros, data types and
external data to be used in conjunction with the libraries. Once a header file has been
included, it has effectively added to the list of reserved words and commands in the
language. You cannot then use the names of functions or macros which have already
been defined in libraries or header files to mean anything other than what the library
specifies.
The most commonly used header file is the standard input/output library which is
called stdio.h. This belongs to a subset of the standard C library which deals with
file handling. The math.h header file belongs to the mathematics library libm.a.
Header files for libraries are included by adding to the source code:
#include header.h
#include "myheader.h"
#include <stdio.h>
Some functions can be used without having to include library files or special libraries
explicitly since every program is always merged with the standard C library, which is
called libc.
#include <stdio.h>
main ()
{
printf ("C standard I/O file is included\n");
printf ("Hello world!");
}
A program wishing to use a mathematical function such as cos would need to include
a mathematics library header file.
#include <stdio.h>
#include <math.h>
main ()
{ double x,y;
y = sin (x);
printf ("Maths library ready");
}
A particular operating system might require its own special library for certain
operations such as using a mouse or for opening windows in a GUI environment, for
example. These details will be found in the local manual for a particular C compiler or
operating system.
To know what names libraries have in a particular operating system you have to
search through its documentation. Unix users are lucky in having an online manual
which is better than most written ones.
• Questions 4:
Questions
1. How is a library file incorporated into a C program?
2. Name the most common library file in C.
3. Is it possible to define new functions with the same names as standard library
functions?
4. What is another name for a library file?
Programming style
No simple set of rules can ever provide the ultimate solution to writing good
programs. In the end, experience and good judgement are the factors which decide
whether a program is written well or poorly written. The main goal of any style is to
achieve clarity. Previously restrictions of memory size, power and of particular
compilers often forced restrictions upon style, making programs clustered and
difficult. All computers today are equipped with more than enough memory for their
purposes, and have very good optimizers which can produce faster code than most
programmers could write themselves without help, so there are few good reasons not
to make programs as clear as possible.
C is made up entirely of building blocks which have a particular `shape' or form. The
form is the same everywhere in a program, whether it is the form of the main program
or of a subroutine. A program is made up of functions, functions are made up of
statements and declarations surrounded by curly braces { }.
main ()
{
}
The parentheses () which follow the name of the function must be included even
though they apparently serve no purpose at this stage. This is how C distinguishes
The next most simple C program is perhaps a program which calls a function
do_nothing and then ends.
/******************************************************/
/* */
/* Program : do nothing */
/* */
/******************************************************/
{
do_nothing();
}
/******************************************************/
{
}
The program now consists of two functions, one of which is called by the other. There
are several new things to notice about this program. Firstly the function
do_nothing() is called by typing its name followed by the characteristic () brackets
and a semi-colon. This is all that is required to transfer control to the new function. In
some languages, words like CALL or PROC are used, or even a symbol like &. No
such thing is needed in C. The semi-colon is vital however. All instructions in C must
end with a semi-colon. This is a signal to inform the compiler that the end of a
statement has been reached and that anything which follows is meant to be a part of
another statement. This helps the compiler diagnose errors.
The `brace' characters { and } mark out a block into which instructions are written.
When the program meets the closing brace } it then transfers back to main() where it
meets another } brace and the program ends. This is the simplest way in which control
flows between functions in C. All functions have the same status as far as a program
is concerned. The function main() is treated just as any other function. When a
program is compiled, each function is compiled as a separate entity and then at the
end the linker phase in the compiler attempts to sew them all together.
The examples above are obviously very simple but they illustrate how control flows
in a C program. Here are some more basic elements which we shall cover.
• comments
• preprocessor commands
• functions
• declarations
• variables
• statements
The skeleton plan of a program, shown below, helps to show how the elements of a C
program relate. The following chapters will then expand upon this as a kind of basic
plan.
/****************************************************/
/* */
/* Skeleton program plan */
/* */
/****************************************************/
/****************************************************/
a=random();
b=function1();
function2(a,b);
}
/****************************************************/
function1 () /* Purpose */
{
....
}
/****************************************************/
int a,b;
{
....
}
• Question 5:
Neither comments nor preprocessor commands have a special place in this list: they
do not have to be in any one particular place within the program.
Questions
1. What is a block?
2. Name the six basic things which make up a C program.
3. Does a C program start at the beginning? (Where is the beginning?)
4. What happens when a program comes to a } character? What does this
character signify?
5. What vital piece of punctuation goes at the end of every simple C statement?
Comments
Annotating programs.
Comments are a way of inserting remarks and reminders into a program without
affecting its content. Comments do not have a fixed place in a program: the compiler
treats them as though they were white space or blank characters and they are
consequently ignored. Programs can contain any number of comments without losing
speed. This is because comments are stripped out of a source program by the compiler
when it converts the source program into machine code.
Because a comment is skipped over as though it were a single space, it can be placed
anywhere where spaces are valid characters, even in the middle of a statement, though
this is not to be encouraged. You should try to minimize the use of comments in a
program while trying to maximize the readability of the program. If there are too
many comments you obscure your code and it is the code which is the main message
in a program.
• Example comment:
• Example comment 2:
• Question 7:
Example 1
main () /* The almost trivial program */
Example 2
#include <stdio.h> /* header file */
#define NOTFINISHED 0
/**********************************************/
main ()
{ int i; /* declarations */
do
{
/* Nothing !!! */
}
while (NOTFINISHED);
Question
1. What happens if a comment is not ended? That is if the programmer types
/* .. to start but forgets the ..*/ to close?
Functions
Making black boxes. Solving problems. Getting results.
A function is a module or block of program code which deals with a particular task.
Making functions is a way of isolating one block of code from other independent
blocks of code. Functions serve two purposes. They allow a programmer to say: `this
piece of code does a specific job which stands by itself and should not be mixed up
with anyting else', and they make a block of code reusable since a function can be
reused in many different contexts without repeating parts of the program text.
identifier (parameter1,parameter2,..)
types of parameters
{ variable declarations
statements..
......
....
}
For example
Pythagoras(x,y,z)
double x,y,z;
{ double d;
d = sqrt(x*x+y*y+z*z);
In the newer ANSI standard, the same function is written slightly differently:
{ double d;
d = sqrt(x*x+y*y+z*z);
The name of a function in C can be anything from a single letter to a long word. The
name of a function must begin with an alphabetic letter or the underscore _ character
but the other characters in the name can be chosen from the following groups:
a .. z
(any letter from a to z)
A .. Z
(any letter from A to Z)
0 .. 9
(any digit from 0 to 9)
_
(the underscore character)
This means that sensible names can easily be chosen for functions making a program
easy to read. Here is a real example function which adds together two integer numbers
a and b and prints the result c. All the variables are chosen to be integers to keep
things simple and the result is printed out using the print-formatted function printf,
from the the standard library, with a "%d" to indicate that it is printing a integer.
int a,b;
{ int c;
c = a + b;
printf ("%d",c);
Notice the position of the function name and where braces and semi-colons are
placed: they are crucial. The details are quickly learned with practice and experience.
This function is not much use standing alone. It has to be called from somewhere. A
function is called (i.e. control is passed to the function) by using its name with the
usual brackets () to follow it, along with the values which are to be passed to the
function:
main ()
{ int c,d;
c = 1;
d = 53;
Add_Two_Numbers (c,d);
Add_Two_Numbers (1,2);
The result of this program would be to print out the number 54 and then the number 3
and then stop. Here is a simple program which makes use of some functions in a
playful way. The structure diagram shows how this can be visualized and the
significance of the program `levels'. The idea is to illustrate the way in which the
functions connect together:
• Structure diagram:
• Program listing:
• Functions with values:
• Breaking out early:
• The exit function:
• Functions and types:
• Questions 6:
Structure diagram
Level 0: main ()
Level 1: DownOne ()
/ \
/ \
Level 2: DownLeft() DownRight()
Note: not all functions fit into a tidy hierarchy like these. Some functions call
themselves, while others can be called from anywhere in a program. Where would
you place the printf function in this hierarchy?
Program Listing
/***********************************************/
/* */
/* Function Snakes & Ladders */
/* */
/***********************************************/
#include <stdio.h>
/***********************************************/
/* Level 0 */
/***********************************************/
main ()
{
printf ("This is level 0: the main program\n");
printf ("About to go down a level \n");
DownOne ();
/************************************************/
/* Level 1 */
/************************************************/
{
printf ("Down here at level 1, all is well\n");
DownLeft (2);
printf ("Through level 1....\n");
DownRight (2);
/************************************************/
/* Level 2 */
/************************************************/
int a;
{
printf ("This is deepest level %d\n",a);
printf ("On the left branch of the picture\n");
printf ("Going up!!");
}
/************************************************/
int a;
{
printf ("And level %d again!\n",a);
}
bill = CalculateBill(data...);
The variable bill is assigned to a function CalculateBill() and data are some
data which are passed to the function. This statement makes it look as though
CalculateBill() is a number. When this statement is executed in a program, control
will be passed to the function CalculateBill() and, when it is done, this function
will then hand control back. The value of the function is assigned to "bill" and the
program continues. Functions which work in this way are said to return a value.
int starter,main,dessert;
{ int total;
int starter,main,dessert;
{ int total;
then the value bill would just be garbage (no predictable value), presuming that the
compiler allowed this to be written at all. On the other hand if the first version were
used (the one which did use the return(total) statement) and furthermore no
assignment were made:
main ()
{
CalculateBill (1,2,3);
}
then the value of the function would just be discarded, quite legitimately. This is
usually what is done with the input output functions printf() and scanf() which
actually return values. So a function in C can return a value but it does not have to be
used; on the other hand, a value which has not been returned cannot be used safely.
NOTE : Functions do not have to return integers: you can decide whether they should
return a different data type, or even no value at all. (See next chapter)
int a,b;
{
while (a < b)
{
if (a > b)
{
return (b);
}
a = a + 1;
}
}
The example shows this. The function is entered with some values for a and b and,
assuming that a is less than b, it starts to execute one of C's loops called while. In that
loop, is a single if statement and a statement which increases a by one on each loop.
If a becomes bigger than b at any point the return(b) statement gets executed and
the function myfunction quits, without having to arrive at the end brace }, and passes
the value of b back to the place it was called.
#define CODE 0
exit (CODE);
This function also calls a number of other functions which perform tidy-up duties
such as closing open files etc.
can only make sense if the variable bill and the value of the function
CalculateBill() are the same kind of object: in other words if CalculatBill()
returns a floating point number, then bill cannot be a character! Both sides of an
assignment must match.
In fact this is done by declaring functions to return a particular type of data. So far no
declarations have been needed because C assumes that all values are integers unless
you specifically choose something different. Declarations are covered in the next
section.
Questions
1. Write a function which takes two values a and b and returns the value of
(a*b).
2. Is there anything wrong with a function which returns no value?
3. What happens if a function returns a value but it is not assigned to anything?
4. What happens if a function is assigned to an object but that function returns no
value?
5. How can a function be made to quit early?
A variable is a seqeuence of program code with a name (also called its identifier). A
name or identifier in C can be anything from a single letter to a word. The name of a
variable must begin with an alphabetic letter or the underscore _ character but the
other characters in the name can be chosen from the following groups:
a .. z
(any letter from a to z)
A .. Z
(any letter from A to Z)
0 .. 9
(any digit from 0 to 9)
_
(the underscore character)
In C variables do not only have names: they also have types. The type of a variable
conveys to the the compiler what sort of data will be stored in it. In BASIC and in
some older, largely obsolete languages, like PL/1, a special naming convention is
used to determine the sort of data which can be held in particular variables. e.g. the
dollar symbol $ is commonly used in BASIC to mean that a variable is a string and
the percentage % symbol is used to indicate an integer. No such convention exists in
C. Instead we specify the types of variables in their declarations. This serves two
purposes:
• It gives a compiler precise information about the amount of memory that will
have to be given over to a variable when a program is finally run and what sort
of arithmetic will have to be used on it (e.g. integer only or floating point or
none).
• It provides the compiler with a list of the variables in a convenient place so
that it can cross check names and types for any errors.
There is a lot of different possible types in C. In fact it is possible for us to define our
own, but there is no need to do this right away: there are some basic types which are
provided by C ready for use. The names of these types are all reserved words in C and
they are summarized as follows:
char
A single ASCII character
short
A short integer (usually 16-bits)
short int
A short integer
int
A standard integer (usually 32-bits)
long
A long integer
long int
A long integer (usually 32-bits, but increasingly 64 bits)
float
A floating point or real number (short)
long float
a long floating point number
double
A long floating point number
void
Discussed in a later chapter.
enum
Discussed in a later chapter.
volatile
Discussed in a later chapter.
There is some repetition in these words. In addition to the above, the word unsigned
can also be placed in front of any of these types. Unsigned means that only positive or
zero values can be used. (i.e. there is no minus sign). The advantage of using this kind
of variable is that storing a minus sign takes up some memory, so that if no minus
sign is present, larger numbers can be stored in the same kind of variable. The ANSI
standard also allows the word signed to be placed in front of any of these types, so
indicate the opposite of unsigned. On some systems variables are signed by default,
whereas on others they are not.
• Declarations:
• Where to declare things:
• Declarations and Initialization:
• Types:
• Choosing Variables:
• Assigning variables to one another:
• Types and The Cast Operator:
• Storage class register static and extern:
• Functions types:
• Questionsdeclare:
Declarations
To declare a variable in a C program one writes the type followed by a list of variable
names which are to be treated as being that type:
typename variablename1,..,..,variablenameN;
For example:
int i,j;
char ch;
double x,y,z,fred;
unsigned long int Name_of_Variable;
Failing to declare a variable is more risky than passing through customs and failing to
declare your six tonnes of Swiss chocolate. A compiler is markedly more efficient
than a customs officer: it will catch a missing declaration every time and will
terminate a compiling session whilst complaining bitterly, often with a host of
messages, one for each use of the undeclared variable.
1. One place is outside all of the functions. That is, in the space between function
definitions. (After the #include lines, for example.) Variables declared here
are called global variables. There are also called static and external variables
in special cases.)
2. #include <stdio.h>
3.
4. int globalinteger; /* Here! outside {} */
5.
6. float global_floating_point;
7.
8. main ()
9.
10. {
11. }
12.
13. The other place where declarations can be made is following the opening
brace, {}, of a block. Any block will do, as long as the declaration follows
immediately after the opening brace. Variables of this kind only work inside
their braces {} and are often called local variables. Another name for them is
automatic variables.
14. main ()
15.
16. { int a;
17. float x,y,z;
18.
19. /* statements */
20.
21. }
22.
or
function ()
{ int i;
/* .... */
/* ... */
}
}
int i = 0;
char ch = 'a';
int i;
char ch;
i = 0;
ch = 'a';
This is called initialization of the variables. C always allows the programmer to write
declarations/initializers in this way, but it is not always desirable to do so. If there are
just one or two declarations then this initialization method can make a program neat
and tidy. If there are many, then it is better to initialize separately, as in the second
case. A lot means when it starts to look as though there are too many. It makes no
odds to the compiler, nor (ideally) to the final code whether the first or second method
is used. It is only for tidiness that this is allowed.
Individual Types
• char:
• Example special chars:
• integers:
• Float:
char
A character type is a variable which can store a single ASCII character. Groups of
char form strings. In C single characters are written enclosed by single quotes, e.g.
'c'! (This is in contrast to strings of many characters which use double quotes, e.g.
"string") For instance, if ch is the name of a character:
char ch;
ch = 'a';
would give ch the value of the character a. The same effect can also be achieved by
writing:
char ch = 'a';
A character can be any ASCII character, printable or not printable from values -128 to
127. (But only 0 to 127 are used.) Control characters i.e. non printable characters are
put into programs by using a backslash \ and a special character or number. The
characters and their meanings are:
\b
backspace BS
\f
form feed FF (also clear screen)
\n
new line NL (like pressing return)
\r
carriage return CR (cursor to start of line)
\t
horizontal tab HT
\v
vertical tab (not all versions)
\"
double quotes (not all versions)
\'
single quote character '
\\
backslash character \
\ddd
character ddd where ddd is an ASCII code given in octal or base 8, See
Character Conversion Table.
\xddd
character ddd where ddd is an ASCII code given in hexadecimal or base 16,
See Character Conversion Table.
Node:Example special chars, Next:integers, Previous:char, Up:Types
Listing
/***************************************************/
/* */
/* Special Characters */
/* */
/***************************************************/
#include <stdio.h>
main ()
{
printf ("Beep! \7 \n");
printf ("ch = \'a\' \n");
printf (" <- Start of this line!! \r");
}
and the text cursor is left where the arrow points. It is also possible to have the type:
unsigned char
This admits ASCII values from 0 to 255, rather than -128 to 127.
Integers
Whole numbers
There are five integer types in C and they are called char, int, long, long long and
short. The difference between these is the size of the integer which either can hold
and the amount of storage required for them. The sizes of these objects depend on the
operating system of the computer. Even different flavours of Unix can have varying
sizes for these objects. Usually, the two to remember are int and short. int means a
`normal' integer and short means a `short' one, not that that tells us much. On a
typical 32 bit microcomputer the size of these integers is the following:
int i,j;
i = j = 0;
or
short i=0,j=0;
Floating Point
There are also long and short floating point numbers in C. All the mathematical
functions which C can use require double or long float arguments so it is common
to use the type float for storage only of small floating point numbers and to use double
elsewhere. (This not always true since the C `cast' operator allows temporary
conversions to be made.) On a typical 32 bit implementation the different types would
be organized as follows:
Typical declarations:
float x,y,z;
x = 0.1;
y = 2.456E5
z = 0;
double bignum,smallnum;
bignum = 2.36E208;
smallnum = 3.2E-300;
Choosing Variables
The sort of procedure that you would adopt when choosing variable names is
something like the following:
int i,j,k;
to be integer type variables used for counting. (There is not particular reason why this
should be; it is just common practice.) Other integer values should have more
meaningful names. Similarly names like:
double x,y,z;
var = 10;
var1 = var2;
In either case the objects on either side of the = symbol must be of the same type. It is
possible (though not usually sensible) to assign a floating point number to a character
for instance. So
int a, b = 1;
a = b;
float x = 1.4;
char ch;
ch = x;
is a valid statement, since the truncated value 1 can be assigned to ch. This is a
questionable practice though. It is unclear why anyone would choose to do this.
Numerical values and characters will interconvert because characters are stored by
their ASCII codes (which are integers!) Thus the following will work:
int i;
char ch = 'A';
i = ch;
int i;
char ch = '\n';
i = (int) ch;
The value of the integer would be the ASCII code of the character. This is the only
integer which it would make any sense to talk about in connection with the character.
Similarly floating point and integer types can be interconverted:
float x = 3.3;
int i;
i = (int) x;
The value of i would be 3 because an integer cannot represent decimal points, so the
cast operator rounds the number. There is no such problem the other way around.
float x;
int i = 12;
x = (float) i;
The general form of the cast operator is therefore:
(type) variable
It does not always make sense to convert types. This will be seen particularly with
regard to structures and unions. Cast operators crop up in many areas of C. This is not
the last time they will have to be explained.
/***************************************************/
/* */
/* Demo of Cast operator */
/* */
/***************************************************/
#include <stdio.h>
{ float x;
int i;
char ch;
x = 2.345;
i = (int) x;
ch = (char) x;
printf ("From float x =%f i =%d ch =%c\n",x,i,ch);
i = 45;
x = (float) i;
ch = (char) i;
printf ("From int i=%d x=%f ch=%c\n",i,x,ch);
ch = '*';
i = (int) ch;
x = (float) ch;
printf ("From char ch=%c i=%d x=%f\n",ch,i,x);
}
File 1 File 2
int i;
main () function ()
{ {
extern int i;
} }
In this example, the function main() in file 1 can use the variable i from the function
main in file 2.
Another class is called static. The name static is given to variables which can
hold their values between calls of a function: they are allocated once and once only
and their values are preserved between any number of function calls. Space is
allocated for static variables in the program code itself and it is never disposed of
unless the whole program is. NOTE: Every global variable, defined outside functions
has the type static automatically. The opposite of static is auto.
If you want this to be different, then a function has to be declared to be a certain type,
just as variables have to be. There are two places where this must be done:
• The name of the function must be declared a certain type where the function is
declared. e.g. a function which returns a float value must be declared as:
• float function1 ()
•
• {
• return (1.229);
• }
•
char function2 ()
{
return ('*');
}
If a function whose type is not integer is not declared like this, then
compilation errors will result! Notice also that the function must be declared
inside every function which calls it, not just main().
Questions
1. What is an identifier?
2. Say which of the following are valid C identifiers:
1. Ralph23
2. 80shillings
3. mission_control
4. A%
5. A$
6. _off
3. Write a statement to declare two integers called i and j.
4. What is the difference between the types floa and double.
5. What is the difference between the types int and unsigned int?
6. Write a statement which assigns the value 67 to the integer variable "I".
7. What type does a C function return by default?
8. If we want to declare a function to return long float, it must be done in, at
least, two places. Where are these?
9. Write a statement, using the cast operator, to print out the integer part of the
number 23.1256.
10. Is it possible to have an automatic global variable?
Not all functions will be as simple as the ones which have been given so far.
Functions are most useful if they can be given information to work with and if they
can reach variables and data which are defined outside of them. Examples of this have
already been seen in a limited way. For instance the function CalculateBill
accepted three values a,b and c.
CalculateBill (a,b,c)
int a,b,c;
{ int total;
total = a + b + c;
return total;
}
When variable values are handed to a function, by writing them inside a functions
brackets like this, the function is said to accept parameters. In mathematics a
parameter is a variable which controls the behaviour of something. In C it is a variable
which carries some special information. In CalculateBill the "behaviour" is the
addition process. In other words, the value of total depends upon the starting values
of a,b and c.
• Declaring parameters:
• Value parameters:
• Functions as actual parameters:
• Example 2:
• Example 3:
• Variable parameters:
• Example 4:
• Qulakfj:
Declaring Parameters
A function was defined by code which looks like this:
identifier (parameters...)
types of parameters
Parameters, like variables and functions, also have types which must be declared. For
instance:
function1 (i,j,x,y)
int i,j;
float x,y;
}
or
double x;
char ch;
return (ch2);
}
Value Parameters
A value parameter is the most common kind of parameter. All of the examples up to
know have been examples of value parameters. When a value parameter is passes
information to a function its value is copied to a new place which is completely
isolated from the place that the information came from. An example helps to show
this. Consider a function which is called from main() whose purpose is to add
together two numbers and to print out the result.
#include <stdio.h>
main ()
{
add (1,4);
}
/*******************************************/
add (a,b)
int a,b;
{
printf ("%d", a+b);
}
When this program is run, two new variables are automatically created by the
language, called a and b. The value 1 is copied into a and the value 4 is copied into b.
Obviously if a and b were given new values in the function add() then this could not
change the values 1 and 4 in main(), because 1 is always 1 and 4 is always 4. They
main ()
{ int a = 1, b = 4;
add (a,b);
}
/**************************************/
add (a,b)
int a,b;
{
printf ("%d", a+b);
}
then it is less clear what will happen. In fact exactly the same thing happens:
• When add() is called from main() two new variables a and b are created by
the language (which have nothing to do with the variables a and b in main()
and are completely isolated from them).
• The value of a in main() is copied into the value of a in add().
• The value of b in main() is copied into the value of b in add().
Now, any reference to a and b within the function add() refers only to the two
parameters of add and not to the variables with the same names which appeared in
main(). This means that if a and b are altered in add() they will not affect a and b in
main(). More advanced computing texts have names for the old and they new a and
b:
Actual Parameters
These are the original values which were handed over to a function. Another
name for this is an argument.
Formal Parameters
These are the copies which work inside the function which was called.
• The names of formal parameters can be anything at all. They do not have to be
the same as the actual parameters. So in the example above it would be
equally valid to write:
• #include <stdio.h>
•
• main ()
•
• { int a = 1, b = 4;
•
• add (a,b);
• }
•
• /*******************************************/
•
• add (i,j)
•
• int i,j;
•
• {
• printf ("%d", i+j);
• }
•
• The parameters ought to, but need not match in number! This surprising fact is
important because programs can go wrong if a formal parameter was missed
out. ANSI C has a way of checking this by function `prototyping', but in
Kernighan & Ritchie C there is no way to check this. If the number of actual
parameters is more than the number of formal parameters and all of the
parameters match in type then the extra values are just discarded. If the
number of actual parameters is less than the number of formal parameters,
then the compiler will assign some unknown value to the formal parameters.
This will probably be garbage.
• Our use of variables as parameters should not leave you with the impression
that we can only use variables as parameters. In fact, we can send any literal
value, or expression with an appropriate type to a function. For example,
• sin(3.41415);
• cos(a+b*2.0);
• strlen("The length of this string");
Node:Functions as actual parameters, Next:Example 2, Previous:Value parameters,
Up:Parameters
main ()
{
PrintOut (SomeValue());
}
/*********************************************/
int a;
{
printf ("%d",a);
}
/**********************************************/
{
return (42);
}
Example Listing
/**************************************************/
/* */
/* Value Parameters */
/* */
/**************************************************/
#include <stdio.h>
/**************************************************/
/* Level 0 */
/**************************************************/
i = 0;
x = 0;
j = resultof (i);
/***************************************************/
/* level 1 */
/***************************************************/
double x;
{
x = x + 1;
return (x);
}
/****************************************************/
int j;
{
return (2*j + 3); /* why not... */
}
Example Listing
/******************************************************/
/* */
/* Program : More Value Parameters */
/* */
/******************************************************/
#include <stdio.h>
/******************************************************/
{ int pupil1,pupil2,pupil3;
int ppr1,ppr2,ppr3;
float pen1,pen2,pen3;
pupil1 = 87;
pupil2 = 45;
pupil3 = 12;
ppr1 = 200;
ppr2 = 230;
ppr3 = 10;
pen1 = 1;
pen2 = 2;
pen3 = 20;
analyse (pupil1,pupil2,pupil3,ppr1,ppr2,
ppr3,pen1,pen2,pen3);
/*******************************************************/
analyse (p1,p2,p3,w1,w2,w3,b1,b2,b3)
int p1,p2,p3,w1,w2,w3;
float b1,b2,b3;
{
printf ("Pupil 1 scored %d percent\n",p1);
printf ("Pupil 2 scored %d percent\n",p2);
printf ("Pupil 3 scored %d percent\n",p3);
/*****************************************************/
int a,b,c;
{
return (a + b + c);
}
One way to hand information back is to use the return statement. This function is
slightly limited however in that it can only hand the value of one variable back at a
time. There is another way of handing back values which is less restrictive, but more
awkward than this. This is by using a special kind of parameter, often called a
#include <stdio.h>
main ()
{ int i,j;
GetValues (&i,&j);
printf ("i = %d and j = %d",i,j)
}
/************************************/
GetValues (p,q)
int *p,*q;
{
*p = 10;
*q = 20;
}
There are two new things to notice about this program: the symbols & and *. The
ampersand & symbol should be read as "the address of..". The star * symbol should be
read as "the contents of the address...". This is easily confused with the multiplication
symbol (which is identical). The difference is only in the context in which the symbol
is used. Fortunately this is not ambiguous since multiplication always takes place
between two numbers or variables, whereas the "contents of a pointer" applies only to
a single variable and the star precedes the variable name.
So, in the program above, it is not the variables themselves which are being passed to
the procedure but the addresses of the the variables. In other words, information about
where the variables are stored in the memory is passed to the function GetValues().
These addresses are copied into two new variables p and q, which are said to be
pointers to i and j. So, with variable parameters, the function does not receive a copy
of the variables themselves, but information about how to get at the original variable
which was passed. This information can be used to alter the "actual parameters"
directly and this is done with the * operator.
*p = 10;
means: Make the contents of the address held in p equal to 10. Recall that the address
held in p is the address of the variable i, so this actually reads: make i equal to 10.
Similarly:
*q = 20;
means make the contents of the address held in q equal to 20. Other operations are
also possible (and these are detailed in the section on pointers) such as finding out the
value of i and putting it into a new variable, say, a:
int a;
a = *p; /* is equivalent to a = i */
Example Listing
/**************************************************/
/* */
/* Program : Variable Parameters */
/* */
/**************************************************/
#include <stdio.h>
/**************************************************/
{ int height,width;
height = 4;
width = 5;
ScaleDimensions (&height,&width);
/****************************************************/
*h = *h * hscale;
*w = *w * wscale;
}
Questions
1. Name two ways that values and results can be handed back from a function.
2. Where are parameters declared?
3. Can a function be used directly as a value parameter?
4. Does it mean anything to use a function directly as a variable parameter?
5. What do the symbols * and & mean, when they are placed in front of an
identifier?
6. Do actual and formal parameters need to have the same names?
From the computer's point of view, a C program is nothing more than a collection of
functions and declarations. Functions can be thought of as sealed capsules of program
code which float on a background of white space, and are connected together by
means of function calls. White space is the name given to the white of an imaginary
piece of paper upon which a program is written, in other words the spaces and new
line characters which are invisible to the eye. The global white space is only the gaps
between functions, not the gaps inside functions. Thinking of functions as sealed
capsules is a useful way of understanding the difference between local and global
objects and the whole idea of scope in a program.
• Global variables:
• Local variables:
• Parameters again:
• Example 5:
• Style note:
• Scope and style:
• Questions 11:
Global Variables
Global variables are declared in the white space between functions. If every function
is a ship floating in this sea of white space, then global variables (data storage areas
which also float in this sea) can enter any ship and also enter anything inside any ship
(See the diagram). Global variables are available everywhere;. they are created when
a program is started and are not destroyed until a program is stopped. They can be
used anywhere in a program: there is no restriction about where they can be used, in
principle.
Local Variables
Local variables are more interesting. They can not enter just any region of the
program because they are trapped inside blocks. To use the ship analogy: if it is
imagined that on board every ship (which means inside every function) there is a large
swimming pool with many toy ships floating inside, then local variables will work
anywhere in the swimming pool (inside any of the toys ships, but can not get out of
the large ship into the wide beyond. The swimming pool is just like a smaller sea, but
one which is restricted to being inside a particular function. Every function has its
own swimming pool! The idea can be taken further too. What about swimming pools
onboard the toy ships? (Meaning functions or blocks inside the functions!
function ()
{
/* On board ship */
{
/* On board a toy ship */
}
}
The same rules apply for the toy ships. Variables can reach anywhere inside them but
they cannot get out. They cannot escape their block braces {}. Whenever a pair of
block braces is written into a program it is possible to make variable declarations
inside the opening brace. Like this:
{ int locali;
char localch;
/* statements */
}
These variables do not exist outside the braces. They are only created when the
opening brace is encountered and they are destroyed when the closing brace is
executed, or when control jumps out of the block. Because they only work in this
local area of a program, they are called local variables. It is a matter of style and
efficiency to use local variables when it does not matter whether variables are
preserved outside of a particular block, because the system automatically allocates
and disposes of them. The programmer does not have to think about this.
Where a variable is and is not defined is called the scope of that variable. It tells a
programmer what a variables horizons are!
Communication : parameters
If functions were sealed capsules and no local variables could ever communicate with
other parts of the program, then functions would not be very useful. This is why
parameters are allowed. Parameters are a way of handing local variables to other
functions without letting them out! Value parameters (see last section) make copies of
local variables without actually using them. The copied parameter is then a local
variable in another function. In other words, it can't get out of the function to which is
it passed ... unless it is passed on as another parameter.
Example Listing
Notice about the example that if there are two variables of the same name, which are
both allowed to be in the same place (c in the example below) then the more local one
wins. That is, the last variable to be defined takes priority. (Technically adept readers
will realize that this is because it was the last one onto the variable stack.)
/***************************************************************/
/* */
/* SCOPE : THE CLLLED CAPSULES */
/* */
/***************************************************************/
#include <stdio.h>
/***************************************************************/
main ()
{ int a = 1, b = 2, c = 3;
if (a == 1)
{ int c;
c = a + b;
printf ("%d",c);
}
handdown (a,b);
printf ("%d",c);
}
/**************************************************************/
int a,b;
{
...
}
Style Note
Some programmers complain about the use of global variables in a program. One
complaint is that it is difficult to see what information is being passed to a function
unless all that information is passed as parameters. Sometimes global variables are
very useful however, and this problem need not be crippling. A way to make this clear
is to write global variables in capital letters only, while writing the rest of the
variables in mainly small letters..
int GLOBALINTEGER;
....
This allows global variables to be spotted easily. Another reason for restricting the use
of global variables is that it is easier to debug a program if only local variables are
used. The reason is that once a function capsule is tested and sealed it can be
guaranteed to work in all cases, provided it is not affected by any other functions from
outside. Global variables punch holes in the sealed function capsules because they
allow bugs from other functions to creep into tried and tested ones. An alert and
careful programmer can usually control this without difficulty.
The following guidelines may help the reader to decide whether to use local or global
data:
• Always think of using a local variable first. Is it impractical? Yes, if it means
passing dozens of parameters to functions, or reproducing a lot of variables.
Global variables will sometimes tidy up a program.
• Local variables make the flow of data in a program clearer and they reduce the
amount of memory used by the program when they are not in use.
• The preference in this book is to use local variables for all work, except where
a program centres around a single data structure. If a data structure is the main
reason for a program's existence, it is nearly always defined globally.
/**************************************/
Another good reason is that any function hands parameters down by only one level at
a time and that any return() statement hands values up a single level. The global
variables are kept to a single place at the head of each program so that they can be
seen to reach into everything.
The diagram shows how the splitting of levels implies something about the scope of
variables and the handing of parameters.
Questions
1. What is a global variable?
2. What is a local variable?
3. What is meant by calling a block (enclosed by braces {} ) a "sealed capsule"?
4. Do parameters make functions leaky? i.e. Do they spoil them by letting the
variables leak out into other functions?
5. Write a program which declares 4 variables. Two integer variables called
number_of_hats,counter which are GLOBAL and two float variables
called x_coord,y_coord which are LOCAL inside the function main(). Then
add another function called another() and pass x_coord,y_coord to this
function. How many different storage spaces are used when this program
runs? (Hint: are x_coord,y_coord and their copies the same?)
Preprocessor Commands
Making programming versatile.
C is unusual in that it has a pre-processor. This comes from its Unix origins. As its
name might suggest, the preprocessor is a phase which occurs prior to compilation of
a program. The preprocessor has two main uses: it allows external files, such as
header files, to be included and it allows macros to be defined. This useful feature
traditionally allowed constant values to be defined in Kernighan and Ritchie C, which
had no constants in the language.
#include <stdio.h>
is a command which tells the preprocessor to treat the file stdio.h as if it were the
actually part of the program text, in other words to include it as part of the program to
be compiled.
Macros are words which can be defined to stand in place of something complicated:
they are a way of reducing the amount of typing in a program and a way of making
long ungainly pieces of code into short words. For example, the simplest use of
macros is to give constant values meaningful names: e.g.
This allows us to use the word TELEPHNUM in the program to mean the number
720663. In this particular case, the word is clearly not any shorter than the number it
will replace, but it is more meaningful and would make a program read more naturally
than if the raw number were used. For instance, a program which deals with several
different fixed numbers like a telephone number, a postcode and a street number
could write:
printf("%d %d %d",TELEPHNUM,postcode,streetnum);
instead of
printf("%d %d %d",720663,345,14);
Using the macros instead makes the actions much clearer and allows the programmer
to forget about what the numbers actually are. It also means that a program is easy to
alter because to change a telephone number, or whatever, it is only necessary to
change the definition, not to retype the number in every single instance.
The important feature of macros is that they are not merely numerical constants which
are referenced at compile time, but are strings which are physically replaced before
compilation by the preprocessor! This means that almost anything can be defined:
#define SUM 1 + 2 + 3 + 4
would allow a commonly used string to be called by the identifier "string" instead of
typing it out afresh each time. The idea of a define statement then is:
Macros cannot define more than a single line to be substituted into a program but they
can be used anywhere, except inside strings. (Anything enclosed in string quotes is
assumed to be complete and untouchable by the compiler.) Some macros are defined
already in the file stdio.h such as:
EOF
The end of file character (= -1 for instance)
NULL
The null character (zero) = 0
• Macro functions:
• Macros with parameters:
• Example 6:
• Note about include:
• Other Preprocessor commands:
• Example 7:
• Questions 12:
Macro Functions
A more advanced use of macros is also permitted by the preprocessor. This involves
macros which accept parameters and hand back values. This works by defining a
macro with some dummy parameter, say x. For example: a macro which is usually
defined in one of the standard libraries is abs() which means the absolute or unsigned
value of a number. It is defined below:
The result of this is to give the positive (or unsigned) part of any number or variable.
This would be no problem for a function which could accept parameters, and it is, in
fact, no problem for macros. Macros can also be made to take parameters. Consider
the ABS() example. If a programmer were to write ABS(4) then the preprocessor
would substitute 4 for x. If a program read ABS(i) then the preprocessor would
substitute i for x and so on. (There is no reason why macros can't take more than one
parameter too. The programmer just includes two dummy parameters with different
names. See the example listing below.) Notice that this definition uses a curious
operator which belongs to C:
(Do not be confused by the above statement which is meant to show what a
programmer might think. It is not a valid C statement.) C can usually produce much
more efficient code for this construction than for a corresponding if-else statement.
ABS(function())
has no meaning. Only variables or number constants will be substituted. Macros are
also severely restricted in complexity by the limitations of the preprocessor. It is
simply not viable to copy complicated sequences of code all over programs.
Example Listing
/************************************************************/
/* */
/* MACRO DEMONSTRATION */
/* */
/************************************************************/
#include <stdio.h>
/************************************************************/
{
printf (STRING1);
printf (STRING2);
printf ("%d\n",EXPRESSION);
printf ("%d\n",EXPR2);
printf ("%d\n",ABS(-5));
printf ("Biggest of 1 2 and 3 is %d",BIGGEST(1,2,3));
}
There are a handful more preprocessor commands which can largely be ignored by
the beginner. They are commonly used in "include" files to make sure that things are
not defined twice.
#undef
This undefines a macro, leaving the name free.
#if
This is followed by some expression on the same line. It allows conditional
compilation. It is an advanced feature which can be used to say: only compile
the code between #if and #endif if the value following #if is true, else leave
out that code altogether. This is different from not executing code--the code
will not even be compiled.
#ifdef
This is followed by a macro name. If that macro is defined then this is true.
#ifndef
This is followed by a macro name. If that name is not defined then this is true.
#else
This is part of an #if, #ifdef, #ifndef preprocessor statement.
#endif
This marks the end of a preprocessor statement.
#line
Has the form:
#line constant filename
This is for debugging mainly. This statement causes the compiler to believe
that the next line is line number (constant) and is part of the file (filename).
#error
This is a part of the proposed ANSI standard. It is intended for debugging. It
forces the compiler to abort compilation.
Node:Example 7, Next:Questions 12, Previous:Other Preprocessor commands,
Up:Preprocessor
Example
/***********************************************************/
/* To compile or not to compile */
/***********************************************************/
/***********************************************************/
#if (CHOICE == 1)
#else
#define OPTIONSTRING "The alternative"
#define DITTO "i.e. This! "
#endif
/***********************************************************/
#ifdef SOMEDEFINITION
#else
#endif
/************************************************************/
main ()
{
printf (OPTIONSTRING);
printf (DITTO);
}
Questions
1. Define a macro called "birthday" which describes the day of the month upon
which your birthday falls.
2. Write an instruction to the preprocessor to include to maths library math.h.
3. A macro is always a number. True or false?
4. A macro is always a constant. True or false?
Pointers
You have a map (a plan) of the computer's memory. You need to find that essential
piece of information which is stored at some unknown location. How will you find it?
You need a pointer!
A pointers is a special type of variable which holds the address or location of another
variable. Pointers point to these locations by keeping a record of the spot at which
they were stored. Pointers to variables are found by recording the address at which a
variable is stored. It is always possible to find the address of a piece of storage in C
using the special & operator. For instance: if location were a float type variable, it
would be easy to find a pointer to it called location_ptr.
float location;
float *location_ptr,*address;
location_ptr = &(location);
or
address = &(location);
The declarations of pointers look a little strange at first. The star * symbol which
stands in front of the variable name is C's way of declaring that variable to be a
pointer. The four lines above make two identical pointers to a floating point variable
called location, one of them is called location_ptr and the other is called address.
The point is that a pointer is just a place to keep a record of the address of a variable,
so they are really the same thing.
A pointer is a bundle of information that has two parts. One part is the address of the
beginning of the segment of memory that holds whatever is pointed to. The other part
is the type of value that the pointer points to the beginning of. This tells the computer
how much of the memory after the beginning to read and how to interpret it. Thus, if
the pointer is of a type int, the segment of memory returned will be four bytes long
(32 bits) and be interpreted as an integer. In the case of a function, the type is the type
of value that the function will return, although the address is the address of the
beginning of the function executable.
If, like some modern day programmers, you believe in sanctity of high level
languages, it is probably a source of wonder why anyone Would ever want to know
the address of these variables. Having gone to the trouble to design a high level
language, like C, in which variables can be given elegant and meaningful names: it
seems like a step in the backward direction to want to be able to find out the exact
number of the memory location at which it is stored! The whole point of variables,
after all, is that it is not necessary to know exactly where information is really stored.
This is not quite fair though. It is certainly rare indeed when we should want to know
the actual number of the memory location at which something is stored. That would
really make the idea of a high level language a bit pointless. The idea behind pointers
is that a high level programmer can now find out the exact location of a variable
without ever having to know the actual number involved. Remember:
A pointer is a variable which holds the address of the storage location for another
given variable.
C provides two operators & and * which allow pointers to be used in many versatile
ways.
• Pointer operators:
• Uses for pointers:
• Pointers and Initialization:
• Example 8:
• Types Casts and Pointers:
• Function pointers:
• Calling functions by pointer:
• Questions 13:
& and *
The & and * operators have already been used once to hand back values to variable
parameters, See Value parameters. They can be read in a program to have the
following meanings:
&
The address of...
*
The contents of the address held in...
*
The contents of the location pointed to by...
This reinforces the idea that pointers reach out an imaginary hand and point to some
location in the memory and it is more usual to speak of pointers in this way. The two
operators * and & are always written in front of a variable, clinging on, so that they
refer, without doubt, to that one variable. For instance:
&x
The address at which the variable x is stored.
*ptr
The contents of the variable which is pointed to by ptr.
The following example might help to clarify the way in which they are used:
int somevar; /* 1 */
int *ptr_to_somevar; /* 2 */
somevar = 42; /* 3 */
ptr_to_somevar = &(somevar); /* 4 */
printf ("%d",*ptr_to_somevar); /* 5 */
*ptr_to_somevar = 56; /* 6 */
int i,*ip;
char ch,*chp;
short s,*sp;
float x,*xp;
double y,*yp;
Pointers are extremely important objects in C. They are far more important in C than
in, say, Pascal or BASIC (PEEK,POKE are like pointers). In particular they are vital
when using data structures like strings or arrays or linked lists. We shall meet these
objects in later chapters.
One example of the use of pointers is the C input function, which is called scanf(). It
is looked at in detail in the next section. scanf() is for getting information from the
keyboard. It is a bit like the reverse of printf(), except that it uses pointers to
variables, not variables themselves. For example: to read an integer:
int i;
scanf ("%d",&i);
or
int *i;
scanf ("%d",i);
The & sign or the * sign is vital. If it is forgotten, scanf will probably corrupt a
program. This is one reason why this important function has been ignored up to now.
Assembly language programmers might argue that there are occasions on which it
would be nice to know the actual address of a variable as a number. One reason why
one might want to know this would be for debugging. It is not often a useful thing to
do, but it is not inconceivable that in developing some program a programmer would
want to know the actual address. The & operator is flexible enough to allow this to be
found. It could be printed out as an integer:
type *ptr:
Think about what happens when the following statement is written. This statement is
really talking about two different storage places in the memory:
int *a = 2;
First of all, what is declared is a pointer, so space for a `pointer to int' is allocated by
the program and to start off with that space will contain garbage (random numbers),
because no statement like
a = &someint;
has yet been encountered which would give it a value. It will then attempt to fill the
contents of some variable, pointed to by a, with the value 2. This is doomed to faliure.
a only contains garbage so the 2 could be stored anywhere. There may not even be a
variable at the place in the memory which a points to. Nothing has been said about
that yet. This kind of initialization cannot possibly work and will most likely crash the
program or corrupt some other data.
#include <stdio.h>
main ()
c = a; /* swap pointers */
a = b;
b = c;
(type) variable
For pointers it is:
(type *) pointer
char *ch;
int *i;
i = (int *) ch;
This copies the value of the pointer ch to the pointer i. The cast operator makes sure
that the pointers are in step and not talking at cross purposes. The reason that pointers
have to be `cast' into shape is a bit subtle and depends upon particular computers. In
practice it may not actually do anything, but it is a necessary part of the syntax of C.
Pointer casting is discussed in greater detail in the chapter on Structures and Unions.
Pointers to functions
This section is somewhat outside of the main development of the book. You might
want to omit it on first reading.
Pointers to functions enable you to tell any function which sub-ordinate function it
should use to do its job. That means that you can plug in a new function in place of an
old one just by passing a different parameter value to the function. You do not have to
rewrite any code. In machine code circles this is sometimes called indirection or
vectoring.
When we come to look at arrays, we'll find that a pointer to the start of an array can
be found by using the name of the array itself without the square brackets []. For
functions, the name of the function without the round brackets works as a pointer to
the start of the function, as long as the compiler understands that the name represents
the function and not a variable with the same name. So--to pass a function as a
parameter to another function you would write
function1(function2);
If you try this as it stands, a stream of compilation errors will be the result. The reason
is that you must declare function2() explicitly like this:
int function2();
If the function returns a different type then clearly the declaration will be different but
the form will be the same. The declaration can be placed together with other
declarations. It is not important whether the variable is declared locally or globally,
since a function is a global object regardless. What is important is that we declare
specifically a pointer to a function which returns a type (even if it is void). The
function which accepts a function pointer as an argument looks like this:
function1 (a)
int (*a)();
{ int i;
i = (*a)(parameters);
}
typename (*fnptr)();
variable = (*fnptr)(parameters);
An example let us look at a function which takes an integer and returns a character.
int i;
char ch, function();
ch = function(i);
but we can also do the same thing with a pointer to the function. First define
char function();
char (*fnptr)();
fnptr = function;
ch = (*fnptr)(i);
switch (choice)
{
case 1: fnptr = printer;
break;
case 2: fnptr = textscreen;
break;
case 3: fnptr = windows;
}
Output(data,fnptr);
Questions
1. What is a pointer?
2. How is a variable declared to be a pointer?
3. What data types can pointers "point to"?
4. Write a statement which converts a pointer to a character into a pointer to a
double type. (This is not as pointless as it seems. It is useful in dealing with
unions and memory allocation functions.)
5. Why is it incorrect to declare: float *number = 2.65; ?
Getting information in and out of a computer is the most important thing that a
program can do. Without input and output computers would be quite useless.
C treats all its output as though it were reading or writing to different files. A file is
really just an abtraction: a place where information comes from or can be sent to.
Some files can only be read, some can only be written to, others can be both read
from and written to. In other situations files are called I/O streams.
C has three files (also called streams) which are always open and ready for use. They
are called stdin, stdout and stderr, meaning standard input and standard output and
standard error file. Stdin is the input which usually arrives from the keyboard of a
computer. stdout is usually the screen. stderr is the route by which all error messages
pass: usually the screen. This is only `usually' because the situation can be altered. In
fact what happens is that these files are just handed over to the local operating system
to deal with and it chooses what to do with them. Usually this means the keyboard
and the screen, but it can also be redirected to a printer or to a disk file or to a modem
etc.. depending upon how the user ran the program.
The keyboard and screen are referred to as the standard input/output files because this
is what most people use, most of the time. Also the programmer never has to open or
close these, because C does it automatically. The C library functions covered by
stdio.h provides some methods for working with stdin and stdout. They are
simplified versions of the functions that can be used on any kind of file, See Files and
Devices. In order of importance, they are:
printf ()
scanf ()
getchar()
putchar()
gets ()
puts ()
• printf again:
• Example 9:
• Output 9:
• Formatting with printf:
• Example 10:
• Output 10:
• Special Control Characters again:
• Questions 15:
• scanf:
• Conversion characters:
• How does scanf see the input:
• First account of scanf:
• The dangerous function:
• Keeping scanf under control:
• Example 11:
• Matching without assigning:
• Formal Definition of scanf:
• Summary of points about scanf:
• Questions 15b:
• Low Level Input/Output:
• Questions 15c:
printf
The printf function has been used widely up to now for output because it provides a
neat and easy way of printing text and numbers to stdout (the screen). Its name is
meant to signify formatted printing because it gives the user control over how text and
numerical data are to be laid out on the screen. Making text look good on screen is
important in programming. C makes this easy by allowing you to decide how the text
will be printed in the available space. The printf function has general form:
printf ("string...",variables,numbers)
It contains a string (which is not optional) and it contains any number of parameters to
follow: one for each blank field in the string.
The blank fields are control sequences which one can put into the string to be filled in
with numbers or the contents of variables before the final result is printed out. These
fields are introduced by using a % character, followed by some coded information,
which says something about the size of the blank space and the type of number or
string which will be filled into that space. Often the string is called the control string
because it contains these control characters.
The simplest use of printf is to just print out a string with no blank fields to be
filled:
The next simplest case that has been used before now is to print out a single integer
number:
printf ("%d",number);
Some number = 42
The text cursor is left pointing to the character just after the 2. Notice the way that %d
is swapped for the number 42. %d defines a field which is filled in with the value of
the variable.
There are other kinds of data than integers though. Any kind of variable can be
printed out with printf. %d is called a conversion character for integers because it
tells the compiler to treat the variable to be filled into it as an integer. So it better had
be an integer or things will go wrong! Other characters are used for other kinds of
data. Here is a list if the different letters for printf.
d
signed denary integer
u
unsigned denary integer
x
hexadecimal integer
o
octal integer
s
string
c
single character
f
fixed decimal floating point
e
scientific notation floating point
g
use f or e, whichever is shorter
The best way to learn these is to experiment with different conversion characters. The
example program and its output below give some impression of how they work:
Example Listing
/*******************************************************/
/* */
/* printf Conversion Characters and Types */
/* */
/*******************************************************/
#include <stdio.h>
main ()
{ int i = -10;
unsigned int ui = 10;
float x = 3.56;
double y = 3.52;
char ch = 'z';
char *string_ptr = "any old string";
Output
signed integer -10
unsigned integer 10
This is wrong! 10See what happens when you get the character wrong!
Hexadecimal FFFFFFF6 A
Octal 37777777766 12
Float and double 3.560000 3.520000
ditto 3.560000E+00 3.520000E+00
ditto 3.560000 3.520000
single character z
whole string -> any old string
%-10.3f
is an extended version of %f, which carries some more information. That extra
information takes the form:
% [-] [fwidth] [.p] X
where the each bracket is used to denote that the item is optional and the symbols
inside them stand for the following.
[fwidth]
This is a number which specifies the field width of this "blank field". In other
words, how wide a space will be made in the string for the object concerned?
In fact it is the minimum field width because if data need more room than is
written here they will spill out of their box of fixed size. If the size is bigger
than the object to be printed, the rest of the field will be filled out with spaces.
[-]
If this included the output will be left justified. This means it will be aligned
with the left hand margin of the field created with [fwidth]. Normally all
numbers are right justified, or aligned with the right hand margin of the field
"box".
[.p]
This has different meanings depending on the object which is to be printed.
For a floating point type (float or double) p specifies the number of decimal
places after the point which are to be printed. For a string it specifies how
many characters are to be printed.
The table below helps to show the effect of changing these format controls. The width
of a field is draw in by using the | bars.
42 %6d | 42|
42 %-6d |42 |
324 %10d | 324|
-1 %-10d |-1 |
-1 %1d |-1|(overspill)
'z' %3c | z|
'z' %-3c |z |
"printf" %s |printf|
"printf" %10s | printf|
"printf" %2s |printf|(overspill)
"printf" %5.3s | pri|
"printf" %-5.3s |pri |
"printf" %.3s |pri|
Example Listing
/***********************************************/
/* */
/* Multiplication Table */
/* */
/***********************************************/
#include <stdio.h>
{ int i,j;
Output
1 2 3 4 5 6 7 8 9 10
2 4 6 8 10 12 14 16 18 20
3 6 9 12 15 18 21 24 27 30
4 8 12 16 20 24 28 32 36 40
5 10 15 20 25 30 35 40 45 50
6 12 18 24 30 36 42 48 54 60
7 14 21 28 35 42 49 56 63 70
8 16 24 32 40 48 56 64 72 80
9 18 27 36 45 54 63 72 81 90
10 20 30 40 50 60 70 80 90 100
\b
backspace BS
\f
form feed FF (also clear screen)
\n
new line NL (like pressing return)
\r
carriage return CR (cursor to start of line)
\t
horizontal tab HT
\v
vertical tab
\"
double quote
\'
single quote character '
\\
backslash character \
\ddd
character ddd where ddd is an ASCII code given in octal or base 8, See
Character Conversion Table.
\xddd
character ddd where ddd is an ASCII code given in hexadecimal or base 16,
See Character Conversion Table.
Node:Questions 15, Next:scanf, Previous:Special Control Characters again,
Up:Standard Output and Standard Input
Questions
1. Write a program which simply prints out: 6.23e+00
2. Investigate what happens when you type the wrong conversion specifier in a
program. e.g. try printing an integer with %f or a floating point number with
%c. This is bound to go wrong - but how will it go wrong?
3. What is wrong with the following statements?
1. printf (x);
2. printf ("%d");
3. printf ();
4. printf ("Number = %d");
scanf
scanf is the input function which gets formatted input from the file stdin (the
keyboard). This is a very versatile function but it is also very easy to go wrong with.
In fact it is probably the most difficult to understand of all the C standard library
functions.
Remember that C treats its keyboard input as a file. This makes quite a difference to
the way that scanf works. The actual mechanics of scanf are very similar to those of
printf in reverse
scanf ("string...",pointers);
with one important exception: namely that it is not variables which are listed after the
control string, but pointers to variables. Here are some valid uses of scanf:
int i;
char ch;
float x;
Notice the & characters which make the arguments pointers. Also notice the
conversion specifiers which tell scanf what types of data it is going to read. The other
possibility is that a program might already have pointers to a particular set of
variables in that case the & is not needed. For instance:
function (i,ch,x)
int *i;
char *ch;
float *x;
{
scanf ("%d %c %f", i, ch, x);
}
In this case it would actually be wrong to write the ampersand & symbol.
Conversion characters
The conversion characters for scanf are not identical to those for printf and it is
much more important to be precise and totally correct with these than it is with
printf.
d
denary integer (int or long int)
ld
long decimal integer
x
hexadecimal integer
o
octal integer
h
short integer
f
float type
lf
long float or double
e
float type
le
double
c
single character
s
character string
The difference between short integer and long integer can make or break a program. If
it is found that a program's input seems to be behaving strangely, check these
carefully. (See the section on Errors and Debugging for more about this.)
Node:How does scanf see the input, Next:First account of scanf, Previous:Conversion
characters, Up:Standard Output and Standard Input
There is an important point here: although scanf will start scanning through
characters as soon as they are in the buffer, the operating system often sees to it that
scanf doesn't get to know about any of the characters until the user has pressed the
RETURN or ENTER key on the computer or terminal. If the buffer is empty scanf will
wait for some characters to be put into it.
To understand how scanf works, it is useful to think of the input as coming in `lines'.
A line is a bunch of characters ending in a newline character \n. This can be
represented by a box like the one below:
--------------------------------------
| some...chars.738/. |'\n'|
--------------------------------------
As far as scanf is concerned, the input is entirely made out of a stream of characters.
If the programmer says that an integer is to be expected by using the %d conversion
specifier then scanf will try to make sense of the characters as an integer. In other
words, it will look for some characters which make up a valid integer, such as a group
of numbers all between 0 and 9. If the user says that floating point type is expected
then it will look for a number which may or may not have a decimal point in it. If the
user just wants a character then any character will do!
int i;
char ch;
float x;
Here is a simplified, ideal view of what happens. scanf looks at the control string and
finds that the first conversion specifier is %d which means an integer. It then tries to
find some characters which fit the description of an integer in the input file. It skips
over any white space characters (spaces, newlines) which do not constitute a valid
integer until it matches one. Once it has matched the integer and placed its value in
the variable i it carries on and looks at the next conversion specifier %c which means
a character. It takes the next character and places it in ch. Finally it looks at the last
conversion specifier %f which means a floating point number and finds some
characters which fit the description of a floating point number. It passes the value
onto the variable x and then quits.
This brief account of scanf does not tell the whole story by a long way. It assumes
that all the characters were successfully found and that everything went smoothly:
something which seldom happens in practice!
{
while (getchar() != '\n')
{
}
}
The action of this function is simply to skip to the end of the input line so that there
are no characters left in the input. It cannot stop scanf from getting out of step before
the end of a line because no function can stop the user from typing in nonsense! So to
get a single integer, for instance, a program could try:
int i;
scanf("%d",&i);
skipgarb();
The programmer must police user-garbage personally by using a loop to the effect of:
while (inputisnonsense)
{
printf ("Get your act together out there!!\n");
scanf (..)
skipgarb();
}
/****************************************/
/* Example 1 */
/****************************************/
#include <stdio.h>
main ()
{ int i = 0;
char ch = '*';
float x = 0;
This program just waits for a line from the user and prints out what it makes of that
line. Things to notice about these examples are the way in which scanf
`misunderstands' what the user has typed in and also the values which the variables
------------------
| 1 |'x'| 2.3 |'\n'|
------------------
In this example everything works properly. There are no spaces to confuse matters. it
is simple for scanf to see what the first number is because the next character is x
which is not a valid number.
Input : 1 x 2.3
Output: 1 0.000000
------ ------
|1|' '| <break> |x 2.3|
------ ------
In this example the integer is correctly matched as 1. The character is now a space and
the x is left in the stream. The x does not match the description of a float value so
scanf terminates, leaving x 2.3 still in the input stream.
Input : .
Output: 0 * 0.000000
---
|'.'| <break>
---
A single full-stop (period). scanf quits straight away because it looks for an integer.
It leaves the whole input line (which is just the period .) in the input stream.
/****************************************/
/* Example 2 */
/****************************************/
#include <stdio.h>
main ()
{ int i = 0;
char ch = '*',ch2,ch3;
float x = 0;
6 x2.36
6 0.000000
x 2
--------- -------------
| 6 | ' ' | <break> |'x'|'2'| .36 |
--------- -------------
Here the integer is successfully matched with 6. The character is matched with a
space but the float character finds an x in the way, so the first scanf aborts leaving
the value of x unchanged and the rest of the characters still in the file. The second
scanf function then picks these up. It can be seen that the first two characters are the x
which caused the previous scanf to fail and the first 2 of the intended floating point
number.
/****************************************/
/* Example 3 */
/****************************************/
#include <stdio.h>
main()
{ char ch1,ch2,ch3;
Trials:
input : abc
output: a b c
input : a [return]
b [return]
c [return]
output: a b c
input : 2.3
output: 2 . 3
scanf ("%*c");
would skip a single character. The * character means do not make an assignment.
Note carefully that the following is wrong:
scanf ("%*c", &ch);
A pointer should not be given for a dummy conversion character. In this simple case
above it probably does not matter, but in a string with several things to be matched, it
would make the conversion characters out of step with the variables, since scanf does
not return a value from a dummy conversion character. It might seem as though there
would be no sense in writing:
because the whole input file is one long string after all, but this is not true because, as
far as scanf is concerned a string is terminated by any white space character, so the
float type x and the character ch would receive values provided there were a space or
newline character after any string.
If any non-conversion characters are typed into the string scanf will match and skip
over them in the input. For example:
If the input were: Number = 256, scanf would skip over the Number = . As usual, if
the string cannot be matched, scanf will abort, leaving the remaining characters in
the input stream.
/****************************************/
/* Example 4 */
/****************************************/
#include <stdio.h>
main()
{ float x = 0;
int i = 0;
char ch = '*';
printf("%f %d %c",x,i,ch);
Input : Skipthis! 23
Output: 0.000000 23 *
Input : 26
Output: 0.000000 0 *
The value n returned is the number of items matched or the end of file character EOF,
or NULL if the first item did not match. This value is often discarded. The control
string contains a number of conversion specifiers with the following general form:
%[*][n]X
[*]
the optional assignment suppression character.
[n]
this is a number giving the maximum field width to be accepted by scanf for a
particular item. That is, the maximum number of characters which are to be
thought of as being part of one the current variable value.
X
is one of the characters listed above.
Any white space characters in the scanf string are ignored. Any other characters are
matched. The pointers must be pointers to variables of the correct type and they must
match the conversion specifiers in the order in which they are written.
There are two variations on the conversion specifiers for strings, though it is very
likely that many compilers will not support this. Both of the following imply strings:
%[set of characters]
a string made up of the given characters only.
%[^set of characters]
a string which is delimited by the set of characters given.
For example, to read the rest of a line of text, up to but not including the end of line,
into a string array one would write:
scanf("%[^\n]",stringarray);
Questions
1. What is a white space character?
2. Write a program which fetches two integers from the user and multiplies them
together. Print out the answer. Try to make the input as safe as possible.
3. Write a program which just echoes all the input to the output.
4. Write a program which strips spaces out of the input and replaces them with a
single newline character.
5. scanf always takes pointer arguments. True or false?
scanf() and printf() are relatively high level functions: this means that they are
versatile and do a lot of hidden work for the user. C also provides some functions for
dealing with input and output at a lower level: character by character. These functions
are called getchar() and putchar() but, in fact, they might not be functions: they
could be macros instead, See Preprocessor.
|
high level: printf() | scanf()
|
/ | \
|
low level: putchar() | getchar()
|
getchar gets a single character from the input file stdin; putchar writes a single
character to the output file stdout. getchar returns a character type: the next character
on the input file. For example:
char ch;
ch = getchar();
This places the next character, what ever it might be, into the variable ch. Notice that
no conversion to different data types can be performed by getchar() because it deals
with single characters only. It is a low level function and does not `know' anything
about data types other than characters.
getchar was used in the function skipgarb() to tame the scanf() function. This
function was written in a very compact way. Another way of writing it would be as
below:
{ char ch;
ch = getchar();
The != symbol means "is not equal to" and the while statement is a loop. This
function keeps on getchar-ing until it finds the newline character and then it quits.
This function has many uses. One of these is to copy immediate keypress statements
of languages like BASIC, where a program responds to keys as they are pressed
without having to wait for return to be pressed. Without special library functions to
give this kind of input (which are not universal) it is only possible to do this with the
return key itself. For example:
skipgarb();
skipgarb() does not receive any input until the user presses RETURN, and then it
simply skips over it in one go! The effect is that it waits for RETURN to be pressed.
putchar() writes a character type and also returns a character type. For example:
char ch = '*';
putchar (ch);
ch = putchar (ch);
These two alternatives have the same effect. The value returned by putchar() is the
character which was written to the output. In other words it just hands the same value
back again. This can simply be discarded, as in the first line. putchar() is not much
use without loops to repeat it over and over again.
putchar( function() );
This depends entirely upon the compiler, but it is something to watch out for.
Two functions which are similar to putchar() and getchar() are puts() and
gets() which mean putstring and getstring respectively. Their purpose is either to
read a whole string from the input file stdin or write a whole string to the output
stdout. Strings are groups or arrays of characters. For instance:
char *string[length];
string = gets(string);
puts(string);
Questions
1. Is the following statement possible? (It could depend upon your compiler: try
it!)
2. putchar(getchar());
3.
What might this do? (Hint: re-read the chapter about the pre-processor.)
An operator is something which takes one or more values and does something useful
with those values to produce a result. It operates on them. The terminology of
operators is the following:
operator
Something which operates on someting.
operand
Each thing which is operated upon by an operator is called an operand.
operation
The action which was carried out upon the operands by the operator!
• Operators which produce new values from old ones. They make a result from
their operands. e.g. +, the addition operator takes two numbers or two
variables or a number and a variable and adds them together to give a new
number.
• Operators which make comparisons. e.g. less than, equal to, greater than...
• Operators which produce new variable types: like the cast operator.
The majority of operators fall into the first group. In fact the second group is a subset
of the first, in which the result of the operation is a boolean value of either true of
false.
C has no less than thirty nine different operators. This is more than, say, Pascal and
BASIC put together! The operators serve a variety of purposes and they can be used
very freely. The object of this chapter is to explain the basics of operators in C. The
more abstruse operators are looked at in another chapter.
+
plus (unary)
-
minus (force value to be negative)
+
addition
-
subtraction
*
multiplication
/
floating point division
/
integer division "div"
%
integer remainder "mod"
These operators would not be useful without a partner operator which could attach the
values which they produce to variables. Perhaps the most important operator then is
the assignment operator:
= assignment operator
x = 2.356;
y = x;
x = x + 2 + 3/5;
The assignment operator takes the value of whatever is on the right hand side of the =
symbol and puts it into the variable on the left hand side. As usual there is some
standard jargon for this, which is useful to know because compilers tend to use this
when handing out error messages. The assignment operator can be summarized in the
following way:
lvalue = expression;
This statement says no more than what has been said about assignments already:
namely that it takes something on the right hand side and attaches it to whatever is on
the left hand side of the = symbol. An expression is simply the name for any string of
operators, variables and numbers. All of the following could be called expressions:
1 + 2 + 3
a + somefunction()
32 * x/3
i % 4
Lvalues on the other hand are simply names for memory locations: in other words
variable names, or identifiers. The name comes from `left values' meaning anything
which can legally be written on the left hand side of an assignment.
Example
/**************************************/
/* */
/* Operators Demo # 1 */
/* */
/**************************************/
#include <stdio.h>
/**************************************/
main ()
{ int i;
i = 6;
printf ("i = 6, -i is : %d\n", -i);
Output
Arithmetic Operators
i = 6, -i is : -6
int 1 + 2 = 3
int 5 - 1 = 4
int 5 * 2 = 10
9 div 4 = 2 remainder 1:
int 9 / 4 = 2
int 9 4 = 1
double 9 / 4 = 2.250000
a + b / 4 * 2
((a + b) / 4) * 2
or
(a + b)/ (4 * 2)
or
a + (b/4) * 2
and so on. By using parentheses, any doubt about what the expression means is
removed. Parentheses are said to have a higher priority than + * or / because they are
evaluated as "sealed capsules" before other operators can act on them. Putting
parentheses in may remove the ambiguity of expressions, but it does not alter than
fact that
a + b / 4 * 2
is ambiguous. What will happen in this case? The answer is that the C compiler has a
convention about the way in which expressions are evaluated: it is called operator
precedence. The convention is that some operators are stronger than others and that
the stronger ones will always be evaluated first. Otherwise, expressions like the one
above are evaluated from left to right: so an expression will be dealt with from left to
right unless a strong operator overrides this rule. Use parentheses to be sure. A table
of all operators and their priorities is given in the reference section.
++ -- + - &
*ptr++;
would do ++ before *.
++
increment: add one to
--
decrement: subtract one from
These attach to any variable of integer or floating point type. (character types too,
with care.) They are used to simply add or subtract 1 from a variable. Normally, in
other languages, this is accomplished by writing:
variable = variable + 1;
In C this would also be quite valid, but there is a much better way of doing this:
variable++; or
++variable;
variable = variable - 1;
is equivalent to:
variable--;
or
--variable;
Notice particularly that these two operators can be placed in front or after the name of
the variable. In some cases the two are identical, but in the more advanced uses of C
operators, which appear later in this book, there is a subtle difference between the
two.
variable += 23;
This performs exactly the same operation. Similarly one could write:
as
variable1 += variable2;
<operation>=
operators: one for each of the major operations which can be performed. There is,
naturally, one for subtraction too:
can be written:
variable -= 42;
More surprisingly, perhaps, the multiplicative assignment:
variable = variable * 2;
may be written:
variable *= 2;
and so on. The main arithmetic operators all follow this pattern:
+=
add assign
-=
subtract assign
*=
multiply assign
/=
divide (double) and (int) types
%=
remainder (int) type only.
and there are more exotic kinds, used for bit operations or machine level operations,
which will be ignored at this stage:
>>=
<<=
^=
|=
&=
Example Listing
/**************************************/
/* */
/* Operators Demo # 2 */
/* */
/**************************************/
#include <stdio.h>
/**************************************/
main ()
{ int i;
i = 10; /* Assignment */
printf("i = 10 : %d\n",i);
i++; /* i = i + 1 */
printf ("i++ : %d\n",i);
i += 5; /* i = i + 5 */
printf ("i += 5 : %d\n",i);
i--; /* i = i = 1 */
printf ("i-- : %d\n",i);
i -= 2; /* i = i - 2 */
printf ("i -= 2 : %d\n",i);
i *= 5; /* i = i * 5 */
printf ("i *= 5 :%d\n",i);
i /= 2; /* i = i / 2 */
printf ("i /= 2 : %d\n",i);
i %= 3; /* i = i % 3 */
printf ("i %%= 3 : %d\n",i);
}
Output
Assignment Operators
i = 10 : 10
i++ : 11
i += 5 : 16
i-- : 15
i -= 2 : 13
i *= 5 :65
i /= 2 : 32
i %= 3 : 2
char ch;
int i;
i = (int) ch;
This operator was introduced earlier, See Variables. It will always produce some
value, whatever the conversion: however remotely improbable it might seem. For
instance it is quite possible to convert a character into a floating point number: the
result will be a floating point representation of its ASCII code!
Node:Expressions and Types, Next:Summary of Operators and Precedence,
Previous:The Cast Operator, Up:Assignments Expressions and Operators
double
long float
int
long int
If the programmer tries to use other types like short or float in a mathematical
expression they will be cast into long types automatically by the compiler. This can
cause confusion because the compiler will spot an error in the following statement:
short i, j = 2;
i = j * 2 + 1;
A compiler will claim that there is a type mismatch between i and the expression on
the right hand side of the assignment. The compiler is perfectly correct of course,
even though it appears to be wrong. The subtlety is that arithmetic cannot be done in
short type variables, so that the expression is automatically converted into long type
or int type. So the right hand side is int type and the left hand side is short type:
hence there is indeed a type mismatch. The programmer can get around this by using
the cast operator to write:
short i, j = 2;
i = (short) j * 2 + 1;
float x, y = 2.3;
x = y * 2.5;
==
is equal to
!=
is not equal to
>
is greater than
<
is less than
>=
is greater than or equal to
<=
is less than or equal to
These operators belong to the second group according to the scheme above but they
do actually result in values so that they could be thought of as being a part of the first
group of operators too. The values which they produce are called true and false. As
words, "true" and "false" are not defined normally in C, but it is easy to define them
as macros and they may well be defined in a library file:
#define TRUE 1
#define FALSE 0
Falsity is assumed to have the value zero in C and truth is represented by any non-
zero value. These comparison operators are used for making decisions, but they are
themselves operators and expressions can be built up with them.
1 == 1
has the value "true" (which could be anything except zero). The statement:
int i;
i = (1 == 2);
Comparisons are often made in pairs or even in groups and linked together with words
like OR and AND. For instance, some test might want to find out whether:
C does not have words for these operations but gives symbols instead. The logical
operators, as they are called, are as follows:
&&
logical AND
||
logical OR inclusive
!
logical NOT
The statement which was written in words above could be translated as:
The statement:
translates to:
Shakespeare might have been disappointed to learn that, whatever the value of a
variable tobe the result of
must always be true. The NOT operator always creates the logical opposite: !true is
false and !false is true. On or the other of these must be true. thequestion is
therefore always true. Fortunately this is not a matter of life or death!
Questions
1. What is an operand?
2. Write a statement which prints out the remainder of 5 divided by 2.
3. Write a short statement which assigns the remainder of 5 divided by 2 to a
variable called "rem".
4. Write a statement which subtracts -5 from 10.
5. Write in C: if 1 is not equal to 23, print out "Thank goodness for
mathematics!"
Decisions
Testing and Branching. Making conditions.
Suppose that a fictional traveller, some character in a book like this one, came to the
end of a straight, unfinished road and waited there for the author to decide where the
road would lead. The author might decide a number of things about this road and its
traveller:
• The road will carry on in a straight line. If the traveller is thirsty he will stop
for a drink before continuing.
• The road will fork and the traveller will have to decide whether to take the left
branch or the right branch.
• The road might have a crossroads or a meeting point where many roads come
together. Again the traveller has to decide which way to go.
We are often faced with this dilemma: a situation in which a decision has to be made.
Up to now the simple example programs in this book have not had any choice about
the way in which they progressed. They have all followed narrow paths without any
choice about which way they were going. This is a very limited way of expressing
ideas though: the ability to make decisions and to choose different options is very
useful in programming. For instance, one might want to implement the following
ideas in different programs:
• If the user hits the jackpot, write some message to say so. "You've won the
game!"
• If a bank balance is positive then print C for credit otherwise print D for debit.
• If the user has typed in one of five things then do something special for each
special case, otherwise do something else.
These choices are actually just the same choices that the traveller had to make on his
undecided path, thinly disguised. In the first case there is a simple choice: a do of
don't choice. The second case gives two choices: do thing 1 or thing 2. The final
choice has several possibilities.
C offers four ways of making decisions like the ones above. They are listed here
below. The method which is numbered 2b was encountered in connection with the C
preprocessor; its purpose is very similar to 2a.
1: if (something_is_true)
{
/* do something */
}
2a: if (something_is_true)
{
/* do one thing */
}
else
{
/* do something else */
}
2b: ? (something_is_true) :
/* do one thing */
:
/* do something else */
3: switch (choice)
{
case first_possibility : /* do something */
case second_possibility : /* do something */
....
}
• if:
• example f1:
• if else:
• Nested ifs and logic:
• Example 14:
• Stringing together if..else:
• switch:
• Example 15:
• To try:
if
The first form of the if statement is an all or nothing choice. if some condition is
satisfied, do what is in the braces, otherwise just skip what is in the braces. Formally,
this is written:
if (condition) statement;
or
if (condition)
{
compound statement
}
Notice that, as well as a single statement, a whole block of statements can be written
under the if statement. In fact, there is an unwritten rule of thumb in C that wherever a
single statement will do, a compound statement will do instead. A compound
statement is a block of single statements enclosed by curly braces.
A condition is usually some kind of comparison, like the ones discussed in the
previous chapter. It must have a value which is either true or false (1 or 0) and it must
be enclosed by the parentheses ( and ). If the condition has the value `true' then the
statement or compound statement following the condition will be carried out,
otherwise it will be ignored. Some of the following examples help to show this:
int i;
scanf ("%ld",&i);
if (i == 0)
{
printf ("The number was zero");
}
if (i > 0)
{
printf ("The number was positive");
}
if (i < 0)
{
printf ("The number was negative");
}
The same code could be written more briefly, but perhaps less consistently in the
following way:
int i;
scanf ("%ld",&i);
The preference in this book is to include the block braces, even when they are not
strictly required. This does no harm. It is no more or less efficient, but very often you
will find that some extra statements have to go into those braces, so it is as well to
include them from the start. It also has the appeal that it makes if statements look the
same as all other block statements and it makes them stand out clearly in the program
text. This rule of thumb is only dropped in very simple examples like:
if (i == 0) i++;
The if statement alone allows only a very limited kind of decision: it makes do or
don't decisions; it could not decide for the traveller whether to take the left fork or the
right fork of his road, for instance, it could only tell him whether to get up and go at
all. To do much more for programs it needs to be extended. This is the purpose of the
else statement, described after some example listings..
Node:example f1, Next:if else, Previous:if, Up:Decisions
Example Listings
/*****************************************/
/* */
/* If... #1 */
/* */
/*****************************************/
#include <stdio.h>
#define TRUE 1
#define FALSE 0
/******************************************/
main ()
{ int i;
if (TRUE)
{
printf ("This is always printed");
}
if (FALSE)
{
printf ("This is never printed");
}
}
/*******************************************/
/* */
/* If demo #2 */
/* */
/*******************************************/
#include <stdio.h>
/*******************************************/
/* Level 0 */
/*******************************************/
main ()
{ double fuel,distance;
FindValues (&fuel,&distance);
Report (fuel,distance);
}
/********************************************/
/* Level 1 */
/********************************************/
double *fuel,*distance;
{
/* how much fuel used since last check on values */
/**********************************************/
double fuel,distance;
{ double kpl;
kpl = distance/fuel;
if (kpl <= 1)
{
printf ("Predict fuel leak or car");
printf (" needs a service\n");
}
if ... else
The if .. else statement has the form:
if (condition)
{
statements
}
else
{
statements
}
The if..else statement is a two way branch: it means do one thing or the other.
When it is executed, the condition is evaluated and if it has the value `true' (i.e. not
zero) then statement1 is executed. If the condition is `false' (or zero) then statement2
is executed. The if..else construction often saves an unnecessary test from having
to be made. For instance:
int i;
scanf ("%ld",i);
if (i > 0)
{
printf ("That number was positive!");
}
else
{
printf ("That number was negative or zero!");
}
It is not necessary to test whether i was negative in the second block because it was
implied by the if..else structure. That is, that block would not have been executed
unless i were NOT greater than zero. The weary traveller above might make a
decision such as:
or:
if (i > 2)
{
if (i < 4)
{
printf ("i is three");
}
}
Both of these test i for the same information, but they do it in different ways. The
first method might been born out of the following sequence of thought:
If i is greater than 2 and i is less than four, both at the same time, then i has
to be 3.
If i is greater than 2, do what is in the curly braces. Inside these curly braces i
is always greater than 2 because otherwise the program would never have
arrived inside them. Now, if i is also less than 4, then do what is inside the
new curly braces. Inside these curly braces i is always less than 4. But wait!
The whole of the second test is held inside the "i is greater than 2" braces,
which is a sealed capsule: nothing else can get in, so, if the program gets into
the "i is less than 4" braces as well, then both facts must be true at the same
time. There is only one integer which is bigger than 2 and less than 4 at the
same time: it is 3. So i is 3.
The aim of this demonstration is to show that there are two ways of making multiple
decisions in C. Using the logical comparison operators &&, || (AND,OR) and so on..
several multiple tests can be made. In many cases though it is too difficult to think in
terms of these operators and the sealed capsule idea begins to look attractive. This is
another advantage of using the curly braces: it helps the programmer to see that if
statements and if..else statements are made up of sealed capsule parts. Once inside
a sealed capsule
if (i > 2)
{
/* i is greater than 2 in here! */
}
else
{
/* i is not greater than 2 here! */
}
the programmer can rest assured that nothing illegal can get in. The block braces are
like regions of grace: they cannot be penetrated by anything which does not satisfy the
right conditions. This is an enourmous weight off the mind! The programmer can sit
back and think: I have accepted that i is greater than 2 inside these braces, so I can
stop worrying about that now. This is how programmers learn to think in a structured
way. They learn to be satisfied that certain things have already been proven and thus
save themselves from the onset of madness as the ideas become too complex to think
of all in one go.
Example Listing
/***********************************************/
/* */
/* If demo #3 */
/* */
/***********************************************/
#include <stdio.h>
/***********************************************/
main ()
{ int persnum,usernum,balance;
persnum = 7462;
balance = -12;
usernum = getnumber();
if (usernum == 7462)
{
printf ("\nThe current state of your account\n");
printf ("is %d\n",balance);
if (balance < 0)
{
printf ("The account is overdrawn!\n");
}
}
else
{
printf ("This is not your account\n");
}
/**************************************************/
{ int num = 0;
scanf ("%d",&num);
return (num);
}
/**************************************************/
/* Program 1 */
/**************************************************/
#include <stdio.h>
main ()
{ int result;
/* end */
/**************************************************/
/* Program 2 */
/**************************************************/
#include <stdio.h>
main ()
{ int result;
printf("Type in exam result");
scanf ("%d",&result);
else
{
if (result > 20)
{
printf ("You have passed.");
}
else
{
if (result > 70)
{
printf ("You got an A!");
}
}
}
}
The answer is that the second of these programs can be more efficient. This because it
uses the else form of the if statement which in turn means that few things have to be
calculated. Program one makes every single test, because the program meets every if
statement, one after the other. The second program does not necessarily do this
however. The nested if statements make sure that the second two tests are only made
if the first one failed. Similarly the third test is only performed if the first two failed.
So the second program could end up doing a third of the work of the first program, in
the best possible case. Nesting decisions like this can be an efficient way of
controlling long lists of decisions like those above. Nested loops make a program
branch into lots of possible paths, but choosing one path would preclude others.
It has an expression which is evaluated and a number of constant `cases' which are to
be chosen from, each of which is followed by a statement or compound statement. An
extra statement called break can also be incorporated into the block at any point.
{
case 1: statement1;
break; /* optional line */
case 2: statement2;
break; /* optional line */
....
Example Listing
/************************************************/
/* */
/* switch .. case */
/* */
/************************************************/
#include <stdio.h>
#define CODE 0
/*************************************************/
main ()
{ short digit;
scanf ("%h",&digit);
/************************************************/
short digit;
{
switch (digit)
{
case 0 : printf ("-----");
break;
case 1 : printf (".----");
break;
case 2 : printf ("..---");
break;
case 3 : printf ("...--");
break;
case 4 : printf ("....-");
break;
case 5 : printf (".....");
break;
case 6 : printf ("-....");
break;
case 7 : printf ("--...");
break;
case 8 : printf ("---..");
break;
case 9 : printf ("----.");
}
}
The program selects one of the printf statements using a switch construction. At every
case in the switch, a break statement is used. This causes control to jump straight out
of the switch statement to its closing brace }. If break were not included it would go
right on executing the statements to the end, testing the cases in turn. break this gives
a way of jumping out of a switch quickly.
There might be cases where it is not necessary or not desirable to jump out of the
switch immediately. Think of a function yes() which gets a character from the user
and tests whether it was 'y' or 'Y'.
{
switch (getchar())
{
case 'y' :
case 'Y' : return TRUE
default : return FALSE
}
}
If the character is either 'y' or 'Y' then the function meets the statement return TRUE.
If there had been a break statement after case 'y' then control would not have been
able to reach case 'Y' as well. The return statement does more than break out of
switch, it breaks out of the whole function, so in this case break was not required. The
default option ensures that whatever else the character is, the function returns false.
Things to try
1. Write a program to get a lot of numbers from the user and print out the
maximum and minimum of those.
2. Try to make a counter which is reset to zero when it reaches 9999.
3. Try to write a program incorporating the statement if (yes()) {...}.
Loops
Decisions can also be used to make up loops. Loops free a program from the
straitjacket of doing things only once. They allow the programmer to build a sequence
of instructions which can be executed again and again, with some condition deciding
when they will stop. There are three kinds of loop in C. They are called:
• while
• do ... while
• for
These three loops offer a great amount of flexibility to programmers and can be used
in some surprising ways!
• while:
• Example 16:
• Example 17:
• do while:
• Example 18:
• for:
• The flexible for loop:
• Quitting Loops and Hurrying Them Up!:
• Nested Loops:
• Questions 18:
while
The simplest of the three loops is the while loop. In common language while has a
fairly obvious meaning: the while-loop has a condition:
while (condition)
{
statements;
}
and the statements in the curly braces are executed while the condition has the value
"true" ( 1 ). There are dialects of English, however, in which "while" does not have its
commonplace meaning, so it is worthwhile explaining the steps which take place in a
while loop.
The first important thing about this loop is that has a conditional expression
(something like (a > b) etc...) which is evaluated every time the loop is executed by
the computer. If the value of the expression is true, then it will carry on with the
instructions in the curly braces. If the expression evaluates to false (or 0) then the
instructions in the braces are ignored and the entire while loop ends. The computer
then moves onto the next statement in the program.
The second thing to notice about this loop is that the conditional expression comes at
the start of the loop: this means that the condition is tested at the start of every `pass',
not at the end. The reason that this is important is this: if the condition has the value
false before the loop has been executed even once, the statements inside the braces
will not get executed at all - not even once.
The best way to illustrate a loop is to give an example of its use. One example was
sneaked into an earlier chapter before its time, in order to write the skipgarb()
function which complemented scanf(). That was:
{
while (getchar() != '\n')
{
}
}
This is a slightly odd use of the while loop which is pure C, through and through. It is
one instance in which the programmer has to start thinking C and not any other
language. Something which is immediately obvious from listing is that the while loop
in skipgarb() is empty: it contains no statements. This is quite valid: the loop will
merely do nothing a certain number of times... at least it would do nothing if it were
not for the assignment in the conditional expression! It could also be written:
{
while (getchar() != '\n')
{
}
}
The assignment inside the conditional expression makes this loop special. What
happens is the following. When the loop is encountered, the computer attempts to
evaluate the expression inside the parentheses. There, inside the parentheses, it finds a
function call to getchar(), so it calls getchar() which fetches the next character
from the input. getchar() then takes on the value of the character which it fetched
from the input file. Next the computer finds the != "is not equal to" symbol and the
newline character \n. This means that there is a comparison to be made. The
computer compares the character fetched by getchar() with the newline character
and if they are `not equal' the expression is true. If they are equal the expression is
false. Now, if the expression is true, the while statement will loop and start again -
and it will evaluate the expression on every pass of the loop to check whether or not it
is true. When the expression eventually becomes false the loop will quit. The net
result of this subtlety is that skipgarb() skips all the input characters up to and
including the next newline \n character and that usually means the rest of the input.
Example Listing
Another use of while is to write a better function called yes(). The idea of this
function was introduced in the previous section. It uses a while loop which is always
true to repeat the process of getting a response from the user. When the response is
either yes or no it quits using the return function to jump right out of the loop.
/***********************************************/
/* */
/* Give me your answer! */
/* */
/***********************************************/
#include <stdio.h>
#define TRUE 1
#define FALSE 0
/*************************************************/
/* Level 0 */
/*************************************************/
main ()
{
printf ("Yes or no? (Y/N)\n");
if (yes())
{
printf ("YES!");
}
else
{
printf ("NO!");
}
}
/*************************************************/
/* Level 1 */
/*************************************************/
{ char getkey();
while (true)
{
switch (getkey())
{
case 'y' : case 'Y' : return (TRUE);
case 'n' : case 'N' : return (FALSE);
}
}
}
/*************************************************/
/* Toolkit */
/*************************************************/
{ char ch;
ch = getchar();
skipgarb();
}
/**************************************************/
skipgarb ()
{
while (getchar() != '\n')
{
}
}
/* end */
Example Listing
This example listing prompts the user to type in a line of text and it counts all the
spaces in that line. It quits when there is no more input left and printf out the number
of spaces.
/***********************************************/
/* */
/* while loop */
/* */
/***********************************************/
#include <stdio.h>
main ()
{ char ch;
short count = 0;
..
do while
The do..while loop resembles most closely the repeat..until loops of Pascal and
BASIC except that it is the `logical opposite'. The do loop has the form:
do
{
statements;
}
while (condition)
Notice that the condition is at the end of this loop. This means that a do..while loop
will always be executed at least once, before the test is made to determine whether it
should continue. This is the only difference between while and do..while.
A do..while loop is like the "repeat .. until" of other languages in the following
sense: if the condition is NOTed using the ! operator, then the two are identical.
repeat do
==
This fact might be useful for programmers who have not yet learned to think in C!
Example Listing
Here is an example of the use of a do..while loop. This program gets a line of input
from the user and checks whether it contains a string marked out with "" quote marks.
If a string is found, the program prints out the contents of the string only. A typical
input line might be:
If the string has only one quote mark then the error message `string was not closed
before end of line' will be printed.
/**********************************************/
/* */
/* do .. while demo */
/* */
/**********************************************/
#include <stdio.h>
/*************************************************/
/* Level 0 */
/*************************************************/
main ()
{ char ch,skipstring();
do
{
if ((ch = getchar()) == '"')
{
printf ("The string was:\n");
ch = skipstring();
}
}
/*************************************************/
/* Level 1 */
/*************************************************/
{ char ch;
do
{
ch = getchar();
putchar(ch);
if (ch == '\n')
{
printf ("\nString was not closed ");
printf ("before end of line\n");
break;
}
}
return (ch);
}
for
The most interesting and also the most difficult of all the loops is the for loop. The
name for is a hangover from earlier days and other languages. It is not altogether
appropriate for C's version of for. The name comes from the typical description of a
classic for loop:
For all values of variable from value1 to value2 in steps of value3, repeat the
following sequence of commands....
In BASIC this looks like:
NEXT variable
The C for loop is much more versatile than its BASIC counterpart; it is actually based
upon the while construction. A for loop normally has the characteristic feature of
controlling one particular variable, called the control variable. That variable is
somehow associated with the loop. For example it might be a variable which is used
to count "for values from 0 to 10" or whatever. The form of the for loop is:
statement1
This is some kind of expression which initializes the control variable. This
statement is only carried out once before the start of the loop. e.g. i = 0;
condition
This is a condition which behaves like the while loop. The condition is
evaluated at the beginning of every loop and the loop is only carried out while
this expression is true. e.g. i < 20;
statement2
This is some kind of expression for altering the value of the control variable.
In languages such as Pascal this always means adding or subtracting 1 from
the variable. In C it can be absolutely anything. e.g. i++ or i *= 20 or i /=
2.3 ...
Compare a C for loop to the BASIC for loop. Here is an example in which the loop
counts from 0 to 10 in steps of 0.5:
NEXT X
sum = 0;
for (i = 0; i <= n; i++)
{
sum += i;
}
g4
Example Listing
This example program prints out all the primes numbers between 1 and the macro
value maxint. Prime numbers are numbers which cannot be divided by any number
except 1 without leaving a remainder.
/************************************************/
/* */
/* Prime Number Generator #1 */
/* */
/************************************************/
#include <stdio.h>
/*************************************************/
/* Level 0 */
/*************************************************/
main ()
{ int i;
/*************************************************/
/* Level 1 */
/*************************************************/
int i;
{ int j;
return TRUE;
}
Node:The flexible for loop, Next:Quitting Loops and Hurrying Them Up!,
Previous:for, Up:Loops
Statement really means what it says. C will accept any statement in the place of those
above, including the empty statement. The while loop could be written as a for loop!
Here there are two empty statements, which are just wasted. This flexibility can be put
to better uses though. Consider the following loop:
This loop begins from 2 and each time the statements in the braces are executed x
squares itself! Another odd looking loop is the following one:
for (ch = '*'; ch != '\n'; ch = getchar())
{
}
This could be used to make yet another different kind of skipgarb() function. The
loop starts off by initializing ch with a star character. It checks that ch != '\n'
(which it isn't, first time around) and proceeds with the loop. On each new pass, ch is
reassigned by calling the function getchar(). It is also possible to combine several
incremental commands in a loop:
for (i = 0, j=10; i < j; i++, j--)
{
printf("i = %d, j= %d\n",i,j);
}
Statement2 can be any statement at all which the programmer would like to be
executed on every pass of the loop. Why not put that statement in the curly braces? In
most cases that would be the best thing to do, but in special instances it might keep a
program tidier or more readable to put it in a for loop instead. There is no good rule
for when to do this, except to say: make you code as clear as possible.
It is not only the statements which are flexible. An unnerving feature of the for
construction (according to some programmers) is that even the conditional expression
in the for loop can be altered by the program from within the loop itself if is written as
a variable.
This is so nerve shattering that many languages forbid it outright. To be sure, is not
often a very good idea to use this facility, but in the right hands, it is a powerful one to
have around.
break;
If this statement is encountered a loop will quit where it stands. For instance, an
expensive way of assigning i to be 12 would be:
Of course, another way to do this would be to use the return() statement, which
jumps right out of a whole function. break only jumps out of the loop, so it is less
drastic.
As well as wanting to quit a loop, a programmer might want to hurry a loop on to the
next pass: perhaps to avoid executing a lot of irrelevant statements, for instance. C
gives a statement for this too, called:
continue;
When a continue statement is encountered, a loop will stop whatever it is doing and
will go straight to the start of the next loop pass. This might be useful to avoid
dividing by zero in a program:
Nested Loops
Like decisions, loops will also nest: that is, loops can be placed inside other loops.
Although this feature will work with any loop at all, it is most commonly used with
the for loop, because this is easiest to control. The idea of nested loops is important
for multi-dimensional arrays which are examined in the next section. A for loop
controls the number of times that a particular set of statements will be carried out.
Another outer loop could be used to control the number of times that a whole loop is
carried out. To see the benefit of nesting loops, the example below shows how a
square could be printed out using two printf statements and two loops.
/*****************************************/
/* */
/* A "Square" */
/* */
/*****************************************/
#include <stdio.h>
#define SIZE 10
/*****************************************/
main ()
{ int i,j;
**********
**********
**********
**********
**********
**********
**********
**********
**********
**********
Questions
1. How many kinds of loop does C offer, and what are they?
2. When is the condition tested in each of the loops?
3. Which of the loops is always executed once?
4. Write a program which copies all input to output line by line.
5. Write a program to get 10 numbers from the user and add them together.
Arrays
int triplet[3];
Notice that there is no space between the square bracket [ and the name of the array.
This statement would cause space for three integers type variables to be created in
memory next to each other as in the diagram below.
------------------------------------
int triplet: | | | |
------------------------------------
The number in the square brackets of the declaration is referred to as the `index'
(plural: indicies) or `subscript' of the array and it must be an integer number between
0 and (in this case) 2. The three integers are called elements of the array and they are
referred to in a program by writing:
triplet[0]
triplet[1]
triplet[2]
Note that the indicies start at zero and run up to one less than the number which is
placed in the declaration (which is called the dimension of the array.) The reason for
this will become clear later. Also notice that every element in an array is of the same
type as every other. It is not (at this stage) possible to have arrays which contain many
different data types. When arrays are declared inside a function, storage is allocated
for them, but that storage space is not initialized: that is, the memory space contains
garbage (random values). It is usually necessary, therefore, to initialize the array
before the program truly begins, to prepare it for use. This usually means that all the
elements in the array will be set to zero.
• The storage spaces in arrays have indicies. These numbers can often be related
to variables in a problem and so there is a logical connection to be made
between an array an a program.
• In C, arrays can be initialized very easily indeed. It is far easier to initialize an
array than it is to initialize twenty or so variables.
The first of these reasons is probably the most important one, as far as C is concerned,
since information can be stored in other ways with equally simple initialization
facilities in C. One example of the use of an array might be in taking a census of the
types of car passing on a road. By defining macros for the names of the different cars,
they could easily be linked to the elements in an array.
car 0
auto 1
bil 2
The array could then be used to store the number of cars of a given type which had
driven past. e.g.
/***********************************************/
/* */
/* Census */
/* */
/***********************************************/
#include <stdio.h>
#define NOTFINISHED 1
#define CAR 0
#define AUTO 1
#define BIL 2
/************************************************/
main ()
{ int type[3];
int index;
while (NOTFINISHED)
{
printf ("Enter type number 0,1, or 2");
scanf ("%d", &index);
skipgarb();
This program, first of all, initializes the elements of the array to be zero. It then enters
a loop which repeatedly fetches a number from the user and increases the value stored
in the array element, labelled by that number, by 1. The effect is to count the cars as
they go past. This program is actually not a very good program for two reasons in
particular:
• Firstly, it does not check that the number which the user typed is actually one
of the elements of the array. (See the section below about this.)
• The loop goes on for ever and the program never gives up the information
which is stores. In short: it is not very useful.
Another example, which comes readily to mind, would be the use of a two
dimensional array for storing the positions of chess pieces in a chess game. Two
dimensional arrays have a chessboard-like structure already and they require two
numbers (two indicies) to pinpoint a particular storage cell. This is just like the
numbers on chess board, so there is an immediate and logical connection between an
array and the problem of keeping track of the pieces on a chess board. Arrays play an
important role in the handling of string variables. Strings are important enough to
have a section of their own, See Strings.
Node:Limits and The Dimension of an array, Next:Arrays and for loops,
Previous:Why use arrays?, Up:Arrays
char array[5];
array[7] = '*';
C would happily try to write the character * at the location which would have
corresponded to the seventh element, had it been declared that way. Unfortunately this
would probably be memory taken up by some other variable or perhaps even by the
operating system. The result would be either:
• The value in the incorrect memory location would be corrupted with
unpredictable consequences.
• The value would corrupt the memory and crash the program completely! On
Unix systems this leads to a memory segmentation fault.
The second of these tends to be the result on operating systems with proper memory
protection. Writing over the bounds of an array is a common source of error.
Remember that the array limits run from zero to the size of the array minus one.
Node:Arrays and for loops, Next:Example 19, Previous:Limits and The Dimension of
an array, Up:Arrays
main ()
{ int i, array[SIZE];
It could equally well be used to fill the array with different values. Consider:
main ()
{ int i, array[size];
This fills each successive space with the number of its index:
index 0 1 2 3 4 5 6 7 8 9
---------------------------------------
element | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
contents ---------------------------------------
The for loop can be used to work on an array sequentially at any time during a
program, not only when it is being initialized. The example listing below shows an
example of how this might work for a one dimensional array, called an Eratosthenes
sieve. This sieve is an array which is used for weeding out prime numbers, that is:
numbers which cannot be divided by any number except 1 without leaving a
remainder or a fraction. It works by filling an array with numbers from 0 to some
maximum value in the same way that was shown above and then by going through the
numbers in turn and deleting (setting equal to zero) every multiple of every number
from the array. This eliminates all the numbers which could be divided by something
exactly and leaves only the prime numbers at the end. Try to follow the listing below.
Example Listing
/******************************************************/
/* */
/* Prime Number Sieve */
/* */
/******************************************************/
#include <stdio.h>
/*******************************************************/
/* Level 0 */
/*******************************************************/
main ()
{ short sieve[SIZE];
FillSeive(sieve);
SortPrimes(sieve);
PrintPrimes(sieve);
}
/*********************************************************/
/* Level 1 */
/*********************************************************/
short sieve[SIZE];
{ short i;
short sieve[SIZE];
{ short i;
/***********************************************************/
short sieve[SIZE];
{ short i;
/***********************************************************/
/* Level 2 */
/***********************************************************/
short i,sieve[SIZE];
{ short j, mult = 2;
/* end */
float numbers[SIZE][SIZE];
SIZE is some constant. (The sizes of the two dimensions do not have to be the same.)
This is called a two dimensional array because it has two indicies, or two labels in
square brackets. It has (SIZE * SIZE) or size-squared elements in it, which form an
imaginary grid, like a chess board, in which every square is a variable or storage area.
------------------------------------
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | ... (up to SIZE)
------------------------------------
| 1 | | | | | | | | |
------------------------------------
| 2 | | | | | | | | |
------------------------------------
| 3 | | | | | | | | |
------------------------------------
| 4 | | | | | | | | |
------------------------------------
| 5 | | | | | | | | |
------------------------------------
| 6 | | | | | | | | |
------------------------------------
| 7 | | | | | | | | |
------------------------------------
.
.
(up to SIZE)
Every element in this grid needs two indicies to pin-point it. The elements are
accessed by giving the coordinates of the element in the grid. For instance to set the
element 2,3 to the value 12, one would write:
array[2][3] = 12;
The usual terminology for the two indicies is that the first gives the row number in the
grid and that the second gives the column number in the grid. (Rows go along,
columns hold up the ceiling.) An array cannot be stored in the memory as a grid:
computer memory is a one dimensional thing. Arrays are therefore stored in rows.
The following array:
------------
| 1 | 2 | 3 |
------------
| 4 | 5 | 6 |
------------
| 7 | 8 | 9 |
------------
would be stored:
------------------------------------
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
------------------------------------
* ROW # 1 * ROW # 2 * ROW #3 *
Another way of saying that arrays are stored row-wise is to say that the second index
varies fastest, because a two-dimensional array is always thought of as...
array[row][column]
so for every row stored, there will be lots of columns inside that row. That means the
column index goes from 0..SIZE inside every row, so it is changing faster as the line
of storage is followed.
A three dimensional array, like a cube or a cuboid, could also be defined in the same
kind of way:
double cube[SIZE][SIZE][SIZE];
short notcubic[2][6][8];
Three dimensional arrays are stored according to the same pattern as two dimensional
arrays. They are kept in computer memory as a linear sequence of variable stores and
the last index is always the one which varies fastest.
main ()
{ int i,j;
float array[SIZE1][SIZE2];
main ()
{ int i,j,k;
float array[SIZE1][SIZE2][SIZE3];
An example program helps to show how this happens in practice. The example below
demonstrates the so-called "Game of Life". The aim is to mimic something like cell
reproduction by applying some rigid rules to a pattern of dots . and stars *. A dot is a
place where there is no life (as we know it!) and a star is a place in which there is a
living thing. The rules will be clear from the listing. Things to notice are the way the
program traverses the arrays and the way in which it checks that it is not overstepping
the boundaries of the arrays.
Example Listing
/*********************************************************/
/* */
/* Game of Life */
/* */
/*********************************************************/
#include <stdio.h>
#define SIZE 20
#define MAXNUM 15
#define INBOUNDS (a>=0)&&(a<SIZE)&&(b>=0)&&(b<SIZE)
#define NORESPONSE 1
/*********************************************************/
/* Level 0 */
/*********************************************************/
main ()
{ int count[SIZE][SIZE];
char array[SIZE][SIZE];
int generation = 0;
while (NORESPONSE)
{
CountNeighbours(array,count);
BuildNextGeneration(array,count);
UpdateDisplay(array,++generation);
/**********************************************************/
/* Level 1 */
/**********************************************************/
char array[SIZE][SIZE];
{ int i,j;
char ch;
/********************************************************/
char array[SIZE][SIZE];
int count[SIZE][SIZE];
{ int i,j;
/*******************************************************/
BuildNextGeneration (array,count)
char array[SIZE][SIZE];
int count[SIZE][SIZE];
{ int i,j;
/*******************************************************/
char array[SIZE][SIZE];
int g;
{ int i,j;
/*******************************************************/
/* Level 2 */
/*******************************************************/
numalive (array,i,j)
char array[SIZE][SIZE];
int i,j;
{ int a,b,census;
census = 0;
return (census);
}
/********************************************************/
/* Toolkit input */
/********************************************************/
quit()
{ char ch;
while (NORESPONSE)
{
scanf ("%c",&ch);
if (ch != '\n') skipgarb();
switch (ch)
{
case 'q' : case 'Q' : return (1);
default : return (0);
}
}
}
/********************************************************/
skipgarb ()
{
while (getchar() != '\n')
{
}
}
^^^^^^^^^^^^^^^^^^^^
(user types in: (It doesn't matter if the input
.................... spills over the SIZE guide,
.................... because "skipgarb()" discards it.)
.....................
.....................
.....................
..........***........
...........*.........
......................
.....................
.....................
.....................
*********************
.....................
......................
....................
.....................
......................
......................
......................
...................... )
Generation 1
....................
....................
....................
....................
...........*........
..........***.......
..........***.......
....................
....................
....................
.******************.
.******************.
.******************.
....................
....................
....................
....................
....................
....................
....................
Generation 2
....................
....................
....................
....................
..........***.......
....................
..........*.*.......
...........*........
....................
..****************..
.*................*.
*..................*
.*................*.
..****************..
....................
....................
....................
....................
....................
....................
Q for quit. RETURN to continue.
Generation 3
....................
....................
....................
...........*........
...........*........
..........*.*.......
...........*........
...........*........
...*******...****...
..****************..
.******************.
**................**
.******************.
..****************..
...**************...
....................
....................
....................
....................
....................
Generation 4
....................
....................
....................
....................
..........***.......
..........*.*.......
..........***.......
....*****.*.*.**....
..*..............*..
.*................*.
*..................*
*..................*
*..................*
.*................*.
..*..............*..
....************....
....................
....................
....................
....................
Initializing Arrays
Arrays can be initialized in two ways. The first way is by assigning every element to
some value with a statement like:
array[2] = 42;
array[3] = 12;
or perhaps with the aid of one or more for loops. Because it is tedious, to say the least,
not to mention uneconomical, to initialize the values of each element to as different
value, C provides another method, which employs a single assignment operator = and
curly braces { }. This method only works for static variables and external variables.
Recall that arrays are stored row-wise or with the last index varying fastest. A 3 by 3
array could be initialized in the following way:
{
{10,23,42},
{1,654,0},
{40652,22,0}
};
The internal braces are unnecessary, but help to distinguish the rows from the
columns. The same thing could be written:
int array[3][3] =
{
10,23,42,
1,654,0
40652,22,0
};
Take care to include the semicolon at the end of the curly brace which closes the
assignment.
Note that, if there are not enough elements in the curly braces to account for every
single element in an array, the remaining elements will be filled out with zeros. Static
variables are always guaranteed to be initialized to zero anyway, whereas auto or local
variables are guaranteed to be garbage: this is because static storage is created by the
compiler in the body of a program, whereas auto or local storage is created at run
time.
The name of an array variable, standing alone, is actually a pointer to the first
element in the array.
float numbers[34];
then numbers is a pointer to the first floating point number in the array; numbers is a
pointer in its own right. (In this case it is type `pointer to float'.) So the first element
of the array could be accessed by writing:
numbers[0] = 22.3;
or by writing
*numbers = 22.3;
For character arrays, which are dealt with in some depth in chapter 20, this gives an
alternative way of getting at the elements in the array.
char arrayname[5];
char *ptr;
The code above sets the array arrayname to zero. This method of getting at array data
is not recommended by this author except in very simple computer environments. If a
program is running on a normal microcomputer, then there should be few problems
with this alternative method of handling arrays. On the hand, if the microcomputer is
multi-tasking, or the program is running on a larger system which has a limited
manager, then memory ceases to be something which can be thought of as a sequence
of boxes standing next to one another. A multi-tasking system shares memory with
other programs and it takes what it can find, where it can find it. The upshot of this is
that it is not possible to guarantee that arrays will be stored in one simple string of
memory locations: it might be scattered around in different places. So
ptr = arrayname + 5;
might not be a pointer to the fifth character in a character array. This could be found
instead using the & operator. A pointer to the fifth element can be reliably found with:
ptr = &(arrayname[5]);
Be warned!
Arrays as Parameters
What happens if we want to pass an array as a parameter? Does the program copy the
entire array into local storage? The answer is no because it would be a waste of time
and memory. Arrays can be passed as parameters, but only as variable ones. This is a
simple matter, because the name of the array is a pointer to the array. The Game of
Life program above does this. Notice from that program how the declarations for the
parameters are made.
main ()
{
char array[23];
function (array);
.....
}
function (arrayformal)
char arrayformal[23];
{
}
Any function which writes to the array, passed as a parameter, will affect the original
copy. Array parameters are always variable parameters
Questions
1. Given any array, how would you find a pointer to the start of it?
2. How do you pass an array as a parameter? When the parameter is received by
a function does C allocate space for a local variable and copy the whole array
to the new location?
3. Write a statement which declares an array of type double which measures 4 by
5. What numbers can be written in the indicies of the array?
Node:Strings, Next:Putting together a program, Previous:Arrays, Up:Top
Strings
Strings are pieces of text which can be treated as values for variables. In C a string is
represented as some characters enclosed by double quotes.
"This is a string"
A string may contain any character, including special control characters, such as \n,
\r, \7 etc...
/**********************************************************/
/* */
/* String Declaration */
/* */
/**********************************************************/
#define SIZE 10
char *global_string1;
char global_string2[SIZE];
main ()
{ char *auto_string;
char arraystr[SIZE];
static char *stat_strng;
static char statarraystr[SIZE];
Strings can be declared in two main ways; one of these is as an array of characters, the
other is as a pointer to some pre-assigned array. Perhaps the simplest way of seeing
how C stores arrays is to give an extreme example which would probably never be
used in practice. Think of how a string called string might be used to to store the
message "Tedious!". The fact that a string is an array of characters might lead you to
write something like:
#define LENGTH 9;
main ()
{ char string[LENGTH];
string[0] = 'T';
string[1] = 'e';
string[2] = 'd';
string[3] = 'i';
string[4] = 'o';
string[5] = 'u';
string[6] = 's';
string[7] = '!';
string[8] = '\0';
This method of handling strings is perfectly acceptable, if there is time to waste, but it
is so laborious that C provides a special initialization service for strings, which
bypasses the need to assign every single character with a new assignment!. There are
six ways of assigning constant strings to arrays. (A constant string is one which is
actually typed into the program, not one which in typed in by the user.) They are
written into a short compilable program below. The explanation follows.
/**********************************************************/
/* */
/* String Initialization */
/* */
/**********************************************************/
main ()
char arraystr[20];
/* end */
The details of what goes on with strings can be difficult to get to grips with. It is a
good idea to get revise pointers and arrays before reading the explanations below.
Notice the diagrams too: they are probably more helpful than words.
The first of these assignments is a global, static variable. More correctly, it is a pointer
to a global, static array. Static variables are assigned storage space in the body of a
program when the compiler creates the executable code. This means that they are
saved on disk along with the program code, so they can be initialized at compile time.
That is the reason for the rule which says that only static arrays can be initialized with
a constant expression in a declaration. The first statement allocates space for a pointer
to an array. Notice that, because the string which is to be assigned to it, is typed into
the program, the compiler can also allocate space for that in the executable file too. In
fact the compiler stores the string, adds a zero byte to the end of it and assigns a
pointer to its first character to the variable called global_string1.
The second statement works almost identically, with the exception that, this time the
compiler sees the declaration of a static array, which is to be initialized. Notice that
there is no size declaration in the square brackets. This is quite legal in fact: the
compiler counts the number of characters in the initialization string and allocates just
the right amount of space, filling the string into that space, along with its end marker
as it goes. Remember also that the name of the array is a pointer to the first character,
so, in fact, the two methods are identical.
The third expression is the same kind of thing, only this time, the declaration is inside
the function main() so the type is not static but auto. The difference between this and
the other two declarations is that this pointer variable is created every time the
function main() is called. It is new each time and the same thing holds for any other
function which it might have been defined in: when the function is called, the pointer
is created and when it ends, it is destroyed. The string which initializes it is stored in
the executable file of the program (because it is typed into the text). The compiler
returns a value which is a pointer to the string's first character and uses that as a value
to initialize the pointer with. This is a slightly round about way of defining the string
constant. The normal thing to do would be to declare the string pointer as being static,
but this is just a matter of style. In fact this is what is done in the fourth example.
The fifth example is again identical, in practice to other static types, but is written as
an `open' array with an unspecified size.
The sixth example is forbidden! The reason for this might seem rather trivial, but it is
made in the interests of efficiency. The array declared is of type auto: this means that
the whole array is created when the function is called and destroyed afterwards. auto-
arrays cannot be initialized with a string because they would have to be re-initialized
every time the array were created: that is, each time the function were called. The
final example could be used to overcome this, if the programmer were inclined to do
so. Here an auto array of characters is declared (with a size this time, because there is
nothing for the compiler to count the size of). There is no single assignment which
will fill this array with a string though: the programmer would have to do it character
by character so that the inefficiency is made as plain as possible!
Arrays of Strings
In the previous chapter we progressed from one dimensional arrays to two
dimensional arrays, or arrays of arrays! The same thing works well for strings which
are declared static. Programs can take advantage of C's easy assignment facilities to
let the compiler count the size of the string arrays and define arrays of messages. For
example here is a program which prints out a menu for an application program:
/*********************************************************/
/* */
/* MENU : program which prints out a menu */
/* */
/*********************************************************/
main ()
{ int str_number;
/*********************************************************/
int n;
{
static char *t[] =
{
" -------------------------------------- \n",
" | ++ MENU ++ |\n",
" | ~~~~~~~~~~~~ |\n",
" | (1) Edit Defaults |\n",
" | (2) Print Charge Sheet |\n",
" | (3) Print Log Sheet |\n",
" | (4) Bill Calculator |\n",
" | (q) Quit |\n",
" | |\n",
" | |\n",
" | Please Enter Choice |\n",
" | |\n",
" -------------------------------------- \n"
};
return (t[n]);
}
Notice the way in which the static declaration works. It is initialized once at compile
time, so there is effectively only one statement in this function and that is the return
statement. This function retains the pointer information from call to call. The Morse
coder program could be rewritten more economically using static strings, See
Example 15.
Example Listing
/************************************************/
/* */
/* static string array */
/* */
/************************************************/
#include <stdio.h>
#define CODE 0
/*************************************************/
main ()
{ short digit;
scanf ("%h",&digit);
/************************************************/
short digit;
{
static char *code[] =
{
"dummy", /* index starts at 0 */
"-----",
".----",
"..---",
"...--",
"....-",
".....",
"-....",
"--...",
"---..",
"----.",
};
printf ("%s\n",code[digit]);
}
One way of getting a simple, single string from the user is to define an array and to
read the characters one by one. An example of this was the Game of Life program the
the previous chapter:
• Define the array to be a certain size
• Check that the user does not type in too many characters.
• Use the string in that array.
Another way is to define a static string with an initializer as in the following example.
The function filename() asks the user to type in a filename, for loading or saving by
and return it to a calling function.
char *filename()
do
{
printf ("Enter filename :");
scanf ("%24s",filenm);
skipgarb();
}
while (strlen(filenm) == 0);
return (filenm);
}
The string is made static and given an initializing expression and this forces the
compiler to make some space for the string. It makes exactly 24 characters plus a zero
byte in the program file, which can be used by an application. Notice that the
conversion string in scanf prevents the characters from spilling over the bounds of the
string. The function strlen() is a standard library function which is described below;
it returns the length of a string. skipgarb() is the function which was introduced in
chapter 15.
Neither of the methods above is any good if a program is going to be fetching a lot of
strings from a user. It just isn't practical to define lots of static strings and expect the
user to type into the right size boxes! The next step in string handling is therefore to
allocate memory for strings personally: in other words to be able to say how much
storage is needed for a string while a program is running. C has special memory
allocation functions which can do this, not only for strings but for any kind of object.
Suppose then that a program is going to get ten strings from the user. Here is one way
in which it could be done:
1. Define one large, static string (or array) for getting one string at a time. Call
this a string buffer, or waiting place.
2. Define an array of ten pointers to characters, so that the strings can be recalled
easily.
3. Find out how long the string in the string buffer is.
4. Allocate memory for the string.
5. Copy the string from the buffer to the new storage and place a pointer to it in
the array of pointers for reference.
6. Release the memory when it is finished with.
The function which allocates memory in C is called malloc() and it works like this:
• malloc() should be declared as returning the type pointer to character, with
the statement:
• char *malloc();
•
• malloc() takes one argument which should be an unsigned integer value
telling the function how many bytes of storage to allocate. It returns a pointer
to the first memory location in that storage:
• char *ptr;
• unsigned int size;
•
• ptr = malloc(size);
•
• The pointer returned has the value NULL if there was no memory left to
allocate. This should always be checked.
The fact that malloc() always returns a pointer to a character does not stop it from
being used for other types of data too. The cast operator can force malloc() to give a
pointer to any data type. This method is used for building data structures in C with
"struct" types.
malloc() has a complementary function which does precisely the opposite: de-
allocates memory. This function is called free(). free() returns an integer code, so
it does not have to be declared as being any special type.
An example of how strings can be created using malloc() and free() is given
below. First of all, some explanation of Standard Library Functions is useful to
simplify the program.
Handling strings
The C Standard Library commonly provides a number of very useful functions which
handle strings. Here is a short list of some common ones which are immediately
relevant (more are listed in the following chapter). Chances are, a good compiler will
support a lot more than those listed below, but, again, it really depends upon the
compiler.
strlen()
This function returns a type int value, which gives the length or number of
characters in a string, not including the NULL byte end marker. An example is:
int len;
char *string;
len = strlen (string);
strcpy()
This function copies a string from one place to another. Use this function in
preference to custom routines: it is set up to handle any peculiarities in the
way data are stored. An example is
char *to,*from;
to = strcpy (to,from);
Where to is a pointer to the place to which the string is to be copied and from
is the place where the string is to be copied from.
strcmp()
This function compares two strings and returns a value which indicates how
they compared. An example:
int value;
char *s1,*s2;
value = strcmp(s1,s2);
The value returned is 0 if the two strings were identical. If the strings were not
the same, this function indicates the (ASCII) alphabetical order of the two. s1
> s2, alphabetically, then the value is > 0. If s1 < s2 then the value is < 0.
Note that numbers come before letters in the ASCII code sequence and also
that upper case comes before lower case.
strstr()
Tests whether a substring is present in a larger string
int n;
char *s1,*s2;
if (n = strstr(s1,s2))
{
printf("s2 is a substring of s1, starting at %d",n);
}
strncpy()
This function is like strcpy, but limits the copy to no more than n characters.
strncmp()
This function is like strcmp, but limits the comparison to no more than n
characters.
More string functions are described in the next section along with a host of Standard
Library Functions.
The user is prompted for a string which he/she types into a buffer. The length of the
string is tested with strlen() and a block of memory is allocated for it using
malloc(). (Notice that this block of memory is one byte longer than the value
returned by strlen(), because strlen() does not count the end of string marker \0.)
malloc() returns a pointer to the space allocated, which is then stored in the array
called array. Finally the strings is copied from the buffer to the new storage with the
library function strcpy(). This process is repeated for each of the 10 strings. Notice
that the program exits through a low level function called QuitSafely(). The reason
for doing this is to exit from the program neatly, while at the same time remembering
to perform all a programmer's duties, such as de-allocating the memory which is no
longer needed. QuitSafely() uses the function exit() which should be provided as
a standard library function. exit() allows a program to end at any point.
/******************************************************/
/* */
/* String storage allocation */
/* */
/******************************************************/
#include <stdio.h>
#define NOOFSTR 10
#define BUFSIZE 255
#define CODE 0
/******************************************************/
/* Level 0 */
/******************************************************/
main ()
array[i] = malloc(strlen(buffer)+1);
if (array[i] == NULL)
{
printf ("Can't allocate memory\n");
QuitSafely (array);
}
strcpy (array[i],buffer);
}
QuitSafely(array);
}
/******************************************************/
/* Snakes & Ladders! */
/******************************************************/
char *array[NOOFSTR];
{ int i, len;
exit (CODE);
}
/* end */
String Input/Output
Because strings are recognized to be special objects in C, some special library
functions for reading and writing are provided for them. These make it easier to deal
with strings, without the need for special user-routines. There are four of these
functions:
gets()
puts()
sprintf()
sscanf()
• gets():
• puts():
• sprintf():
• sscanf():
This function fetches a string from the standard input file stdin and places it into some
buffer which the programmer must provide.
strptr = gets(buffer);
If the routine is successful in getting a string, it returns the value buffer to the string
pointer strptr. Otherwise it returns NULL (==0). The advantage of gets() over
scanf("%s"..) is that it will read spaces in strings, whereas scanf() usually will
not. gets() quits reading when it finds a newline character: that is, when the user
presses RETURN.
NOTE: there are valid concerns about using this function. Often it is implemented as
a macro with poor bounds checking and can be exploited to produce memory
corruption by system attackers. In order to write more secure code, use fgets()
instead.
puts()
puts() sends a string to the output file stdout, until it finds a NULL end of string
marker. The NULL byte is not written to stdout, instead a newline character is written.
char *string;
int returncode;
returncode = puts(string);
puts() returns an integer value, whose value is only guaranteed if there is an error.
returncode == EOF if an end of file was encountered or there was an error.
sprintf()
This is an interesting function which works in almost the same way as printf(), the
exception being that it prints to a string! In other words it treats a string as though it
were an output file. This is useful for creating formatted strings in the memory. On
most systems it works in the following way:
int n;
char *sp;
n = strlen(sprintf(parameters......));
sscanf()
This function is the complement of sprintf(). It reads its input from a string, as
though it were an input file.
int n;
char *sp;
sp is a pointer to the string which is to be read from. The string must be NULL
terminated (it must have a zero-byte end marker '\0'). sscanf() returns an integer
value which holds the number of items successfully matched or EOF if an end of file
marker was read or an error occurred. The conversion specifiers are identical to those
for scanf().
Example Listing
/************************************************/
/* */
/* Formatted strings */
/* */
/************************************************/
#include <stdio.h>
#define SIZE 20
#define CODE 0
/************************************************/
main ()
if (n > SIZE)
{
printf ("Error: string overflowed!\n");
exit (CODE);
}
puts (s2);
}
Questions
1. What are the two main ways of declaring strings in a program?
2. How would you declare a static array of strings?
3. Write a program which gets a number between 0 and 9 and prints out a
different message for each number. Use a pre-initialized array to store the
strings.
ls -l /etc
cc -o program prog.c
In these examples, the first word is the command itself, while the subsequent words
are options and arguments to the command. We need some way getting this
information into a C program. Unix solved this problem by passing C programs an
array of these arguments together with their number as parameters to the function
main(). Since then most other operating systems have adopted the same model, since
it has become a part of the C language.
main (argc,argv)
int argc;
char *argv[];
The traditional names for the parameters are the argument count argc and the
argument vector (array) argv. The operating system call which starts the C program
breaks up the command line into an array, where the first element argv[0] is the
name of the command itself and the last argument argv[argc-1] is the last argument.
For example, in the case of
cc -o program prog.c
argv[0]
cc
argv[1]
-o
argv[2]
program
argv[3]
prog.c
The following program prints out the command line arguments:
main (argc,argv)
int argc;
char *argv[];
{ int i;
if (argc > 1)
{
for (i = 1; i < argc; i++)
{
printf("argv[%d] = %s\n",i,argv[i]);
}
}
else
{
printf("Command has no arguments\n");
}
}
Processing options
getopt
Environment variables
When we write a C program which reads command line arguments, they are fed to us
by the argument vector. Unix processes also a set of text variable associations called
environment variables. Each child process inherits the environment of its parent. The
static environment variables are stored in a special array which is also passed to
main() and can be read if desired.
main (argc,argv,envp)
int argc;
char *argv[], *envp[];
The array of strings envp[] is a list of values of the environment variables of the
system, formatted by
NAME=value
char *string;
string = getenv("HOME");
string is now a pointer to static but public data. You should not use string as if it
were you're own property because it will be used again by the system. Copy it's
contents to another string before using the data.
char buffer[500];
strcpy (buffer,string);
Node:Special Library Functions and Macros, Next:Hidden Operators,
Previous:Putting together a program, Up:Top
A program generally has to #include special header files in order to use special
functions in libraries. The names of the appropriate files can be found in particular
compiler manuals. In the examples above the names of the header files are given in
parentheses.
• Character Identification:
• Example 24:
• Output 24:
• String Manipulation:
• Example 25:
• Mathematical Functions:
• Examples 26:
• Maths Errors:
• Example 27:
• Questions 21:
Character Identification
Some or all of the following functions/macros will be available for identifying and
classifying single characters. The programmer ought to beware that it would be
natural for many of these facilities to exist as macros rather than functions, so the
usual remarks about macro parameters apply, See Preprocessor. An example of their
use is given above. Assume that `true' has any non-zero, integer value and that `false'
has the integer value zero. ch stands for some character, or char type variable.
isalpha(ch)
This returns true if ch is alphabetic and false otherwise. Alphabetic means a..z
or A..Z.
isupper(ch)
Returns true if the character was upper case. If ch was not an alphabetic
character, this returns false.
islower(ch)
Returns true if the character was lower case. If ch was not an alphabetic
character, this returns false.
isdigit(ch)
Returns true if the character was a digit in the range 0..9.
isxdigit(ch)
Returns true if the character was a valid hexadecimal digit: that is, a number
from 0..9 or a letter a..f or A..F.
isspace(ch)
Returns true if the character was a white space character, that is: a space, a TAB
character or a newline.
ispunct(ch)
Returns true if ch is a punctuation character.
isalnum(ch)
Returns true if a character is alphanumeric: that is, alphabetic or digit.
isprint(ch)
Returns true if the character is printable: that is, the character is not a control
character.
isgraph(ch)
Returns true if the character is graphic. i.e. if the character is printable
(excluding the space)
iscntrl(ch)
Returns true if the character is a control character. i.e. ASCII values 0 to 31
and 127.
isascii(ch)
Returns true if the character is a valid ASCII character: that is, it has a code in
the range 0..127.
iscsym(ch)
Returns true if the character was a character which could be used in a C
identifier.
toupper(ch)
This converts the character ch into its upper case counterpart. This does not
affect characters which are already upper case, or characters which do not
have a particular case, such as digits.
tolower(ch)
This converts a character into its lower case counterpart. It does not affect
characters which are already lower case.
toascii(ch)
This strips off bit 7 of a character so that it is in the range 0..127: that is, a
valid ASCII character.
Node:Example 24, Next:Output 24, Previous:Character Identification, Up:Special
Library Functions and Macros
Examples
/********************************************************/
/* */
/* Demonstration of character utility functions */
/* */
/********************************************************/
#include <stdio.h>
#include <ctype.h> /* contains character utilities */
/********************************************************/
{ char ch;
for (ALLCHARS)
{
if (isalpha(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (isupper(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (islower(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (isdigit(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (isxdigit(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (ispunct(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (isalnum(ch))
{
printf ("%c ",ch);
}
}
for (ALLCHARS)
{
if (iscsym(ch))
{
printf ("%c ",ch);
}
}
}
Program Output
VALID CHARACTERS FROM isalpha()
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i
j
k l m n o p q r s t u v w x y z
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 A B C D E F a b c d e f
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W
X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z
0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W
X Y Z _ a b c d e f g h i j k l m n o p q r s t u v w x y z
String Manipulation
The following functions perform useful functions for string handling, See Strings.
strcat()
This function "concatenates" two strings: that is, it joins them together into
one string. The effect of:
char *new,*this, onto[255];
new = strcat(onto,this);
is to join the string this onto the string onto. new is a pointer to the complete
string; it is identical to onto. Memory is assumed to have been allocated for
the starting strings. The string which is to be copied to must be large enough to
accept the new string, tagged onto the end. If it is not then unpredictable
effects will result. (In some programs the user might get away without
declaring enough space for the "onto" string, but in general the results will be
garbage, or even a crashed machine.) To join two static strings together, the
following code is required:
main ()
{ char buffer[255];
strcat(buffer,s1);
strcat(buffer,s2);
}
buffer would then contain "string onestring two".
strlen()
This function returns a type int value, which gives the length or number of
characters in a string, not including the NULL byte end marker. An example is:
int len;
char *string;
len = strlen (string);
strcpy()
This function copies a string from one place to another. Use this function in
preference to custom routines: it is set up to handle any peculiarities in the
way data are stored. An example is
char *to,*from;
to = strcpy (to,from);
Where to is a pointer to the place to which the string is to be copied and from
is the place where the string is to be copied from.
strcmp()
This function compares two strings and returns a value which indicates how
they compared. An example:
int value;
char *s1,*s2;
value = strcmp(s1,s2);
The value returned is 0 if the two strings were identical. If the strings were not
the same, this function indicates the (ASCII) alphabetical order of the two. s1
> s2, alphabetically, then the value is > 0. If s1 < s2 then the value is < 0.
Note that numbers come before letters in the ASCII code sequence and also
that upper case comes before lower case.
There are also variations on the theme of the functions above which begin with strn
instead of str. These enable the programmer to perform the same actions with the
first n characters of a string:
strncat()
This function concatenates two strings by copying the first n characters of
this to the end of the onto string.
char *onto,*new,*this;
new = strncat(onto,this,n);
strncpy()
This function copies the first n characters of a string from one place to another
char *to,*from;
int n;
to = strncpy (to,from,n);
strncmp()
This function compares the first n characters of two strings
int value;
char *s1,*s2;
value = strcmp(s1,s2,n);
atof()
ASCII to floating point conversion.
double x;
char *stringptr;
x = atof(stringptr);
atoi()
ASCII to integer conversion.
int i;
char *stringptr;
i = atoi(stringptr);
atol()
ASCII to long integer conversion.
long i;
char *stringptr;
i = atol(stringptr);
Examples
/********************************************************/
/* */
/* String comparison */
/* */
/********************************************************/
#include <stdio.h>
#define TRUE 1
#define MAXLEN 30
/********************************************************/
main ()
{ char string1[MAXLEN],string2[MAXLEN];
int result;
while (TRUE)
{
printf ("Type in string 1:\n\n");
scanf ("%30s",string1);
if (result == 0)
{
printf ("Those strings were the same!\n");
}
if (result > 0)
{
printf ("string1 > string2\n");
}
if (result < 0)
{
printf ("string1 < string 2\n");
}
}
}
Mathematical Functions
C has a library of standard mathematical functions which can be accessed by
#including the appropriate header files (math.h etc.). It should be noted that all of
these functions work with double or long float type variables. All of C's
mathematical capabilities are written for long variable types. Here is a list of the
functions which can be expected in the standard library file. The variables used are all
to be declared long
The functions themselves must be declared long float or double (which might be done
automatically in the mathematics library file, or in a separate file) and any constants
must be written in floating point form: for instance, write 7.0 instead of just 7.
ABS()
MACRO. Returns the unsigned value of the value in parentheses. See fabs()
for a function version.
fabs()
Find the absolute or unsigned value of the value in parentheses:
result = fabs(x);
ceil()
Find out what the ceiling integer is: that is, the integer which is just above the
value in parentheses. This is like rounding up.
i = ceil(x);
/* ceil (2.2) is 3 */
floor()
Find out what the floor integer is: that is, the integer which is just below the
floating point value in parentheses
i = floor(x);
/* floor(2.2) is 2 */
exp()
Find the exponential value.
result = exp(x);
result = exp(2.7);
log()
Find the natural (Naperian) logarithm. The value used in the parentheses must
be unsigned: that is, it must be greater than zero. It does not have to be
declared specifically as unsigned. e.g.
result = log(x);
result = log(2.71828);
log10()
Find the base 10 logarithm. The value used in the parentheses must be
unsigned: that is, it must be greater than zero. It does not have to be declared
specifically as unsigned.
result = log10(x);
result = log10(10000);
pow()
Raise a number to the power.
result = pow(x,y); /*raise x to the power y */
result = pow(x,2); /*find x-squared */
sqrt()
Find the square root of a number.
result = sqrt(x);
result = sqrt(2.0);
sin()
Find the sine of the angle in radians.
result = sin(x);
result = sin(3.14);
cos()
Find the cosine of the angle in radians.
result = cos(x);
result = cos(3.14);
tan()
Find the tangent of the angle in radians.
result = tan(x);
result = tan(3.14);
asin()
Find the arcsine or inverse sine of the value which must lie between +1.0 and
-1.0.
result = asin(x);
result = asin(1.0);
acos()
Find the arccosine or inverse cosine of the value which must lie between +1.0
and -1.0.
result = acos(x);
result = acos(1.0);
atan()
Find the arctangent or inverse tangent of the value.
result = atan(x);
result = atan(200.0);
atan2()
This is a special inverse tangent function for calculating the inverse tangent of
x divided by y. This function is set up to find this result more accurately than
atan().
result = atan2(x,y);
result = atan2(x/3.14);
sinh()
Find the hyperbolic sine of the value. (Pronounced "shine" or "sinch")
result = sinh(x);
result = sinh(5.0);
cosh()
Find the hyperbolic cosine of the value.
result = cosh(x);
result = cosh(5.0);
tanh()
Find the hyperbolic tangent of the value.
result = tanh(x);
result = tanh(5.0);
Examples
/******************************************************/
/* */
/* Maths functions demo #1 */
/* */
/******************************************************/
#include <stdio.h>
#include <math.h>
#include <limits.h>
#define TRUE 1
#define AMPLITUDE 30
#define INC 0.02
/******************************************************/
/* Level 0 */
/******************************************************/
Pendulum();
}
/*****************************************************/
/* Level 1 */
/*****************************************************/
Pendulum ()
{ double x, twopi = pi * 2;
int i,position;
while (true)
{
for (x = 0; x < twopi; x += INC)
{
position = (int)(AMPLITUDE * sin(x));
/*****************************************************/
/* Toolkit */
/*****************************************************/
startofline()
{
putchar('\r');
}
Maths Errors
Mathematical functions can be delicate animals. There exist mathematical functions
which simply cannot produce sensible answers in all possible cases. Mathematical
functions are not "user friendly"! One example of an unfriendly function is the inverse
sine function asin(x) which only works for values of x in the range +1.0 to -1.0. The
reason for this is a mathematical one: namely that the sine function (of which asin()
is the opposite) only has values in this range. The statement
y = asin (25.3);
is nonsense and it cannot possibly produce a value for y, because none exists.
Similarly, there is no simple number which is the square root of a negative value, so
an expression such as:
x = sqrt(-2.0);
would also be nonsense. This doesn't stop the programmer from writing these
statements though and it doesn't stop a faulty program from straying out of bounds.
What happens then when an erroneous statement is executed? Some sort of error
condition would certainly have to result.
In many languages, errors, like the ones above, are terminal: they cause a program to
stop without any option to recover the damage. In C, as the reader might have come to
expect, this is not the case. It is possible (in principle) to recover from any error,
whilst still maintaining firm control of a program.
Errors like the ones above are called domain errors (the set of values which a function
can accept is called the domain of the function). There are other errors which can
occur too. For example, division by zero is illegal, because dividing by zero is
"mathematical nonsense" - it can be done, but the answer can be all the numbers
which exist at the same time! Obviously a program cannot work with any idea as
vague as this. Finally, in addition to these "pathological" cases, mathematical
operations can fail just because the numbers they deal with get too large for the
computer to handle, or too small, as the case may be.
Domain error
Illegal value put into function
Division by zero
Dividing by zero is nonsense.
Overflow
Number became too large
Underflow
Number became too small.
Loss of accuracy
No meaningful answer could be calculated
Although it is not possible to generalize, the following remarks about the behaviour of
mathematical functions may help to avoid any surprises about their behaviour in error
conditions.
• A function which fails to produce a sensible answer, for any of the reasons
above, might simply return zero or it might return the maximum value of the
computer. Be careful to check this. (Division by zero and underflow probably
return zero, whereas overflow returns the maximum value which the computer
can handle.)
• Some functions return the value NaN. Not a form of Indian unleavened bread,
this stands for `Not a Number', i.e. no sensible result could be calculated.
• Some method of signalling errors must clearly be used. This is the exception
structure (a special kind of C variable) which gives information about the last
error which occurred. Find out what it is and trap errors!
• Obviously, wherever possible, the programmer should try to stop errors from
occurring in the first place.
Node:Example 27, Next:Questions 21, Previous:Maths Errors, Up:Special Library
Functions and Macros
Example
Here is an example for the mathematically minded. The program below performs
numerical integration by the simplest possible method of adding up the area under
small strips of a graph of the function f(y) = 2*y. The integral is found between the
limits 0 and 5 and the exact answer is 25. (See diagram.) The particular compiler used
for this program returns the largest number which can be represented by the computer
when numbers overflow, although, in this simple case, it is impossible for the
numbers to overflow.
/**********************************************************/
/* */
/* Numerical Estimation of Integral */
/* */
/**********************************************************/
#include <stdio.h>
#include <math.h>
#include <limits.h>
#define LIMIT 5
/***********************************************************/
/** LEVEL 0 */
/***********************************************************/
main ()
{ double y,integrand();
double integral = 0;
twopi = 4 * asin(1.0);
/***************************************************************/
/** LEVEL 1 **/
/***************************************************************/
double y;
{ double value;
value = 2*y;
if (value > 1e308)
{
printf ("Overflow error\n");
exit (0);
}
return (value);
}
Questions
1. What type of data is returned from mathematical functions?
2. All calculations are performed using long variables. True or false?
3. What information is returned by strlen()?
4. What action is performed by strcat()?
5. Name five kinds of error which can occur in a mathematical function.
Concise expressions
Many operators in C are more versatile than they appear to be, at first glance. Take,
for example, the following operators
= ++ -- += -= etc...
The first thing to notice is that ++ and -- are unary operators: that is, they are applied
to a single variable and they affect that variable alone. They therefore produce one
unique value each time they are used. The assignment operator, on the other hand, has
the unusual position of being both unary, in the sense that it works out only one
expression, and also binary or dyadic because it sits between two separate objects: an
"lvalue" on the left hand side and an expression on the right hand side. Both kinds of
operator have one thing in common however: both form statements which have values
in their own right. What does this mean? It means that certain kinds of statement, in
C, do not have to be thought of as being complete and sealed off from the rest of a
program. To paraphrase a famous author: "In C, no statement is an island". A
statement can be taken as a whole (as a "black box") and can be treated as a single
value, which can be assigned and compared to things! The value of a statement is the
result of the operation which was carried out in the statement.
c = 5;
c++;
The second of these statement c++; has the value 6, and similarly:
c = 5;
c--;
The second of these statements c--; has the value 4. Entire assignment statements
have values too. A statement such as:
c = 5;
has the value which is the value of the assignment. So the example above has the
value 5. This has some important implications.
b = (c = 0);
or simply:
b = c = 0;
These equivalent statements set b and c to the value zero, provided b and c are of the
same type! It is equivalent to the more usual:
b = 0;
c = 0;
or simply:
a = b = c = d = e = 5;
This very neat syntax compresses five lines of code into one single line! There are
other uses for the valued assignment statement, of course: it can be used anywhere
where a value can be used. For instance:
The uses are manifold. Consider how an assignment statement might be used as a
parameter to a function. The function below gets a character from the input stream
stdin and passes it to a function called ProcessCharacter():
ch = getchar();
ProcessCharacter (ch);
The two methods are entirely equivalent. If there is any doubt, examine a little more
of this imaginary character processing program:
ProcessCharacter(ch = getchar());
if (ch == '*')
{
printf ("Starry, Starry Night...");
}
The purpose in adding the second statement is to impress the fact that ch has been
assigned quite legitimately and it is still defined in the next statement and the one
after...until it is re-assigned by a new assignment statement. The fact that the
assignment was hidden inside another statement does not make it any less valid. All
the same remarks apply about the specialized assignment operators +=, *=, /= etc..
Example
/************************************************/
/* */
/* Hidden Assignment #1 */
/* */
/************************************************/
main ()
{
do
{
switch (ch = getchar())
{
default : putchar(ch);
break;
case 'Q' : /* Quit */
}
}
while (ch != 'Q');
}
/* end */
/************************************************/
/* */
/* Hidden Assignment #2 */
/* */
/************************************************/
main ()
{ double x = 0;
/* end */
Postfix Prefix
var++ ++var
var-- --var
and these two forms have subtly different meanings. Look at the following example:
int i = 3;
PrintNumber (i++);
If the operator is used as a prefix, the operation is performed before the function
call. If the operator is used as a postfix, the operation is performed after the
function call.
In the example above, then, the value 3 is passed to the function and when the
function returns, the value of i is incremented to 4. The alternative is to write:
int i = 3;
PrintNumber (++i);
in which case the value 4 is passed to the function PrintNumber(). The same remarks
apply to the decrement operator.
#define SIZE 20
int i, array[SIZE];
This is a neat way of initializing an array to zero. Notice that the postfixed form of the
increment operator is used. This prevents the element array[0] from assigning zero
to memory which is out of the bounds of the array.
Strings too can benefit from hidden operators. If the standard library function
strlen() (which finds the length of a string) were not available, then it would be a
simple matter to write the function
char *string;
{ char *ptr;
int count = 0;
return (count);
}
This function increments count while the end of string marker \0 is not found.
Example
/*********************************************************/
/* */
/* Hidden Operator Demo */
/* */
/*********************************************************/
#include <stdio.h>
/*********************************************************/
main ()
{ int a,b,c,d,e;
a = (b = (c = (d = (e = 0))));
a = b = c = d = e = 0;
/* end */
/*******************************************************/
/* */
/* Hidden Operator demo #2 */
/* */
/*******************************************************/
#include <stdio.h>
/*******************************************************/
{
printf ("%d",Value());
}
/*******************************************************/
{ int value;
if ((value = GetValue()) == 0)
{
printf ("Value was zero\n");
}
return (value);
}
/********************************************************/
{
return (0);
}
/* end */
are not recommendable programming style and they are no more efficient than the
more longwinded:
ch++;
i = (int)ch;
if (i <= comparison)
{
}
comparison--;
There is always a happy medium in which to settle on a readable version of the code.
The statement above might perhaps be written as:
i = (int) ch++;
if (i <= --comparison)
{
}
Example
/******************************************************/
/* */
/* Arrays and Hidden Operators */
/* */
/******************************************************/
#include <stdio.h>
#define SIZE 10
/******************************************************/
/* Level 0 */
/******************************************************/
Initialize(array);
i = 4;
array[i++] = 8;
Print (array);
Initialize(array);
i = 4;
array[++i] = 8;
Print(array);
}
/*******************************************************/
/* Level 1 */
/*******************************************************/
int array[SIZE];
{ int i;
/******************************************************/
int array[SIZE];
{ int i = 0;
putchar ('\n');
}
/* end */
/****************************************************/
/* */
/* Hidden Operator */
/* */
/****************************************************/
#include <stdio.h>
#define MAXNO 20
/*****************************************************/
Questions
1. Which operators can be hidden inside other statements?
2. Give a reason why you would not want to do this in every possible case.
3. Hidden operators can be used in return statements .e.g
4. return (++x);
5.
return (x++);
FILE
The type which files are classified under
enum
Enumerated type for abstract data
void
The "empty" type
volatile
New ANSI standard type for memory mapped I/O
const
New ANSI standard type for fixed data
struct
Groups of variables under a single name
union
Multi-purpose storage areas for dynamical memory allocation
Up to now the distinction between long and short integer types has largely been
ignored. Constant values can be declared explicitly as long values, in fact, by placing
the letter L after the constant.
variable = 236526598L;
For example:
This kind of notation has already been applied to strings and single character
constants with the backslash notation, instead of the leading zero character:
ch = '\ddd';
ch = '\xdd';
The values of character constants, like these, cannot be any greater than 255.
Node:FILE, Next:enum, Previous:Special Constant Expressions, Up:More on Data
Types
FILE
In all previous sections, the files stdin, stdout and stderr alone have been used in
programs. These special files are always handled implicitly by functions like
printf() and scanf(): the programmer never gets to know that they are, in fact,
files. Programs do not have to use these functions however: standard input/output files
can be treated explicitly by general file handling functions just as well. Files are
distinguished by filenames and by file pointers. File pointers are variables which pass
the location of files to file handling functions; being variables, they have to be
declared as being some data type. That type is called FILE and file pointers have to be
declared "pointer to FILE". For example:
FILE *fp;
FILE *fopen();
File handling functions which return file pointers must also be declared as pointers to
files. Notice that, in contrast to all the other reserved words FILE is written in upper
case: the reason for this is that FILE is not a simple data type such as char or int, but
a structure which is only defined by the header file stdio.h and so, strictly speaking,
it is not a reserved word itself. We shall return to look more closely at files soon.
enum
Abstract data are usually the realm of exclusively high level languages such as Pascal.
enum is a way of incorporating limited "high level" data facilities into C.
enum is short for enumerated data. The user defines a type of data which is made up of
a fixed set of words, instead of numbers or characters. These words are given
substitute integer numbers by the compiler which are used to identify and compare
enum type data. For example:
enum countries
{
England,
Scotland,
Wales,
Eire,
Norge,
Sverige,
Danmark,
Deutschland
};
main ()
variable = England;
}
Why go to all this trouble? The point about enumerated data is that they allow the
programmer to forget about any numbers which the computer might need in order to
deal with a list of words, like the ones above, and simply concentrate on the logic of
using them. Enumerated data are called abstract because the low level number form of
the words is removed from the users attention. In fact, enumerated data are made up
of integer constants, which the compiler generates itself. For this reason, they have a
natural partner in programs: the switch statement. Here is an example, which uses the
countries above to make a kind of airport "help computer" in age of electronic
passports!
Example
/**********************************************************/
/* */
/* Enumerated Data */
/* */
/**********************************************************/
#include <stdio.h>
enum countries
{
England,
Ireland,
Scotland,
Wales,
Danmark,
Island,
Norge,
Sverige
};
/**********************************************************/
switch (birthplace)
{
case England : printf ("Welcome home!\n");
break;
case Danmark :
case Norge : printf ("Velkommen til England\n");
break;
}
}
/************************************************************/
{
return (England);
}
/* end */
enum makes words into constant integer values for a programmer. Data which are
declared enum are not the kind of data which it makes sense to do arithmetic with
(even integer arithmetic), so in most cases it should not be necessary to know or even
care about what numbers the compiler gives to the words in the list. However, some
compilers allow the programmer to force particular values on words. The compiler
then tries to give the values successive integer numbers unless the programmer states
otherwise. For instance:
enum planets
{
Mercury,
Venus,
Earth = 12,
Mars,
Jupiter,
Saturn,
Uranus,
Neptune,
Pluto
};
This would probably yield values Mercury = 0, Venus = 1, Earth = 12, Mars =
13, Jupiter = 14 ... etc. If the user tries to force a value which the compiler has
already used then the compiler will complain.
Example
/**********************************************************/
/* */
/* Enumerated Data */
/* */
/**********************************************************/
#include <stdio.h>
#define TRUE 1
#define FALSE 0
/***********************************************************/
/* Level 0 */
/***********************************************************/
if (getobject(object))
{
printf ("Congratulations you've found the gold!\n");
}
else
{
printf ("Too bad -- you just missed your big chance");
}
}
/***********************************************************/
/* Level 1 */
/***********************************************************/
{ enum answer
{
no = false,
yes = true
};
if (ob == gold)
{
printf ("Pick up object? Y/N\n");
switch (getchar())
{
case 'y' :
case 'Y' : return ((int) yes); /* true and false */
default : return ((int) no); /* are integers */
}
}
else
{
printf ("You grapple with the dirt\n");
return (false);
}
}
/* end */
enum numbers
{
zero,
one,
two,
three
};
enum animals
{
cat,
dog,
cow,
sheep,
};
enum plants
{
grass,
roses,
cabbages,
oaktree
};
enum diseases
{
heart,
skin,
malnutrition,
circulatory
};
enum quarks
{
up,
down,
charmed,
strange,
top,
bottom,
truth,
beauty
};
void
void is a peculiar data type which has some debatable uses. The void datatypes was
introduced in order to make C syntactically consistent. The main idea of void is to be
able to declare functions which have no return value. The word `void' is intended in
the meaning `empty' rather than `invalid'. If you recall, the default is for C functions
to return a value of type int. The value returned by a function did not have to be
specified could always be discarded, so this was not a problem in practice. It did make
compiler checks more difficult however: how do you warn someone about
inconsistent return values if it is legal to ignore return values?
The ANSI solution was to introduce a new data type which was called void for
functions with no value. The word void is perhaps an unfortunate choice, since it has
several implicit meanings none of which really express what is intended. The words
`novalue' or `notype' would have been better choices. A variable or function can be
declared void in the following ways.
void function();
void variable;
void *ptr;
(void) returnvalue();
or
(void) scanf("%c",&ch);
Few programmers would do this since it merely clutters up programs with
irrelevant verbiage.
• A void pointer can point to to any kind of object. This means that any pointer
can be assigned to a void pointer, regardless of its type. This is also a highly
questionable feature of the ANSI draft. It replaces the meaning of void from
`no type or value' to `no particular type'. It allows assignments between
incompatible pointer types without a cast operator. This is also rather dubious.
volatile
volatile is a type which has been proposed in the ANSI standard. The idea behind
this type is to allow memory mapped input/output to be held in C variables. Variables
which are declared volatile will be able to have their values altered in ways which a
program does not explicitly define: that is, by external influences such as clocks,
external ports, hardware, interrupts etc...
The volatile datatype has found another use since the arrival of multiprocessor,
multithreaded operating systems. Independent processes which share common
memory could each change a variable independently. In other words, in a
multithreaded environment the value of a variable set by one process in shared
memory might be altered by another process without its knowledge. The keyword
volatile servers as a warning to the compiler that any optimizing code it produces
should not rely on caching the value of the variable, it should always reread its value.
const
The reserved word const is used to declare data which can only be assigned once,
either because they are in ROM (for example) or because they are data whose values
must not be corrupted. Types declared const must be assigned when they are first
initialized and they exist as stored values only at compile time:
Since a constant array only exists at compile time, it can be initialized by the
compiler.
It is worth comparing the const declaration to enumerated data, since they are
connected in a very simple way. The following two sets of of statements are the same:
enum numbers
{
zero,
one,
two,
three,
four
};
and
const zero = 0;
const one = 1;
const two = 2;
const three = 3;
const four = 4;
Constant types and enumerated data are therefore just different aspects of the same
thing. Enumerated data provide a convenient way of classifying constants, however,
while the compiler keeps track of the values and types. With const you have to keep
track of constant values personally.
struct
Structures are called records in Pascal and many other languages. They are packages
of variables which are all wrapped up under a single name. Structures are described in
detail in chapter 25.
union
Unions are often grouped together with structures, but they are quite unlike them in
almost all respects. They are like general purpose storage containers, which can hold a
variety of different variable types, at different times. The compiler makes a container
which is large enough to take any of these, See Structures and Unions.
typedef
C allows us to define our own data types or to rename existing ones by using a
compiler directive called typedef. This statement is used as follows:
typedef type newtypename;
So, for example, we could define a type called byte, which was exactly one byte in
size by redefining the word char:
The compiler type checking facilities then treat byte as a new type which can be used
to declare variables:
The typedef statement may be written inside functions or in the global white space of
a program.
/**************************************************/
/* Program */
/**************************************************/
main ()
{
typedef char newname2;
}
This program will compile and run (though it will not do very much).
It is not very often that you want to rename existing types in the way shown above.
The most important use for typedef is in conjunction with structures and unions.
Structures and unions can, by their very definition, be all kinds of shape and size and
their names can become long and tedious to declare. typedef makes dealing with these
simple because it means that the user can define a structure or union with a simple
typename.
Questions
1. Is FILE a reserved word? If so why is it in upper case?
2. Write a statement which declares a file pointer called fp.
3. Enumerated data are given values by the compiler so that it can do arithmetic
with them. True or false?
4. Does void do anything which C cannot already do without this type?
5. What type might a timer device be declared if it were to be called by a variable
name?
6. Write a statement which declares a new type "real" to be like the usual type
"double".
7. Variables declared const can be of any type. True or false?
Down in the depths of your computer, below even the operating system are bits of
memory. These days we are used to working at such a high level that it is easy to
forget them. Bits (or binary digits) are the lowest level software objects in a computer:
there is nothing more primitive. For precisely this reason, it is rare for high level
languages to even acknowledge the existence of bits, let alone manipulate them.
Manipulating bit patterns is usually the preserve of assembly language programmers.
C, however, is quite different from most other high level languages in that it allows a
programmer full access to bits and even provides high level operators for
manipulating them.
Since this book is an introductory text, we shall treat bit operations only superficially.
Many of the facilities which are available for bit operations need not concern the
majority of programs at all. This section concerns the main uses of bit operations for
high level programs and it assumes a certain amount of knowledge about
programming at the low level. You may wish to consult a book on assembly language
programming to learn about low level memory operations, in more detail.
• Bit Patterns:
• Flags registers:
• Bit Operators and Assignments:
• Bit operators:
• Shift Operations:
• Truth Tables and Masking:
• Example 33:
• Output 33:
• Example 34:
• Example 35:
• Questions 24:
Bit Patterns
All computer data, of any type, are bit patterns. The only difference between a string
and a floating point variable is the way in which we choose to interpret the patterns of
bits in a computer's memory. For the most part, it is quite unnecessary to think of
computer data as bit patterns; systems programmers, on the other hand, frequently
find that they need to handle bits directly in order to make efficient use of memory
when using flags. A flag is a message which is either one thing or the other: in system
terms, the flag is said to be `on' or `off' or alternatively set or cleared. The usual place
to find flags is in a status register of a CPU (central processor unit) or in a pseudo-
register (this is a status register for an imaginary processor, which is held in memory).
A status register is a group of bits (a byte perhaps) in which each bit signifies
something special. In an ordinary byte of data, bits are grouped together and are
interpreted to have a collective meaning; in a status register they are thought of as
being independent. Programmers are interested to know about the contents of bits in
these registers, perhaps to find out what happened in a program after some special
operation is carried out. Other uses for bit patterns are listed below here:
Messages are just like pseudo status registers: they are collections of flags which
signal special information between different devices and/or different programs in a
computer system. Messages do not necessarily have fixed locations: they may be
passed a parameters. Messages are a very compact way of passing information to low
level functions in a program. Flags, registers, pseudo-registers and messages are all
treated as bit patterns. A program which makes use of them must therefore be able to
assign these objects to C variables for use. A bit pattern would normally be declared
as a character or some kind of integer type in C, perhaps with the aid of a typedef
statement.
bitpattern variable;
byte message;
<<
Bit shift left (a specified number or bit positions)
>>
Bit shift right(a specified number of bit positions)
|
Bitwise Inclusive OR
^
Bitwise Exclusive OR
&
Bitwise AND
~
Bitwise one's complement
&=
AND assign (variable = variable & value)
|=
Exclusive OR assign (variable = variable | value)
^=
Inclusive OR assign (variable = variable ^ value)
>>=
Shift right assign (variable = variable >> value)
<<=
Shift left assign (variable = variable << value)
Below is a brief summary of the operations which are performed by the above
operators on the bits of their operands.
Shift Operations
Imagine a bit pattern as being represented by the following group of boxes. Every box
represents a bit; the numbers inside represent their values. The values written over the
top are the common integer values which the whole group of bits would have, if they
were interpreted collectively as an integer.
128 64 32 16 8 4 2 1
-------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | = 1
-------------------------------
Shift operators move whole bit patterns left or right by shunting them between boxes.
The syntax of this operation is:
1 << 1
would have the value 2, because the bit pattern would have been moved one place the
the left:
128 64 32 16 8 4 2 1
-------------------------------
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | = 2
-------------------------------
Similarly:
1 << 4
has the value 16 because the original bit pattern is moved by four places:
128 64 32 16 8 4 2 1
-------------------------------
| 0 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | = 16
-------------------------------
And:
6 << 2 == 12
128 64 32 16 8 4 2 1
-------------------------------
| 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | = 6
-------------------------------
128 64 32 16 8 4 2 1
-------------------------------
| 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | = 12
-------------------------------
Notice that every shift left multiplies by 2 and that every shift right would divide by
two, integerwise. If a bit reaches the edge of the group of boxes then it falls out and is
lost forever. So:
1 >> 1 == 0
2 >> 1 == 1
2 >> 2 == 0
n >> n == 0
A common use of shifting is to scan through the bits of a bitpattern one by one in a
loop: this is done by using masks.
• Complement ~:
• AND:
• OR:
• XOR:
Complement ~
~value == result
0 1
1 0
AND &
0 0 0
0 1 0
1 0 0
1 1 1
OR |
0 0 0
0 1 1
1 0 1
1 1 1
XOR/EOR ^
0 0 0
0 1 1
1 0 1
1 1 0
The result is 1 if one OR the other (but not both) of the values is 1.
Bit patterns and logic operators are often used to make masks. A mask is as a thing
which fits over a bit pattern and modifies the result in order perhaps to single out
particular bits, usually to cover up part of a bit pattern. This is particularly pertinent
for handling flags, where a programmer wishes to know if one particular flag is set or
not set and does not care about the values of the others. This is done by deliberately
inventing a value which only allows the particular flag of interest to have a non-zero
value and then ANDing that value with the flag register. For example: in symbolic
language:
MASK = 00000001
VALUE1 = 10011011
VALUE2 = 10011100
The zeros in the mask masks off the first seven bits and leave only the last one to
reveal its true value. Alternatively, masks can be built up by specifying several flags:
FLAG1 = 00000001
FLAG2 = 00000010
FLAG3 = 00000100
MESSAGE == 00000111
It should be emphasized that these expressions are only written in symbolic language:
it is not possible to use binary values in C. The programmer must convert to
hexadecimal, octal or denary first. (See the appendices for conversion tables).
/***************************************************/
/* */
/* Bit Manipulation #1 */
/* */
/***************************************************/
#include <stdio.h>
#define NUMBEROFBITS 8
/****************************************************/
main ()
{ short i,j,bit,;
short MASK = 0x80;
if (i > 128)
{
printf ("Too big\n");
return (0);
}
printf ("\n");
}
/* end */
Output
Enter any number less than 128: 56
Binary value = 00111000
Example
/***************************************************/
/* */
/* Bit Manipulation #2 */
/* */
/***************************************************/
#include <stdio.h>
#define NUMBEROFBITS 8
/****************************************************/
main ()
{ short j,hex = 0;
short MASK;
char binary[NUMBEROFBITS];
/* end */
Questions
1. What distinguishes a bit pattern from an ordinary variable? Can any variable
be a bit pattern?
2. What is the difference between an inclusive OR operation and an exclusive
OR operation?
3. If you saw the following function call in a program, could you guess what its
parameter was?
4. OpenWindow (BORDER | GADGETS | MOUSECONTROL | SIZING);
5.
6. Find out what the denary (decimal) values of the following operations are:
1. 7 & 2
2. 1 & 1
3. 15 & 3
4. 15 & 7
5. 15 & 7 & 3
Try to explain the results. (Hint: draw out the numbers as binary patterns,
using the program listed.)
7. Find out what the denary (decimal) values of the following operations are:
1. 1 | 2
2. 1 | 2 | 3
8. Find out the values of:
1. 1 & (~1)
2. 23 & (~23)
3. 2012 & (~2012)
(Hint: write a short program to work them out. Use short type variables for
all the numbers).
An operating system allows a program to see files in the outside world by providing a
number of channels or `portals' (`inlets' and `outlets') to work through. In order to
examine the contents of a file or to write information to a file, a program has to open
one of these portals. The reason for this slightly indirect method of working is that
channels/portals hide operating system dependent details of filing from the
programmer. Think of it as a protocol. A program which writes information does no
more than pass that information to one of these portals and the operating system's
filing subsystem does the rest. A program which reads data simply reads values from
its file portal and does not have to worry about how they got there. This is extremely
simple to work in practice. To use a file then, a program has to go through the
following routine:
• Open a file for reading or writing. (Reserve a portal and locate the file on disk
or whatever.)
• Read or write to the file using file handling functions provided by the standard
library.
• Close the file to free the operating system "portal" for use by another program
or file.
A program opens a file by calling a standard library function and is returned a file
pointer, by the operating system, which allows a program to address that particular
file and to distinguish it from all others.
• Files Generally:
• File Positions:
• High Level File Handling Functions:
• Opening files:
• Closing a file:
• fprintf:
• fscanf:
• skipfilegarb?:
• Single Character I/O:
• getc and fgetc:
• ungetc:
• putc and fputc:
• fgets and fputs:
• feof:
• Printer Output:
• Example 36:
• Output 36:
• Converting example:
• File Errors:
• Other Facilities for High Level Files:
• fread() and fwrite():
• ftell and fseek:
• rewind:
• fflush:
• Low Level Filing Operations:
• File Handles:
• open:
• close:
• creat:
• read:
• write:
• lseek:
• unlink remove:
• Example 37:
• Questions 25:
Files Generally
C provides two levels of file handling; these can be called high level and low level.
High level files are all treated as text files. In fact, the data which go into the files are
exactly what would be seen on the screen, character by character, except that they are
stored in a file instead. This is true whether a file is meant to store characters,
integers, floating point types. Any file, which is written to by high level file handling
High level text files are also read back as character files, in the same way that input is
acquired from the keyboard. This all means that high level file functions are identical
in concept to keyboard/screen input/output.
The alternative to these high level functions, is obviously low level functions. These
are more efficient, in principle, at filing data as they can store data in large lumps, in
raw memory format, without converting to text files first. Low level input/output
functions have the disadvantage that they are less `programmer friendly' than the high
level ones, but they are likely to work faster.
• File Positions:
• High Level File Handling Functions:
• Opening files:
• Closing a file:
• fprintf:
• fscanf:
• skipfilegarb?:
• Single Character I/O:
• getc and fgetc:
• ungetc:
• putc and fputc:
• fgets and fputs:
• feof:
• Converting example:
• File Errors:
• Other Facilities for High Level Files:
• fread() and fwrite():
• ftell and fseek:
• rewind:
• fflush:
• Low Level Filing Operations:
• File Handles:
• open:
• close:
• creat:
• read:
• write:
• lseek:
• unlink remove:
File Positions
When data are read from a file, the operating system keeps track of the current
position of a program within that file so that it only needs to make a standard library
call to `read the next part of the file' and the operating system obliges by reading some
more and advancing its position within the file, until it reaches the end. Each single
character which is read causes the position in a file to be advanced by one.
Although the operating system does a great deal of hand holding regarding file
positions, a program can control the way in which that position changes with
functions such as ungetc() if need be. In most cases it is not necessary and it should
be avoided, since complex movements within a file can cause complex movements of
a disk drive mechanism which in turn can lead to wear on disks and the occurrence of
errors.
fprintf()
fscanf()
fgets()
fputs()
These are all generalized file handling versions of the standard input/output library.
They work with generalized files, as opposed to the specific files stdin and stdout
which printf() and scanf() use. The file versions differ only in that they need an
extra piece of information: the file pointer to a particular portal. This is passed as an
extra parameter to the functions. they process data in an identical way to their
standard I/O counterparts. Other filing functions will not look so familiar. For
example:
fopen()
fclose()
getc()
ungetc();
putc()
fgetc()
fputc()
feof()
Before any work can be done with high level files, these functions need to be
explained in some detail.
Opening files
A file is opened by a call to the library function fopen(): this is available
automatically when the library file <stdio.h> is included. There are two stages to
opening a file: firstly a file portal must be found so that a program can access
information from a file at all. Secondly the file must be physically located on a disk or
as a device or whatever. The fopen() function performs both of these services and, if,
in fact, the file it attempts to open does not exist, that file is created anew. The syntax
of the fopen() function is:
FILE *returnpointer;
returnpointer = fopen("filename","mode");
or
FILE returnpointer;
char *fname, *mode;
returnpointer = fopen(fname,mode);
The filename is a string which provides the name of the file to be opened. Filenames
are system dependent so the details of this must be sought from the local operating
system manual. The operation mode is also a string, chosen from one of the
following:
r
Open file for reading
w
Open file for writing
a
Open file for appending
rw
Open file for reading and writing (some systems)
This mode string specifies the way in which the file will be used. Finally,
returnpointer is a pointer to a FILE structure which is the whole object of calling
this function. If the file (which was named) opened successfully when fopen() was
called, returnpointer is a pointer to the file portal. If the file could not be opened, this
pointer is set to the value NULL. This should be tested for, because it would not make
sense to attempt to write to a file which could not be opened or created, for whatever
reason.
A read only file is opened, for example, with some program code such as:
FILE *fp;
{
printf ("File could not be opened\n");
error_handler();
}
A question which springs to mind is: what happens if the user has to type in the name
of a file while the program is running? The solution to this problem is quite simple.
Recall the function filename() which was written in chapter 20.
do
{
printf ("Enter filename :");
scanf ("%24s",filenm);
skipgarb();
}
while (strlen(filenm) == 0);
return (filenm);
}
This function makes file opening simple. The programmer would now write
something like:
FILE *fp;
char *filename();
{
printf ("File could not be opened\n");
error_handler();
}
and then the user of the program would automatically be prompted for a filename.
Once a file has been opened, it can be read from or written to using the other library
functions (such as fprintf() and fscanf()) and then finally the file has to be closed
again.
Closing a file
A file is closed by calling the function fclose(). fclose() has the syntax:
int returncode;
FILE *fp;
if (fclose(fp) != 0)
{
printf ("File did not exist.\n");
error_handler();
}
fprintf()
This is the highest level function which writes to files. Its name is meant to signify
"file-print-formatted" and it is almost identical to its stdout counterpart printf().
The form of the fprintf() statement is as follows:
fprintf (fp,"string",variables);
where fp is a file pointer, string is a control string which is to be formatted and the
variables are those which are to be substituted into the blank fields of the format
string. For example, assume that there is an open file, pointed to by fp:
int i = 12;
float x = 2.356;
char ch = 's';
fprintf (fp, "%d %f %c", i, x, ch);
The conversion specifiers are identical to those for printf(). In fact fprintf() is
related to printf() in a very simple way: the following two statements are identical.
fscanf()
The analogue of scanf() is fscanf() and, as with fprintf(), this function differs
from its standard I/O counterpart only in one extra parameter: a file pointer. The form
of an fscanf() statement is:
FILE *fp;
int n;
n = fscanf (fp,"string",pointers);
where n is the number of items matched in the control string and fp is a pointer to the
file which is to be read from. For example, assuming that fp is a pointer to an open
file:
int i = 10;
float x = -2.356;
char ch = 'x';
The remarks which were made about scanf() also apply to this function: fscanf()
is a `dangerous' function in that it can easily get out of step with the input data unless
the input is properly formatted.
skipfilegarb() ?
Do programs need a function such as skipgarb() to deal with instances of badly
formatted input data? A programmer can assume a bit more about files which are read
into a program from disk file than it can assume about the user's typed input. A disk
file will presumably have been produced by the same program which generated it, or
will be in a format which the program expects. Is a function like skipgarb()
necessary then? The answer is: probably not. This does not mean to say that a
program does not need to check for "bad files", or files which do not contain the data
they are alleged to contain. On the other hand, a programmer is at liberty to assume
that any file which does not contain correctly formatted data is just nonsense: he/she
does not have to try to make sense of it with a function like skipgarb(), the program
could simply return an error message like "BAD FILE" or whatever and recover in a
sensible way. It would probably not make sense to use a function like skipgarb() for
files. For comparison alone, skipfilegarb() is written below.
skipfilegarb(fp)
FILE *fp;
{
while (getc(fp) != '\n')
{
}
}
getchar()
putchar()
getc()
ungetc();
putc()
fgetc()
fputc()
FILE *fp;
char ch;
/* open file */
ch = getc (fp);
ch = fgetc (fp);
These functions return a character from the specified file if they operated
successfully, otherwise they return EOF to indicate the end of a file or some other
error. Apart from this, these functions/macros are quite unremarkable.
Node:ungetc, Next:putc and fputc, Previous:getc and fgetc, Up:Files and Devices
ungetc()
ungetc() is a function which `un-gets' a character from a file. That is, it reverses the
effect of the last get operation. This is not like writing to a file, but it is like stepping
back one position within the file. The purpose of this function is to leave the input in
the correct place for other functions in a program when other functions go too far in a
file. An example of this would be a program which looks for a word in a text file and
processes that word in some way.
The program would skip over spaces until it found a character and then it would know
that this was the start of a word. However, having used getc() to read the first
character of that word, the position in the file would be the second character in the
word! This means that, if another function wanted to read that word from the
beginning, the position in the file would not be correct, because the first character
would already have been read. The solution is to use ungetc() to move the file
position back a character:
int returncode;
returncode = ungetc(fp);
FILE *fp;
char ch;
int returncode;
Node:fgets and fputs, Next:feof, Previous:putc and fputc, Up:Files and Devices
char *strbuff,*returnval;
int n;
FILE *fp;
strbuff is a pointer to an input buffer for the string; fp is a pointer to an open file.
returnval is a pointer to a string: if there was an error in fgets() this pointer is set to
the value NULL, otherwise it is set to the value of "strbuff". No more than (n-1)
characters are read by fgets() so the programmer has to be sure to set n equal to the
size of the string buffer. (One byte is reserved for the NULL terminator.) The form of
an fputs() statement is as follows:
char *str;
int returnval;
FILE *fp;
Where str is the NULL terminated string which is to be sent to the file pointed to by
fp. returnval is set to EOF if there was an error in writing to the file.
feof()
This function returns a true or false result. It tests whether or not the end of a file has
been reached and if it has it returns `true' (which has any value except zero);
otherwise the function returns `false' (which has the value zero). The form of a
statement using this function is:
FILE *fp;
int outcome;
outcome = feof(fp);
Most often feof() will be used inside loops or conditional statements. For example:
consider a loop which reads characters from an open file, pointed to by fp. A call to
feof() is required in order to check for the end of the file.
while (!feof(fp))
{
ch = getc(fp);
}
Translated into pidgin English, this code reads: `while NOT end of file, ch equals get
character from file'. In better(?) English the loop continues to fetch characters as long
as the end of the file has not been reached. Notice the logical NOT operator ! which
stands before feof().
Node:Printer Output, Next:Example 36, Previous:feof, Up:Files and Devices
Printer Output
Any serious application program will have to be in full control of the output of a
program. For instance, it may need to redirect output to the printer so that data can be
made into hard copies. To do this, one of three things must be undertaken:
•
stdout must be redirected so that it sends data to the printer device.
•
A new "standard file" must be used (not all C compilers use this method.)
•
A new file must be opened in order to write to the printer device
The first method is not generally satisfactory for applications programs, because the
standard files stdin and stdout can only easily be redirected from the operating
system command line interpreter (when a program is run by typing its name).
Examples of this are:
which send a text file to the printer device. The second method is reserved for only a
few implementations of C in which another `standard file' is opened by the local
operating system and is available for sending data to the printer stream. This file
might be called "stdprn" or "standard printer file" and data could be written to the
printer by switching writing to the file like this:
The final method of writing to the printer is to open a file to the printer, personally.
To do this, a program has to give the "filename" of the printer device. This could be
something like "PRT:" or "PRN" or "LPRT" or whatever. The filename (actually
called a pseudo device name) is used to open a file in precisely the same way as any
other file is opened: by using a call to fopen(). fopen() then returns a pointer to file
(which is effectively "stdprn") and this is used to write data to a computer's printer
driver. The program code to do this should look something like the following:
FILE *stdprn;
Example
Here is an example program which reads a source file (for a program, written in C,
Pascal or whatever...) and lists it, along with its line numbers. This kind of program is
useful for debugging programs. The program provides the user with the option of
sending the output to the printer. The printer device is assumed to have the filename
"PRT:". Details of how to convert the program for other systems is given at the end.
/***************************************************************/
/* */
/* LIST : program file utility */
/* */
/***************************************************************/
#include <stdio.h>
#define CODE 0
#define SIZE 255
#define ON 1
#define OFF 0
#define TRUE 1
#define FALSE 0
FILE *fin;
FILE *fout = stdout; /* where output goes to */
/***************************************************************/
/* Level 0 */
/***************************************************************/
main ()
{ char strbuff[size],*filename();
int Pon = false;
int line = 1;
if (yes())
{
Pon = Printer(ON);
}
while (!feof(fin))
{
if (fgets(strbuff,size,fin) != strbuff)
{
if (!feof(fin))
{
printf ("Source file corrupted\n");
exit (CODE);
}
}
fprintf (fout,"%4d %s",line++,strbuff);
}
CloseFiles(Pon);
}
/*************************************************************/
/* Level 1 */
/*************************************************************/
int Pon;
{
if (Pon)
{
Printer(OFF);
}
if (fclose(fin) != 0)
{
printf ("Error closing input file\n");
}
/***********************************************************/
int status;
{
switch (status)
{
case on: while ((fout = fopen("PRT:","w")) == NULL)
{
printf ("Printer busy or disconnected\n");
printf ("\n\nRetry? Y/N\n");
if (!yes())
{
exit(CODE);
}
}
break;
/***********************************************************/
/* Toolkit */
/***********************************************************/
do
{
printf ("Enter filename :");
scanf ("%24s",filenm);
skipgarb();
}
while (strlen(filenm) == 0);
return (filenm);
}
/*************************************************************/
{ char ch;
while (TRUE)
{
ch = getchar();
skipgarb();
switch (ch)
{
case 'y' : case 'Y' : return (TRUE);
case 'n' : case 'N' : return (FALSE);
}
}
}
/*************************************************************/
{
while (getchar() != '\n')
{
}
}
/* end */
Node:Output 36, Next:Converting example, Previous:Example 36, Up:Files and
Devices
Output
Here is a sample portion of the output of this program as applied to one of the
example programs in section 30.
1 /********************************************************/
2 /* */
3 /* C programming utility : variable referencer */
4 /* */
5 /********************************************************/
6
7 /* See section 30 */
8
9 #include <stdio.h>
10 #include <ctype.h>
11
12 #define TRUE 1
13 #define FALSE 0
14 #define DUMMY 0
15 #define MAXSTR 512
16 #define MAXIDSIZE 32
int status;
{
switch (status)
{
case on: fout = stdprn;
break;
Filing Errors
The standard library provides an error function/macro which returns a true/false result
according to whether or not the last filing function call returned an error condition.
This is called ferror(). To check for an error in an open file, pointed to by fp:
FILE *fp;
if (ferror(fp))
{
error_handler();
}
This function/macro does not shed any light upon the cause of errors, only whether
errors have occurred at all. A detailed diagnosis of what went wrong is only generally
possible by means of a deeper level call to the disk operating system (DOS).
Node:Other Facilities for High Level Files, Next:fread() and fwrite(), Previous:File
Errors, Up:Files and Devices
fread()
fwrite()
ftell()
fseek()
rewind()
fflush()
These functions provide facilities to read and write whole blocks of characters in one
operation as well as further facilities to locate and alter the current focus of attention
within a file. They offer, essentially, low level filing operations for files which have
been opened for high level use!
Node:fread() and fwrite(), Next:ftell and fseek, Previous:Other Facilities for High
Level Files, Up:Files and Devices
FILE *fp;
int noread,n,size;
char *ptr;
FILE *fp;
int nowritten,n,size;
char *ptr;
This time the parameters in parentheses provide information about where the data, to
be written to a file, will be found. fp is a pointer to an open file; ptr is a pointer to the
start of a block of memory at which the data are stored; size is the size of a "block" of
data in characters; n is the number of blocks of data to be read; nowritten is a return
value which indicates the actual number of blocks which was written. Again, this
should be checked.
A caution about these functions: each of these block transfer routines makes an
important assumption about the way in which data are stored in the computer system.
It is assumed that the data are stored contiguously in the memory, that is, side by side,
in sequential memory locations. In some systems this can be difficult to arrange (in
multi-tasking systems in particular) and almost impossible to guarantee. Memory
which is allocated in C programs by the function malloc() does not guarantee to find
contiguous portions of memory on successive calls. This should be noted carefully
when developing programs which use these calls.
pos = ftell(fp);
fp is an open file, which is in some state of being read or written to. pos is a long
integer value which describes the position in terms of the number of characters from
the beginning of the file. Aligning a file portal with a particular place in a file is more
sophisticated than simply taking note of the current position. The call to fseek()
looks like this:
long int pos;
int mode,returncode;
FILE *fp;
0
pos is an offset measured relative to the beginning of the file.
1
pos is an offset measured relative to the current position.
2
is an offset measured relative to the end of the file.
pos
Some examples help to show how this works in practice:
long int pos = 50;
int mode = 0,returncode;
FILE *fp;
rewind()
rewind() is a macro, based upon fseek(), which resets a file position to the
beginning of the file. e.g.
FILE *fp;
rewind(fp);
fseek(fp,0L,0); /* = rewind() */
Node:fflush, Next:Low Level Filing Operations, Previous:rewind, Up:Files and
Devices
fflush()
This is a macro/function which can be used on files which have been opened for
writing or appending. It flushes the output buffer which means that it forces the
characters in the output buffer to be written to the file. If used on files which are open
for reading, it causes the input buffer to be emptied (assuming that this is allowed at
all). Example:
FILE *fp;
fflush(fp);
open()
close()
creat()
read()
write()
rename()
unlink()/remove()
lseek()
These low level routines work on the operating system's end of the file portals. They
should be regarded as being advanced features of the language because they are
dangerous routines for bug ridden programs. The data which they deal with is
untranslated: that is, no conversion from characters to floating point or integers or any
type at all take place. Data are treated as a raw stream of bytes. Low level functions
should not be used on any file at the same time as high level routines, since high level
file handling functions often make calls to the low level functions.
Working at the low level, programs can create, delete and rename files but they are
restricted to the reading and writing of untranslated data: there are no functions such
as fprintf() or fscanf() which make type conversions. As well as the functions
listed above a local operating system will doubtless provide special function calls
which enable a programmer to make the most of the facilities offered by the particular
operating environment. These will be documented, either in a compiler manual, or in
an operating system manual, depending upon the system concerned. (They might
concern special graphics facilities or windowing systems or provide ways of writing
special system dependent data to disk files, such as date/time stamps etc.)
Node:File Handles, Next:open, Previous:Low Level Filing Operations, Up:Files and
Devices
File descriptors
At the low level, files are not handled using file pointers, but with integers known as
file handles or file descriptors. A file handle is essentially the number of a particular
file portal in an array. In other words, for all the different terminology, they describe
the same thing. For example:
int fd;
open()
open() is the low level file open function. The form of this function call is:
fd = open (filename,mode);
where filename is a string which holds the name of the file concerned, mode is a value
which specifies what the file is to be opened for and fd is either a number used to
distinguish the file from others, or -1 if an error occurred.
A program can give more information to this function than it can to fopen() in order
to define exactly what open() will do. The integer mode is a message or a pseudo
register which passes the necessary information to open(), by using the following
flags:
The macro definitions of these flags will be included in a library file: find out which
one and #include it in the program. The normal procedure is to open a file using one
of the first three modes. For example:
#define FAILED -1
main()
{ char *filename();
int fd;
fd = open(filename(), O_RDONLY);
if (fd == FAILED)
{
printf ("File not found\n");
error_handler (failed);
}
}
This opens up a read-only file for low level handling, with error checking. Some
systems allow a more flexible way of opening files. The four appended modes are
values which can be bitwise ORed with one of the first three in order to get more
mileage out of open(). The bitwise OR operator is the vertical bar "|". For example,
to emulate the fopen() function a program could opt to create a file if it did not
already exist:
fd = open (filename(), O_RDONLY | O_CREAT);
open() sets the file position to zero if the file is opened successfully.
close()
close() releases a file portal for use by other files and brings a file completely up to
date with regard to any changes that have been made to it. Like all other filing
functions, it returns the value 0 if it performs successfully and the value -1 if it fails.
e.g.
#define FAILED -1
if (close(fd) == FAILED)
{
printf ("ERROR!");
}
creat()
This function creates a new file and prepares it for access using the low level file
handling functions. If a file which already exists is created, its contents are discarded.
The form of this function call is:
fd = creat(filename,pmode);
filename must be a valid filename; pmode is a flag which contains access-privilege
mode bits (system specific information about allowed access) and fd is a returned file
handle. In the absence of any information about pmode, this parameter can be set to
zero. Note that, the action of creating a file opens it too. Thus after a call to creat,
you should close the file descriptor.
Node:read, Next:write, Previous:creat, Up:Files and Devices
read()
This function gets a block of information from a file. The data are loaded directly into
memory, as a sequence of bytes. The user must provide a place for them (either by
making an array or by using malloc() to reserve space). read() keeps track of file
positions automatically, so it actually reads the next block of bytes from the current
file position. The following example reads n bytes from a file:
The return value should be checked. Its values are defined as follows:
0
End of file
-1
Error occurred
n
the number of bytes actually read. (If all went well this should be equal to n.)
Node:write, Next:lseek, Previous:read, Up:Files and Devices
write()
This function is the opposite of read(). It writes a block of n bytes from a contiguous
portion of memory to a file which was opened by open(). The form of this function
is:
lseek()
Low level file handing functions have their equivalent of fseek() for finding a
specific position within a file. This is almost identical to fseek() except that it uses
the file handle rather than a file pointer as a parameter and has a different return
value. The constants should be declared long int, or simply long.
pos gives the new file position if successful, and -1 (long) if an attempt was made to
read past the end of the file. The values which mode can take are:
0
Offset measured relative to the beginning of the file.
1
Offset measured relative to the current position.
2
Offset measured relative to the end of the file.
Node:unlink remove, Next:Example 37, Previous:lseek, Up:Files and Devices
#define FAILED -1
int returnvalue;
char *filename;
rename()
This function renames a file. The programmer specifies two filenames: the old
filename and a new file name. As usual, it returns the value -1 if the action fails. An
example illustrates the form of the rename() call:
#define FAILED -1
char *old,*new;
if (rename(old,new) == FAILED)
{
printf ("Can't rename %s as %s\n",old,new);
}
rename() can fail because a file is protected or because it is in use, or because one of
the filenames given was not valid.
Example
This example strings together some low level filing actions so as to illustrate their use
in a real program. The idea is to present a kind of file or "project" menu for creating,
deleting, renaming files. A rather feeble text editor allows the user to enter 255
characters of text which can be saved.
/***************************************************************/
/* */
/* LOW LEVEL FILE HANDLING */
/* */
/***************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <fcntl.h> /* defines O_RDONLY etc.. */
#define CODE 0
#define SIZE 255
#define FNMSIZE 30 /* Max size of filenames */
#define TRUE 1
#define FALSE 0
#define FAILED -1
int fd;
/***************************************************************/
/* Level 0 */
/***************************************************************/
main ()
{ char *data,getkey(),*malloc();
while (TRUE)
{
menu();
switch (getkey())
{
case 'l' : LoadFile(data);
break;
case 's' : SaveFile(data);
break;
case 'e' : Edit(data);
break;
case 'd' : DeleteFile();
break;
case 'r' : RenameFile();
break;
case 'q' : if (sure())
{
return (CODE);
}
break;
}
}
}
/*************************************************************/
/* Level 1 */
/*************************************************************/
menu ()
{
CLRSCRN();
printf (" ---------------------------------\n");
printf ("| MENU |\n");
printf ("| ~~~~~~ |\n");
printf ("| |\n");
printf ("| L) Load File |\n");
printf ("| S) Save File |\n");
printf ("| E) Edit File |\n");
printf ("| D) Delete File |\n");
printf ("| R) Rename File |\n");
printf ("| Q) Quit |\n");
printf ("| |\n");
printf ("| Select Option and RETURN |\n");
printf ("| |\n");
printf (" --------------------------------- \n");
NEWLINE();
}
/*************************************************************/
char *data;
{ char *filename(),getkey();
int error;
fd = open(filename(), O_RDONLY);
if (fd == FAILED)
{
printf ("File not found\n");
return (FAILED);
}
if (error == FAILED)
{
printf ("Error loading file\n");
wait();
}
else
{
if (error != SIZE)
{
printf ("File was corrupted\n");
wait();
}
}
close (fd,data,SIZE);
return (error);
}
/*************************************************************/
char *data;
{ char *filename(),getkey(),*fname;
int error,fd;
if (fd == FAILED)
{
printf ("File cannot be written to\n");
printf ("Try to create new file? Y/N\n");
if (yes())
{
if ((fd = CreateFile(fname)) == FAILED)
{
printf ("Cannot create file %s\n",fname);
return (FAILED);
}
}
else
{
return (FAILED);
}
}
close (fd,data,SIZE);
wait();
return (error);
}
/*************************************************************/
char *data;
{ char *ptr;
int ctr = 0;
skipgarb();
}
/*************************************************************/
{ char *filename(),getkey(),*fname;
printf ("Delete File\n\n");
fname = filename();
if (sure())
{
if (remove(fname) == FAILED)
{
printf ("Can't delete %s\n",fname);
}
}
else
{
printf ("File NOT deleted!\n");
}
wait();
}
/*************************************************************/
RenameFile()
{ char old[FNMSIZE],*new;
if (rename(old,new) == FAILED)
{
printf ("Can't rename %s as %s\n",old,new);
}
wait();
}
/*************************************************************/
/* Level 2 */
/*************************************************************/
CreateFile (fname)
char *fname;
{ int fd;
return (fd);
}
/*************************************************************/
/* Toolkit */
/*************************************************************/
do
{
printf ("Enter filename :");
scanf ("%24s",statfilenm);
skipgarb();
}
while (strlen(statfilenm) == 0);
return (statfilenm);
}
/**************************************************************/
{
printf ("Are you absolutely, unquestionably certain? Y/N\n");
return(yes());
}
/**************************************************************/
yes()
{ char getkey();
while (TRUE)
{
switch(getkey())
{
case 'y' : return (TRUE);
case 'n' : return (FALSE);
}
}
}
/**************************************************************/
wait()
{ char getkey();
/**************************************************************/
{ char ch;
ch = getchar();
skipgarb();
return((char)tolower(ch));
}
/**************************************************************/
skipgarb() /* skip garbage corrupting input */
{
while (getchar() != '\n')
{
}
}
/* end */
Node:Questions 25, Previous:Example 37, Up:Files and Devices
Questions
1. What are the following?
1. File name
2. File pointer
3. File handle
2. What is the difference between high and low level filing?
3. Write a statement which opens a high level file for reading.
4. Write a statement which opens a low level file for writing.
5. Write a program which checks for illegal characters in text files. Valid
characters are ASCII codes 10,13,and 32..126. Anything else is illegal for
programs.
6. What statement performs formatted writing to text files?
7. Print out all the header files on your system so that you can see what is defined
where!
Tidy programs are a blessing to programmers. Tidy data are just as important. As
programs become increasingly complex, their data also grow in complexity and
single, independent variables or arrays are no longer enough. What one then needs is a
data structure. This is where a new type of variable comes in: it is called a struct
type, or in other languages, a record. struct types or structures are usually lumped
together with another type of variable called a union. In fact their purposes are quite
different.
This shows a program as a kind of society of sealed function capsules which work
together like a beehive of activity upon a honeycomb of program data. This
imaginative idea is not a bad picture of a computer program, but it is not complete
either. A program has to manipulate data: it has to look at them, move them around
and copy them from place to place. All of these things would be very difficult if data
were scattered about liberally, with no particular structure. For this reason C has the
facility, within it, to make sealed capsules - not of program code - but of program
data, so that all of these actions very simply by grouping variables together in
convenient packages for handling. These capsules are called structures.
struct
A structure is a package of one or usually more variables which are grouped under a
single name. Structures are not like arrays: a structure can hold any mixture of
different types of data: it can even hold arrays of different types. A structure can be as
simple or as complex as the programmer desires.
The word struct is a reserved word in C and it represents a new data type, called an
aggregate type. It is not any single type: the purpose of structures is to offer a tool for
making whatever shape or form of variable package that a programmer wishes. Any
particular structure type is given a name, called a structure-name and the variables
(called members) within a structure type are also given names. Finally, every variable
which is declared to be a particular structure type has a name of its own too. This
plethora of names is not really as complicated as it sounds.
Declarations
A structure is declared by making a blank template for a variable package. This is
most easily seen with the help of an example. The following statement is actually a
declaration, so it belongs with other declarations, either at the head of a program or at
the start of a block.
struct PersonalData
{
char name[namesize];
char address[addresssize];
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
};
At this stage, no storage has been given over to a variable, nor has any variable been
declared: only a type has been defined. Having defined this type of structure,
however, the programmer can declare variables to be of this type. For example:
struct PersonalData x;
struct PersonalData
{
char name[namesize];
char address[addresssize];
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
}
x; /* variable identifier follows type */
Alternatively, typedef can be used to cut down a bit on typing in the long term. This
type definition is made once at the head of the program and then subsequent
declarations are made by using the new name:
typedef struct
{
char name[namesize];
char address[addresssize];
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
}
PersonalData;
then declare:
PersonalData x;
Scope
Both structure types and structure variables obey the rules of scope: that is to say, a
structure type declaration can be local or global, depending upon where the
declaration is made. Similarly if a structure type variable is declared locally it is only
valid inside the block parentheses in which it was defined.
main ()
{ struct ONE
{
int a;
float b;
};
struct ONE x;
function ()
Using Structures
How does a program use the variables which are locked inside structures? The whole
point about structures is that they can be used to group data into sensible packages
which can then be treated as single objects. Early C compilers, some of which still
exist today, placed very severe restrictions upon what a program could do with
structures. Essentially, the members of a structure could be assigned values and
pointers to individual structures could be found. Although this sounds highly
restrictive, it did account for the most frequent uses of structures. Modern compilers
allow more flexible use of structures: programs can assign one structure variable to
another structure variable (provided the structures match in type); structure variables
can be passed, whole, as parameters to functions and functions can return structure
values. This makes structures extremely powerful data objects to have in a program.
A structure is assigned to another structure by the following statements.
x = y;
The whole bundle of members is copied in one statement! Structures are passed as
parameters in the usual way:
function (x,y);
function (x,y)
{
}
Finally, a function which returns a structure variable such as:
{ struct PersonalData x,function();
x = function();
}
{
}
Notice that the return type of such a function must also be declared in the function
which calls that it, in the usual way. The reader will begin to see that structure names
account for a good deal of typing! The typedef statement is a very good way of
reducing this burden.
The members of a structure are accessed with the . dot character. This is a structure
member operator. Consider the structure variable x, which has the type struct
PersonalData. The members of x could be assigned by the following program:
main ()
{ struct PersonalData x;
where FillArray() is a hypothetical function which copies the string in the first
parameter to the array in the second parameter. The dot between the variable and the
names which follow implies that the statements in this brief program are talking about
the members in the structure variable x, rather than the whole collective bundle.
Members of actual structure variables are always accessed with this dot operator. The
general form of a member reference is:
This applies to any type of structure variable, including those accessed by pointers.
Whenever a program needs to access the members of a structure, this dot operator can
be used. C provides a special member operator for pointers, however, because they
are used so often in connection with structures. This new operator is described below.
int i;
The members of the arrays would then be accessed by statements like the following
examples:
array[i] = x;
array[i] = array[j];
array[i].YearOfBirth = 1987;
i = array[2].MonthOfBirth;
Example
This listing uses a structure type which is slightly different to PersonalData in that
string pointers are used instead of arrays. This allows more convenient handling of
real-life strings.
/*********************************************************/
/* */
/* Structures Demo */
/* */
/*********************************************************/
#include <stdio.h>
#define NAMESIZE 30
#define ADDRSIZE 80
#define NOOFPERSONS 20
#define NEWLINE() putchar('\n');
/*********************************************************/
typedef struct
{
char *Name;
char *Address;
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
}
PersonDat;
/*********************************************************/
{ PersonDat record[NOOFPERSONS];
PersonDat PersonalDetails();
int person;
DisplayRecords (record);
}
/*********************************************************/
{ PersonDat dat;
char strbuff[ADDRSIZE], *malloc();
return (dat);
}
/**********************************************************/
DisplayRecords (rec)
PersonDat rec[NOOFPERSONS];
{ int pers;
/**********************************************************/
/* Toolkit */
/**********************************************************/
int a,b;
{ int p, i = a - 1;
/**********************************************************/
{
while (getchar() != '\n')
{
}
}
/* end */
Structures of Structures
Structures are said to nest. This means that structure templates can contain other
structures as members. Consider two structure types:
struct first_structure
{
int value;
float number;
};
and
struct second_structure
{
int tag;
struct first_structure fs;
}
x;
These two structures are of different types, yet the first of the two is included in the
second! An instance of the second structure would be initialized by the following
assignments. The structure variable name is x:
x.tag = 10;
x.fs.value = 20;
x.fs.number = 30.0;
Notice the way in which the member operator . can be used over and over again.
Notice also that no parentheses are necessary, because the reference which is
calculated by this operator is worked out from left to right. This nesting can, in
principle, go on many times, though some compilers might place restrictions upon this
nesting level. Statements such as:
variable.tag1.tag2.tag3.tag4 = something;
are probably okay (though they do not reflect good programming). Structures should
nest safely a few times.
A word of caution is in order here. There is a problem with the above scheme that has
not yet been addressed. It is this: what happens if a structure contains an instance of
itself? For example:
struct Regression
{
int i;
struct Regression tag;
}
There is simply no way that this kind of statement can make sense, unless the
compiler's target computer has an infinite supply of memory! References to variables
of this type would go on for ever and an infinite amount of memory would be needed
for every variable. For this one reason, it is forbidden for a structure to contain an
instance of itself. What is not forbidden, however, is for a structure to contain an
instance of a pointer to its own type (because a pointer is not the same type as a
structure: it is merely a variable which holds the address of a structure). Pointers to
structures are quite invaluable, in fact, for building data structures such as linked lists
and trees. These extremely valuable devices are described below.
Pointers to Structures
A pointer to a structure type variable is declared by a statement like:
ptr is then, formally, a pointer to a structure of type Name only. ptr can be assigned
to any other pointer of similar type and it can be used to access the members of a
structure. It is in the second of these actions that a new structure operator is revealed.
According to the rules which have described so far, a structure member could be
accessed by pointers with the following statements:
(*ptr).YearOfBirth = 20;
This says let the member YearOfBirth of the structure pointed to by ptr, have the
value 20. Notice that *ptr, by itself, means the contents of the address which is held
in ptr and notice that the parentheses around this statement avoid any confusion about
the precedence of these operators. There is a better way to write the above statement,
however, using a new operator: ->. This is an arrow made out of a minus sign and a
greater than symbol and it is used simply as follows:
ptr->YearOfBirth = 20;
This statement is identical in every way to the first version, but since this kind of
access is required so frequently, when dealing with structures, C provides this special
operator to make the operation clearer. In the statements above, it is assumed that ptr
has been assigned to the address of some pre-assigned structure: for example, by
means of a statement such as:
ptr = &x;
#include <stdio.h>
#define NAMESIZE 30
#define ADDRSIZE 80
#define NOOFPERSONS 20
#define NEWLINE() putchar('\n');
/*********************************************************/
typedef struct
{
char *Name;
char *Address;
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
}
PersonDat;
/*********************************************************/
{ PersonDat record[NOOFPERSONS];
int person;
DisplayRecords (record);
}
/*********************************************************/
PersonDat *dat;
/**********************************************************/
DisplayRecords (rec)
PersonDat rec[NOOFPERSONS];
{ int pers;
/**********************************************************/
/* Toolkit */
/**********************************************************/
/* As before */
#define NAMESIZE 20
#define ADDRESSSIZE 22
struct PersonDat
{
char *name;
char *address;
int YearOfBirth;
int MonthOfBirth;
int DayOfBirth;
};
main ()
/* rest of program */
The items in the curly braces are matched to the members of the structure variable and
any items which are not initialized by items in the list are filled out with zeros.
malloc()
and pointers. The idea is to create the memory space for a new structure as and when
it is needed and to use a pointer to access the members of that structure, using the ->
operator. malloc() was described in connection with strings: it allocates a fixed
number of bytes of memory and returns a pointer to that data. For instance, to allocate
ten bytes, one would write something like this:
ptr = malloc(10);
ptr is then a pointer to the start of that block of 10 bytes. When a program wants to
create the space for a structure, it has a template for that structure, which was used to
define it, but it does not generally know, in advance, how many bytes long a structure
is. In fact, it is seldom possible to know this information, since a structure may
occupy more memory than the sum of its parts. How then does a program know how
must space to allocate? The C compiler comes to the rescue here, by providing a
compile time operator called
sizeof ()
which calculates the size of an object while a program is compiling. For example:
sizeof(int)
Works out the number of bytes occupied by the type int.
sizeof(char)
Works out the number of bytes occupied by a single character. This equals 1,
in fact.
(type *) value
Consider the following example of C source code which allocates space for a
structure type called SomeStruct and creates a correctly aligned pointer to it, called
ptr.
This rather laboured statement provides both the memory and the location of that
memory in a legal and type-sensical way. The next section of this book discusses
what we can do with dynamically allocated structures.
• Declaration of union:
• Using unions:
Declaration
A union is declared in the same way as a structure. It has a list of members, which are
used to mould the type of object concerned.
union IntOrFloat
{
int ordinal;
float continuous;
};
At different times the program is to treat x,y and z as being either integers or float
types. When the variables are referred to as
x.ordinal = 1;
the program sees x as being an integer type. At other times (when x is referred to as
x.continuous) it takes on another aspect: its alter ego, the float type. Notice that x by
itself does not have a value: only its members have values, x is just a box for the
different members to share.
Unions are coded with the same constructions as structures. The dot . operator selects
the different members for variable and the arrow -> selects different values for
pointers. The form of such statements is:
union_variable.member;
union_pointer->member;
Unions are seldom very useful objects to have in programs, since a program has no
automatic way of knowing what type of member is currently stored in the union type.
One way to overcome this is to keep a variable which signals the type currently held
in the variable. This is done very easily with the aid of enumerated data. Consider the
following kind of union:
union WhichType
{
int ordinal;
float continuous;
char letter;
};
enum Types
{
INT,
FLOAT,
CHAR
};
union WhichType x;
enum Types x_status;
switch (x_status)
{
case INT : x.ordinal = 12;
break;
case FLOAT : x.continuous = 12.23;
break;
case CHAR : x.letter = '*';
}
struct Union_Handler
{
union WhichType x;
enum Types x_status;
}
var;
var.x.ordinal = 2;
ptr->x.ordinal = 2;
var.x_status = CHAR;
and so on...
Questions
1. What is the difference between a structure and a union?
2. What is a member?
3. If x is a variable, how would you find out the value of a member called mem.
4. If ptr is a pointer to a structure, how would you find out the value of a
member called mem.
5. A union is a group of variables in a single package. True or false?
Data Structures
Data structures are organized patterns of data. The purpose of building a data structure
is to create a pattern of information which models a particular situation clearly and
efficiently. Take the simplest kind of data structure: the array. Arrays are good for
storing patterns of information which look like tables, or share a tabular structure. For
example, a chess board looks like a two dimensional array, so a chess game would
naturally use a two dimensional array to store the positions of pieces on the chess
board. The aim of a data structure is to model real life patterns with program data.
Most real application programs require a more complex data structure than C
variables can offer; often arrays are not suitable structures for a given application. To
see this, consider an application example in which a program stores a map of the local
countryside. This program has to store information about individual towns and it has
to be able to give directions to the user about how to get to particular towns from
some reference point. In real life, all of this information is most easily conveyed by
means of a map, with towns' vital statistics written on it. (See figure 1.) The diagram
shows such a simplified map of the surrounding land. This sort of map is, ideally, just
what a computer ought to be able to store. The handicap is that the map does not look
very computerish. If the map is ever going to be stored in a computer it will need to
look more mechanical. A transformation is needed. In order to make the map into a
A structure diagram is a picture which shows how something is connected up. Most
often a structure diagram shows how a problem is connected up by relating all the
parts which go together to make it up. In this case, the structure diagram just shows
how program data are related to each other.
struct Town
{
struct Town *north;
struct Town *south;
struct Town *east;
struct Town *west;
struct LocalInfo help;
};
Assume for now that LocalInfo is a structure which contains all the information
about a town required by the program. This part of the information is actually
irrelevant to the structure of the data because it is hidden inside the sealed capsule. It
is the pointers which are the main items of concern because it is pointers which
contain information that enables a program to find its way around the map very
quickly. If the user of this imaginary application program wished to know about the
town to the north of one particular place, the program would only have to refocus its
attention on the new structure which was pointed to by the struct member north and
similarly for other directions.
A data structure is built up, like a model, by connecting struct type variables together
with pointers: these are the building blocks.
By thinking of struct types and pointers in terms of pictures, one begins to see how
structures can be fashioned, in computer memory, to look exactly like the problems
which they represent.
What is interesting about data structure diagrams is the way in which they resemble
the structure diagrams of C programs, which were drawn in chapter 7. There is a
simple reason for this similarity: computer programs are themselves just data
structures in which the data are program instructions and the pointers and sealed
boxes are function calls. The structure of a computer program is called a hierachy.
Sometimes the shape of data structures and programs are identical; when this
happens, a kind of optimum efficiency has been reached in conceptual terms.
Programs which behave exactly like their data operate very simply. This is the reason
why structure diagrams are so useful in programming: a structure diagram is a
diagram which solves a problem and does so in a pictorial way, which models the way
we think.
Root
This is a place where a data structure starts. Every chain has to start
somewhere. The address of the root of a data structure has to be stored
explicitly in a C variable.
Links
A link is a pointer to a new struct type. Links are used to chain structures
together. The address of the next element in a chain structure is stored inside
the previous structure.
Data structures do not have to be linear chains and they are often not. Structures, after
all, can hold any number of pointers to other structures, so there is the potential to
branch out into any number of new structures. In the map example above, there were
four pointers in each structure, so the chaining was not linear, but more like a
latticework.
We need to think about where and how data structures are going to be stored.
Remember that pointers alone do not create any storage space: they are only a way of
finding out the contents of storage space which already exists. In fact, a program must
create its own space for data structures. The key phrase is dynamic storage: a program
makes space for structures as new ones are required and deletes space which is does
not require. The functions which perform this memory allocation and release are:
There are some advantages which go with the use of dynamic storage for data
structures and they are summarized by the following points:
The remaining parts of this section aim to provide a basic plan or formula for putting
data structures together in C. This is done with recourse to two example structures,
which become two example programs in the next chapter.
• Group all the data, which must be stored, together and define a struct type to
hold them.
• Think of a pattern which reflects the way in which the data are connected and
add structure pointers to the struct definition, to connect them.
• Design the programming algorithms to handle the memory allocation, link
pointers and data storage.
Node:Setting Up A Data Structure, Next:Example Structures, Previous:Programme
For Building Data Structures, Up:Data structures
One of these is used to hold the root of the data structure and the other is used
as a current pointer.
This sets the pointers north and south to the value NULL, which
conventionally means that the pointer does not point anywhere.
• When other structures have been created, the pointers can be assigned to them:
• ptr = (struct Town *) malloc(sizeof(struct Town));
• ptr->north = NULL;
• ptr->south = NULL;
•
• /* etc.. initialize members */
•
• root->north = ptr;
•
This last statement connects the new structure onto the north branch of root.
NULL pointer assignments tell the program handling the data structure when it has
come to the edge of the structure: that is when it has found a pointer which doesn't
lead anywhere.
Example Structures
Two data structures of thids kind are very common: the linked list and the binary
tree and both work upon the principles outlined above (In fact they are just different
manifestations of the same thing.)
struct list
{
double value;
struct list *succ;
};
A linked list has only a single pointer per structure, which points to the successor in
the list. If the blocks were labelled A B C D E... then B would be the successor of A;
C would be the successor of B and so on. Linked lists have two advantages over one
dimensional arrays: they can be sorted easily (see diagram) and they can be made any
length at all.
A binary tree is a sequence of structures, each of which branches out into two new
ones.
struct BinaryTree
{
/* other info */
*tree = NULL;
A binary tree structure has two pointers per struct type. This is useful for classifying
Right and left branches are taken to mean `greater than' and `less than' respectively.
The programs which handle these data structures are written in the form of complete,
usable application programs. They are simple by professional standards, but they are
long by book standards so they are contained in a section by themselves, along with
their accompanying programmers' documentation, See Example Programs chapter.
Questions
1. What is a structure diagram?
2. How are data linked together to make a data structure?
3. Every separate struct type in a data structure has its own variable name. True
or false?
4. How are the members of structures accessed in a data structure?
5. Write a statement which creates a new structure of type "struct BinaryTree"
and finds its address. Store that address in a variable which is declared as
follows:
6. struct BinaryTree *ptr;
7.
8. Write a small program which makes a linked list, three structures long and
assigns all their data to be zero. Can you automate this program with a loop?
Can you make it work for any number of structures?
Recursion
This section is about program structures which can talk about themselves. What
happens to a function which makes a call itself? Examine the function below:
Well_Function ()
{
/* ... other statements ... */
Well_Function ();
}
This kind of function calling scenario is doomed to continue without end, as, each
time the function is called, it is inevitably called again. The computer becomes totally
consumed with the task of calling Well_Function() over and over. It is apparently
doomed to repeat the same procedure for ever. Or is it?
function1()
/ \
/ \
function2() function3()
/ \ / \
The answer to this puzzle is that the computer keeps a record of the addresses of the
places to which it must return, no matter how many times functions are called. It does
A stack is quite literally a pile of data, organized in the memory. Information is placed
on top of a stack and taken from the top. It is called a last in, first out (LIFO) structure
because the last thing to go on the top of a stack is always the first thing to come off
it. C organizes a stack structure when it runs a program and uses it for storing local
variables and for keeping track of where it has to return to. When it calls a function, it
leaves itself a reminder, on the top of its program stack, which tells it where it has to
go to when it has finished executing that function. C management makes sure that it
does not put anything else on top of that reminder to spoil the flow of control. When a
function is finished, the program takes the first message from the top of the stack and
carries on executing statements at the place specified by the message. Normally this
method works perfectly, without any problems at all: functions are called and they
return again; the stack grows and shrinks and all is well.
What happens when a recursive function, like Well_Function() calls itself? The
system works as normal. C makes a note of the place it has to return to and puts that
note on top of the stack. It then begins executing statements. When it comes to the call
Well_Function() again, it makes a new note of where it has to come back to and
deposits it on top of the stack. It then begins the function again and when it finds the
function call, it makes a new note and puts on the top of the stack.... As this process
continues, the memory gets filled up with the program's messages to itself: the stack
of messages gets larger and larger. Since the function has no chance of returning
control to its caller, the messages never get taken off the stack and it just builds up.
Eventually the computer runs out of memory and the computer crashes or interrupts
the program with a fatal error message.
/****************************************************/
/* Level 1 */
/****************************************************/
in the programs in this book. The main() function is at level 0 because it is the root of
the program. If main() calls any functions at all, control drops down to level one.
When a level one function returns, it hands control back to level zero. These level
numbers actually count the height of the program stack at any point in a program. The
level number is the number of messages or reminders on the stack.
A function like Well_Function() digs itself a well of infinite depth. It punches a
great hole in a program; it has no place in a levelled structure diagram. The function is
pathological because it causes the stack fill up the memory of the computer. A better
name for this function would be:
{
StackOverflow();
}
Earlier we remarked that programs and data structures aim to model the situation they
deal with as closely as possible. Some problems are made up of many levels of detail
(see the introduction to this tutorial) and the details are identical at all levels. Since
recursion is about functions which contain themselves at all levels, this tends to
suggest that recursion would be useful for dealing with these self-similar problems.
Data structures are prime candidates for this because they are made up of identical
structure types, connected together in a way which make them look like programs
connected up by function calls.
Recursive functions can be tamed by making sure that there is a safe way exit them,
so that recursion only happens under particular circumstances. The aim is to control
the number of times that recursion takes place by making a decision about what
happens in the function: the decision about whether a function calls itself or not. For
example, it is easy to make Well_Function recurse four times only, by making a test:
Well_Function(nooftimes)
int nooftimes;
{
if (nooftimes == 0)
{
return (0);
}
else
{
Well_Function(nooftimes-1);
}
}
A call of WellFunction(4) would make this function drop down four stack levels
and then return. Notice the way in which the if..else statement shields the program
from the recursion when nooftimes equals zero. It effectively acts as a safety net,
stopping the programming from plunging down the level well infinitely.
factorial(4) == 1 * 2 * 3 * 4 == 24
factorial(6) == 1 * 2 * 3 * 4 * 5 * 6 == 720
and
factorial (0) = 1
factorial (n)
unsigned int n;
{
if (n == 0)
{
return (1);
}
else
{
return (n * factorial(n-1));
}
}
To see how this works, try following it through for n equals three. The statement:
factorial (3);
causes a call to be made to factorial(). The value of n is set to three. factorial()
then tests whether n is zero (which it is not) so it takes the alternative branch of the
if..else statement. This instructs it to return the value of:
3 * factorial(3-1)
In order to calculate that, the function has to call factorial recursively, passing the
value (3-1) or 2 to the new call. The new call takes this value, checks whether it is
zero (it is not) and tries to return the value 2 * factorial(1). In order to work this
out, it needs to call factorial again, which checks that n is not 0 (it is not) and so tries
to return 1 * factorial(0). Finally, it calls factorial(0) which does not call
factorial any more, but starts unloading the stack and returning the values. The
expression goes through the following steps before finally being evaluated:
== 3 * 2 * 1 * 1
Try to write this function without using recursion and compare the two.
/* structure definition */
struct list
{
/* some other data members */
struct list *succ;
};
/**************************************************************/
{
if (ptr != NULL)
{
deletetoend (ptr->succ);
releasestruct (ptr);
}
return (NULL);
}
/**************************************************************/
{
if (free((char *) ptr) != 0)
{
printf ("DEBUG [Z0/TktDtStrct] memory release failure\n");
}
}
We supply a pointer to the place we would like the list to end. This need not be the
very beginning: it could be any place in the list. The function then eliminates all
structures after that point, up to the end of the list. It does assume that the programmer
has been careful to ensure that the end of the list is marked by a NULL pointer. This is
the conventional way of denoting a pointer which does not point anywhere. If the
pointer supplied is already NULL then this function does nothing. If it is not NULL then
it executes the statements enclosed by the if braces. Notice that deletetoend() calls
itself immediately, passing its successor in the list as a parameter. (ptr->succ) The
function keeps doing this until it finds the end on the list. The very last-called
deletetoend() then reaches the statement releasestruct() which frees the
memory taken up by the last structure and hands it back to the free memory pool. That
function consequently returns and allows the second-last deletetoend() to reach the
releasestruct() statement, releasing the second last structure (which is now on the
end of the list). This, in turn, returns and the process continues until the entire list has
been deleted. The function returns the value NULL at each stage, so that when called,
deletetoend() offers a very elegant way of deleting part or all of a linked list:
recursion ()
{
if (++GLOBAL == 0)
{
return (0);
}
This function is treading a fine line between safety and digging its own recursive
grave. If alterGLOBAL() makes GLOBAL more negative, as fast as ++ can make it
more positive then GLOBAL will never be able to satisfy the condition of being zero
and it will go on making recursive calls, never returning. If alterGLOBAL() makes
the mistake of setting GLOBAL to a positive value, then the ++ operator in
recursion() can only make GLOBAL larger and it will never be able to satisfy the
condition that GLOBAL == 0 and so again the function would never be able to return.
The stack would fill up the memory and the program would plunge down an unending
recursive well.
If global variables and parameters are used instead, this difficulty can be controlled
much more easily. alterGLOBAL() cannot alter a variable in recursion() by
accident, if only local variables are used, because it only works with its own local
copies of parameters and variables which are locked away in a sealed capsule, out of
harm's way.
Questions
1. What is a recursive function?
2. What is a program "stack" and what is it for.
3. State the major disadvantage of recursion.
Example Programs
The aim of this section is to provide two substantial examples of C, which use the
data structures described in section 28.
• The editor:
• Insert Overwrite:
• Quitting section:
• Program listing stat:
Node:The editor, Next:Insert Overwrite, Previous:Statistical Data Handler,
Up:Statistical Data Handler
The Editor
A simple machine independent editor is provided for entering data. The editor first
asks the user whether the current number of sets of data is to be altered. The default
value is zero so, when data are typed in for the first time, this should be set up, by
responding Y for yes. Up to twenty independent sets of data can be used. This number
is set at the start and it is held in the memory and saved to disk with data files. If the
number of sets is reduced at any time, the top sets are cut off from the calculations,
but they are not lost forever, provided the number is changed back to include them
before they are saved to disk, since the number of sets is used as an upper bound in a
for loop: it does not actually alter the memory. More sets can be added at any time by
making this value larger.
Insert/Overwrite
A project file can be edited in either insert mode or overwrite mode. Files which
contain no data may only be edited insert mode. The editor senses this and selects the
mode automatically. In insert mode the user is prompted for values. Type 0.0 in place
of an entry to get out of this mode. In overwrite mode the user is offered each entry in
turn. If a non digit character is typed in (such as a . (dot) or a - (dash) etc..) the value
of an entry is not altered. However, if a new value is entered, the new value will
replace the old one. By default, the values are offered in turn from 1 to the final value.
However, on selecting overwrite mode, the user is prompted for a starting value, and
the values are offered from the starting number to the end. This is to avoid the rather
tedious process of working through all the entries which are not required in a system
independent way.
Quitting Sections
When quitting sections in which the user is supposed to enter data, the convention is
that typing a zero value (0.0 for a time, 0 in any other instance) is a signal to break out
of a section. Typing 0.0 while editing in insert mode causes the editor to quit.
The program includes three library files, which are used for the following purposes.
#include <stdio.h>
Standard IO eader file
#include <ctype.h>
Contains character ID macros
#include <math.h>
Includes math function declarations
The flow of program logic is most easily described by means of a program structure
diagram. The diagram shows the structure of function calls within the program and
this can be related to the listing. The general scheme of the program is this:
The data structure of this program is an array of linked lists. The array provides the
roots of several independent linked lists: one for each group of data. These linked lists
are attended to by toolkit routines and by special functions such as over().
Node:Listing stat, Next:Variable Cross Referencer, Previous:Statistical Data Handler,
Up:Example Programs chapter
Listing
/************************************************************/
/* */
/* Statistical Calculator */
/* */
/************************************************************/
#include <stdio.h>
#include <ctype.h>
#include <math.h>
/***********************************************************/
/** Manifest Constants / Macros / Static Variables **/
/***********************************************************/
#define TRUE 1
#define FALSE 0
#define GRPS 20 /* No grps which can be handled */
#define CAREFULLY 1
#define FAST 0
#define NOTZERO 1
#define ENDMARK -1.1
#define NOTENDMARK 0
#define BIGNUM 1e300
int DATSETS = 0;
short DATATHERE = FALSE; /* list data */
char *FSP = ".........................."; /* project name */
/**********************************************************/
/** STRUCTURES **/
/**********************************************************/
struct list
{
double value;
struct list *succ;
};
struct Vlist
{
struct list *datptr;
int datathere;
}
Data[GRPS];
/***********************************************************/
/** LEVEL 0 : Main Program **/
/***********************************************************/
main ()
{ char getkey();
clrflags();
while (TRUE)
{
Menu();
switch (getkey())
{
case '1' : edit(noofgroups());
break;
case '2' : LoadSave();
break;
case '3' : Analyse();
break;
case 'q' : if (wantout(CAREFULLY)) quit();
}
}
}
/************************************************************/
/** LEVEL 1 **/
/************************************************************/
{ short i;
/***********************************************************/
Menu ()
{
CLRSCRN();
printf ("\nStatistical Calculator V1.0\n\n\n");
/*************************************************************/
int no_grps;
{ char s,status(),getkey();
int i,stop = FALSE,ctr;
void saveproject();
double over(),t,correct,getfloat();
struct list *ptr,*here,*eolist(),
*install(),*startfrom();
while (TRUE)
{
i = whichgroup();
switch (s = status(i))
{
case 'i':
for (here = eolist(i,&ctr); TRUE; ctr++)
{
updatescrn (i,s);
printf("%d:",ctr);
if ((t = getfloat ()) == 0) break;
here = install (here,t,i);
}
printf ("\n\nFile closed\n\n");
break;
case 'o':
for (ptr=startfrom(&ctr,i); ptr != NULL; ptr = ptr-
>succ)
{
if (ctr % 4 == 1) updatescrn (i,s);
correct = over(ctr++,ptr->value);
ptr->value = correct;
}
break;
/************************************************************/
{ char ch,getkey();
ch = getkey();
switch (tolower(ch))
{
case 'a' : printf ("\nHow many groups for this file?
(0..%d)\n\n",GRPS);
return (DATSETS = getint(0,GRPS));
/*************************************************************/
{ char ch,getkey();
CLRSCRN();
printf ("\nCurrent Project %s\n\n\n", FSP);
printf ("Load new project or Save current one (L/S/Quit) ?\n\n");
ch = getkey();
switch (tolower(ch))
{
case 'l' : if (sure())
{
DATATHERE = loadproject ();
}
break;
case 's' : if (sure())
{
saveproject ();
}
case 'q' :
}
}
/************************************************************/
{ char getkey();
double mean(), mn, millikan();
int i;
stddevs(mn);
getkey();
}
/************************************************************/
{ short i;
struct list *deletetoend();
exit(0);
}
/************************************************************/
/* LEVEL 2 */
/************************************************************/
void saveproject ()
{ FILE *dfx;
char *filename(),ch,getkey();
struct list *ptr;
int i;
fprintf (dfx,"%ld\n",DATSETS);
/************************************************************/
{ FILE *dfx;
char *filename(),ch,getkey();
int r,i;
double t = 1.0;
struct list *ptr,*install(),*deletetoend();
fscanf (dfx,"%ld",&DATSETS);
while (fclose(dfx) != 0)
{
printf ("Waiting to close file");
}
blankline();
return (TRUE);
}
/**********************************************************/
whichgroup ()
{ int n = 0;
return (n);
}
/***********************************************************/
int i;
{ char stat;
if (i==0)
{
stat = 'q';
}
else
{
if (Data[i].datathere)
{
printf ("Insert/Overwrite/Load/Save/Quit?");
stat = getkey();
stat = tolower(stat);
}
else
{
stat = 'i';
}
}
return (stat);
}
/************************************************************/
int grp;
char status;
{ int ctr=0;
struct list *ptr;
CLRSCRN();
printf ("\nStatistical Editor V1.0\n\n");
printf ("\nThis project file contains %d groups.\n",DATSETS);
switch (tolower(status))
{
case 'i' : printf ("INSERT MODE **\n"); break;
case 'o' : printf ("OVERWRITE MODE **\n");
}
NEWLINE();
}
/**********************************************************/
int n;
double old;
{ double correct = 0;
if (correct == 0)
{
return (old);
}
else
{
return(correct);
}
}
/************************************************************/
int i;
sum = num = 0;
return (sum/num);
}
/**************************************************************/
double mean;
int i;
{ double sum,num,var;
struct list *ptr;
sum = num = 0;
for (ptr = Data[i].datptr; ptr != NULL; ptr=ptr->succ)
{
sum += (ptr->value - mean) * (ptr->value - mean);
num ++;
}
/************************************************************/
int i;
return(record);
}
/************************************************************/
/* LEVEL 3 */
/************************************************************/
char *filename ()
{
do
{
printf ("Enter filename : ");
scanf ("%s",FSP);
skipgarb();
}
/************************************************************/
/* Toolkit data structure */
/************************************************************/
*c = 1;
/*************************************************************/
int *ctr,i;
return (p);
}
/*************************************************************/
if (!Data[i].datathere)
{
Data[i].datptr = thispos;
Data[i].datathere = TRUE;
}
else
{
ptr->succ = thispos;
}
thispos->value = t;
thispos->succ = NULL;
return (thispos);
}
/************************************************************/
{
if (ptr != NULL)
{
deletetoend (ptr->succ);
releasestruct (ptr);
}
return (NULL);
}
/************************************************************/
{ char *malloc();
return ((struct list *) malloc(sizeof(struct list)));
}
/***********************************************************/
{
if (free((char *) ptr) != 0)
{
printf ("DEBUG [Z0/TktDtStrct] memory release faliure\n");
}
}
/********************************************************/
/* Toolkit CONSOLE Output */
/********************************************************/
CLRSCRN ()
{
printf ("\f");
}
/*********************************************************/
newline ()
{
printf ("\n");
}
/**********************************************************/
blankline ()
{
printf (" \r");
}
/**********************************************************/
warning ()
{
putchar('\7');
}
/***********************************************************/
/*** Toolkit CONSOLE Input **/
/***********************************************************/
int becareful;
{
if (becareful)
{
printf ("Really quit? (Y/N)\n");
if (yes()) return (TRUE); else return (FALSE);
}
return (TRUE);
}
/*************************************************************/
int becareful;
{
if (becareful)
{
printf ("Are you sure? (Y/N)\n");
if (yes()) return (TRUE); else return (FALSE);
}
return (TRUE);
}
/***********************************************************/
{
while (TRUE)
{
switch (getkey())
{
case 'y' : case 'Y' : return (TRUE);
case 'n' : case 'N' : return (FALSE);
}
}
}
/***********************************************************/
{ char ch;
scanf ("%c",&ch);
skipgarb();
return (ch);
}
/***********************************************************/
int a,b;
{ int p, i = a - 1;
/***********************************************************/
{ double x = 0;
/************************************************************/
{
while (getchar() != '\n');
}
/* end */
Node:Variable Cross Referencer, Previous:Listing stat, Up:Example Programs
chapter
• Listing Cref.c:
• Output of Cross Referencer:
• Commonts on cref.c:
Listing Cref.c
1 /********************************************************/
2 /* */
3 /* C programming utility : variable referencer */
4 /* */
5 /********************************************************/
6
7 /* See notes above */
8
9 #include <stdio.h>
10 #include <ctype.h>
11
12 #define TRUE 1
13 #define FALSE 0
14 #define DUMMY 0
15 #define MAXSTR 512
16 #define MAXIDSIZE 32
17 #define WORDTABLE 33
18
19 int LINECOUNT = 1; /* Contains line no. in file */
20 char BUFFER[MAXIDSIZE]; /* Input BUFFER for IDs */
21 char CH; /* Current input character */
22 char SPECIALCHAR; /* macro/pointer flag */
23
24 /**********************************************************/
25 /* TABLE */
26 /**********************************************************/
27
28 char *WORDTABLE [WORDTABLE] = /* Table of resvd words */
29
30 {
31 "auto" ,
32 "break" ,
33 "case" ,
34 "char" ,
35 "const",
36 "continue",
37 "default" ,
38 "do" ,
39 "double" ,
40 "else" ,
41 "entry" ,
42 "enum" ,
43 "extern" ,
44 "float" ,
45 "for" ,
46 "goto" ,
47 "if" ,
48 "int" ,
49 "long" ,
50 "register",
51 "return" ,
52 "short" ,
53 "signed" ,
54 "sizeof" ,
55 "static" ,
56 "struct" ,
57 "switch" ,
58 "typedef" ,
59 "union" ,
60 "unsigned",
61 "void" ,
62 "volatile",
63 "while" ,
64 };
65
66 /********************************************************/
67 /** STRUCTURES **/
68 /********************************************************/
69
70 struct heap
71
72 {
73 short num;
74 char spec;
75 struct heap *next;
76 };
77
78 /**********************************************************/
79
80 struct BinaryTree
81
82 {
83 char *name;
84 struct heap *line;
85 struct BinaryTree *left;
86 struct BinaryTree *right;
87 }
88
89 *tree = NULL;
90
91 /**********************************************************/
92 /* LEVEL 0 : main program */
93 /**********************************************************/
94
95 main ()
96
97 { FILE *fp;
98 char *filename();
99 struct BinaryTree *CloseDataStruct();
100
101 printf ("\nIdentifier Cross Reference V 1.0\n\n");
102 if ((fp = fopen (filename(),"r")) == NULL)
103 {
104 printf ("Can't read file .. Aborted!\n\n");
105 exit(0);
106 }
107 CH = getc(fp);
108
109 while (!feof(fp))
110 {
111 SkipBlanks (fp);
112 RecordWord (fp);
113 }
114
115 listIDs (tree);
116 CloseDataStruct(tree);
117 printf ("\n%d lines in source file\n",LINECOUNT);
118 }
119
120 /**********************************************************/
121 /* LEVEL 1 */
122 /**********************************************************/
123
124 SkipBlanks (fp) /* Skip irrelevant characters */
125
126 FILE *fp;
127
128 {
129
130 while (!feof(fp))
131
132 {
133 if (iscsymf(CH))
134 {
135 return(DUMMY);
136 }
137 else
138 {
139 ParticularSkip(fp);
140 }
141 }
142 }
143
144 /**********************************************************/
145
146 RecordWord (fp) /* get ID in buffer & tube it to data */
147
148 FILE *fp;
149
150 { int tok;
151
152 CopyNextID (fp);
153
154 if ((tok = token()) == 0) /* if not resved word */
155 {
156 RecordUserID(isfunction(fp));
157 }
158
159 SPECIALCHAR = ' ';
160 }
161
162 /**********************************************************/
163
164 listIDs (p) /* List Binary Tree */
165
166 struct BinaryTree *p;
167
168 { struct heap *h;
169 int i = 0;
170
171 if (p != NULL)
172 {
173 listIDs (p->left);
174 printf ("\n%-20s",p->name);
175
176 for (h = p->line; (h != NULL); h = h->next)
177 {
178 printf ("%c%-5d",h->spec,h->num);
179 if ((++i % 8) == 0)
180 {
181 printf ("\n ");
182 }
183 }
184
185 printf ("\n");
186 listIDs (p->right);
187 }
188 }
189
190 /*********************************************************/
191
192 struct BinaryTree *CloseDataStruct (p) /* Recursive! */
193
194 struct BinaryTree *p;
195
196 {
197 if (p->left != NULL)
198 {
199 CloseDataStruct(p->left);
200 }
201 else if (p->right != NULL)
202 {
203 CloseDataStruct(p->right);
204 }
205
206 deleteheap(p->line);
207 releasetree(p);
208 return (NULL);
209 }
210
211 /*********************************************************/
212 /* LEVEL 2 */
213 /*********************************************************/
214
215 ParticularSkip (fp) /* handle particular characters */
216
217 FILE *fp;
218
219 { char c;
220
221 switch (CH)
222
223 {
224 case '/' : if ((c = getc(fp)) == '*')
225 {
226 skipcomment (fp);
227 }
228 else
229 {
230 CH = c;
231 return (DUMMY);
232 }
233 break;
234
235 case '"' : if (skiptochar (fp,'"') > MAXSTR)
236 {
237 printf ("String too long or unterminated ");
238 printf ("at line %d\n",LINECOUNT);
239 exit (0);
240 }
241 break;
242
243 case '\'': if (skiptochar (fp,'\'') == 1)
244 {
245 if (CH=='\'') CH = getc(fp);;
246 }
247 break;
248
249 case '#' : skiptochar(fp,' ');
250 SPECIALCHAR = '#';
251 break;
252
253 case '\n': ++LINECOUNT;
254 default : CH = getc(fp);
255 SPECIALCHAR = ' ';
256 }
257 }
258
259 /*********************************************************/
260
261 CopyNextID (fp) /* Put next identifier into BUFFER */
262
263 FILE *fp;
264
265 { int i = 0;
266
267 while (!feof(fp) && (iscsym (CH)))
268 {
269 BUFFER[i++] = CH;
270 CH = getc (fp);
271 }
272
273 BUFFER[i] = '\0';
274 }
275
276 /**********************************************************/
277
278 token () /* Token: pos in WORDTABLE */
279
280 { int i;
281
282 for (i = 0; i < WORDTABLE; i++)
283 {
284 if (strcmp(&(BUFFER[0]),WORDTABLE[i]) == 0)
285 {
286 return(i);
287 }
288 }
289 return(0);
290 }
291
292 /*********************************************************/
293
294 RecordUserID (fnflag) /* check ID type & install data */
295
296 int fnflag;
297
298 { char *strcat();
299 struct BinaryTree *install();
300
301 if (fnflag)
302 {
303 strcat (BUFFER,"()");
304 tree = install (tree);
305 }
306 else
307 {
308 tree = install (tree);
309 }
310 }
311
312 /**********************************************************/
313
314 isfunction (fp) /* returns TRUE if ID is a fn */
315
316 FILE *fp;
317
318 {
319 while(!feof(fp))
320 {
321 if (!(CH == ' ' || CH == '\n'))
322 {
323 break;
324 }
325 else if (CH == '\n')
326 {
327 ++LINECOUNT;
328 }
329 CH = getc(fp);
330 }
331
332 if (CH == '(')
333 {
334 return (TRUE);
335 }
336 else
337 {
338 return (FALSE);
339 }
340 }
341
342 /**********************************************************/
343
344 deleteheap (h) /* Release back to free memory pool */
345
346 struct heap *h;
347
348 { struct heap *temp = h;
349
350 while (h!=NULL && temp!=NULL)
351 {
352 temp = h->next;
353 releaseheap(h);
354 h = temp;
355 }
356 }
357
358 /**********************************************************/
359 /** LEVEL 3 **/
360 /**********************************************************/
361
362 skipcomment (fp) /* skip to char after comment */
363
364 FILE *fp;
365
366 { char cs = 'x';
367
368 for (CH = getc(fp); !feof(fp); CH = getc(fp))
369 {
370 switch (CH)
371 {
372 case '\n': ++LINECOUNT;
373 break;
374 case '/' : if (cs == '*')
375 {
376 CH = getc(fp);
377 return(DUMMY);
378 }
379 }
380 cs = CH;
381 }
382 }
383
384 /*********************************************************/
385
386 skiptochar (fp,ch) /* skip to char after ch */
387
388 FILE *fp;
389 char ch;
390
391 { int c=0;
392
393 while (((CH =getc(fp)) != ch) && !feof(fp))
394 {
395 if (CH == '\n')
396 {
397 ++LINECOUNT;
398 }
399 c++;
400 }
401
402 CH = getc(fp);
403 return (c);
404 }
405
406 /*********************************************************/
407
408 struct BinaryTree *install (p) /* install ID in tree */
409
410 struct BinaryTree *p;
411
412 { struct heap *pushonheap();
413 struct BinaryTree *newtree();
414 char *stringin();
415 int pos;
416
417 if (p == NULL) /* new word */
418 {
419 p = newtree();
420 p->name = stringin(BUFFER);
421 p->line = pushonheap (NULL);
422 p->left = NULL;
423 p->right = NULL;
424 return (p);
425 }
426
427 if ((pos = strcmp (BUFFER,p->name)) == 0) /* found word*/
428 {
429 p->line = pushonheap(p->line);
430 return (p);
431 }
432
433 if (pos < 0) /* Trace down list */
434 {
435 p->left = install(p->left);
436 }
437 else
438 {
439 p->right = install(p->right);
440 }
441
442 return (p);
443 }
444
445 /*********************************************************/
446 /* LEVEL 4 */
447 /*********************************************************/
448
449 struct heap *pushonheap (h) /* push nxt ln no.to heap */
450
451 struct heap *h;
452
453 { struct heap *hp,*newheap();
454
455 hp = newheap();
456 hp->num = LINECOUNT;
457 hp->spec = SPECIALCHAR;
458 hp->next = h;
459
460 return (hp);
461 }
462
463 /*********************************************************/
464 /* TOOLKIT file input */
465 /*********************************************************/
466
467 backone (ch,fp) /* backspace one in file */
468
469 char ch;
470 FILE *fp;
471
472 {
473 if (ungetc(ch,fp) != ch)
474 {
475 printf ("\nDebug: Toolkit file input: backone()
FAILED\n");
476 exit(0);
477 }
478 }
479
480 /**********************************************************/
481 /* TOOLKIT stdin */
482 /**********************************************************/
483
484 char *filename ()
485
486 { static char *fsp = ".................................";
487
488 do
489 {
490 printf ("Enter filename of source program: ");
491 scanf ("%33s",fsp);
492 skipgarb ();
493 }
494 while (strlen(fsp) == 0);
495 return (fsp);
496 }
497
498 /*********************************************************/
499
500 skipgarb () /* skip garbage upto end of line */
501
502 {
503 while (getchar() != '\n');
504 }
505
506 /**********************************************************/
507 /* TOOLKIT data structure */
508 /**********************************************************/
509
510 char *stringin (array) /* cpy str in arry to ptr loc*/
511
512 char *array;
513
514 { char *malloc(),*ptr;
515 int i;
516
517 ptr = malloc (strlen(array)+1);
518 for (i = 0; array[i] != '\0'; ptr[i] = array[i++]);
519 ptr[i] = '\0';
520 return(ptr);
521 }
522
523 /**********************************************************/
524
525 struct heap *newheap ()
526
527 { char *malloc ();
528 return ((struct heap *) malloc(sizeof(struct heap)));
529 }
530
531 /**********************************************************/
532
533 struct BinaryTree *newtree ()
534
535 { char *malloc ();
536 return ((struct BinaryTree *) malloc(sizeof(struct
BinaryTree)));
537 }
538
539 /*********************************************************/
540
541 releaseheap (ptr)
542
543 struct heap *ptr;
544
545 {
546 if (free((char *) ptr) != 0)
547
548 {
549 printf ("TOOLKIT datastruct: link release failed\n");
550 }
551 }
552
553 /**********************************************************/
554
555 releasetree (ptr)
556
557 struct BinaryTree *ptr;
558
559 {
560 if (free((char *) ptr) != 0)
561
562 {
563 printf ("TOOLKIT datastruct: link release failed\n");
564 }
565 }
566 /* end */
567
568
WORDTABLE 284 28
backone() 467
fopen() 102
getchar() 503
iscsym() 267
iscsymf() 133
main() 95
MAXIDSIZE 20 #16
scanf() 491
ungetc() 473
Comments
This simplified program could be improved in a number of ways. Here are some
suggestions for improvement:
• The program could determine whether an identifier was of type pointer or not
and, if so, label the line number with a *, e.g. *123 342 *1234
• At present the program only marks macros with a # symbol on the line at
which they are defined. It could be made to mark them at every line, so that
#undef-ined symbols and variables were clearly distinguished.
Mistakes!
Debugging can be a difficult process. In many cases compiler errors are not generated
because the actual error which was present but because the compiler got out of step.
Often the error messages give a completely misleading impression of what has gone
wrong. It is useful therefore to build a list of errors and probable causes personally.
These few examples here should help beginners get started and perhaps give some
insight into the way C works.
Missing semicolon;
A missing semicolon is easily trapped by the compiler. Every statement must end with
a semi colon. A compound statement which is held in curly braces seldom needs a
semi colon to follow.
statement;
but:
{
}; <-- This semi colon is only needed if the curly
braces enclose a type declaration or an
initializer for static array/structure etc.
Node:Missing brace, Next:Mistyping Upper Lower Case, Previous:Missing
semicolon;, Up:Compiler Trappable Errors
This error is harder to spot and may cause a whole host of irrelevant and incorrect
errors after the missing brace. Count braces carefully. One way to avoid this is to
always fill braces in before the statements are written inside them. So write
and fill in the statements afterwards. Often this error will generate a message like
`unexpected end of file' because it is particularly difficult for a compiler to diagnose.
C distinguishes between small and capital letters. If a program fails at the linking
stage because it has found a reference to a function which had not been defined, this is
often the cause.
If a quote is missed out of a statement containing a string then the compiler will
usually signal this with a message like:
This means that a variable is used which has not first been declared, or that a variable
is used outside of its sealed capsule.
If abs (x) is a macro and not a function then the following are incorrect:
abs (function());
abs (x = function());
Only a single variable can be substituted into a macro. This error might generate
something like "lvalue required".
All functions return values of int by default. If it is required that they return another
type of variable, this must by declared in two places: a) in the function which calls the
new function, along with the other declarations:
CallFunction ()
The function1() is type char; function2() is type pointer to char. This must also
be declared where the function is defined:
char function1 ()
{
}
and
char *function2()
{
}
This error might result in the message "type mismatch" or "external variable/function
type/attribute mismatch"
There is a rule in C that all maths operations have to be performed with long
variables. These are
int
long int
double
long float
The result is also a long type. If the user forgets this and tries to use short C
automatically converts it into long form. The result cannot therefore be assigned to a
short type afterwards or the compiler will complain that there is a type mismatch. So
the following is wrong:
short i,j = 2;
i = j * 2;
If a short result is required, the cast operator has to be used to cast the long result to be
a short one.
short i,j = 2;
i = (short) j * 2;
Confusion of = and ==
if (a = 0)
{
}
is valid C, but notice that = is the assignment operator and not the equality operator
==. It is legal to put an assignment inside the if statement (or any other function) and
the value of the assignment is the value being assigned! So writing the above would
always give the result zero (which is `FALSE' in C) so the contents of the braces {}
would never be executed. To compare a to zero the correct syntax is:
if (a == 0)
{
}
This error can often be trapped by a compiler, but not in all cases. The arguments of
the scanf statement must be pointers or addresses of variables, not the contents of the
variables themselves. Thus the following is wrong:
int i;
char ch;
int i;
char;
int *i;
char *ch;
Including the & now would be wrong. If this error is trappable then it will be
something like "Variable is not a pointer".
In many cases these two forms are identical. However, if they are hidden inside
another statement e.g.
array [C++] = 0;
C stores arrays in rows, and as far as the language is concerned the storage locations
are next to one another in one place up to the end of the array. This might not be
exactly true, in general. A program will be loaded into one or more areas (where ever
the operating system can find space) and new variable space will be found wherever it
is available, but this will not generally be in whole blocks `side by side' in the
memory. The following sort of construction only works for simple data types:
char array[10];
*array = 0;
*(array + 1) = 0;
...
*(array + 10) = 0;
While it is true that the variable "array" used without its square brackets is a pointer to
the first element of the array, it is not necessarily true that the array will necessarily be
stored in this way. Using:
char array[10];
array[0] = 0;
array[1] = 0;
...
array[10] = 0;
is safe. When finding a pointer to, say, the third element, you should not assume that
Do not assume that the size of a structure is the sum of the sizes of its parts! There
may be extra data inside for operating system use or for implementation reasons, like
aligning variables with particular addresses.
This problem can be avoided in ANSI C and C++ but not in K&R C. When passing
values to a function the compiler will not spot whether you have the wrong number of
parameters in a statement, provided they are all of the correct type. The values which
are assumed for missing parameters cannot be guaranteed. They are probably garbage
and will most likely spoil a program.
Incorrect I/O is can be the result of poorly matched conversion strings in I/O
statements. These are wrong:
float x; float x;
should be
scanf ("%d",&x); scanf ("%f",&x);
or even:
double x; float x;
should perhaps be
scanf ("%f",&x); scanf("%ld",&x);
Another effect which can occur if the conversion specifier is selected as being long
when it the variable is really short is that neighbouring variables can receive the scanf
values instead! For instance if two variables of the same type happen to be stored next
to each other in the memory:
short i,j;
--------------------------------------
| | |
--------------------------------------
i j
and the user tries to read into one with a long int value, scanf will store a long int
value, which is the size of two of these short variables. Suppose the left hand box
were i and the right hand box were j and you wanted to input the value of i: instead
of getting:
--------------------
| 002345 | |
--------------------
i j
0000000000000002345
as
------------------------
| 000000000 | 0000002345 |
------------------------
i j
because the value was long, but this would mean that the number would over flow out
of i into j and in fact j might get the correct value and i would be set to zero!! Check
the conversion specifiers!!
Often when working with characters one also wants to know their ASCII values. If
characters/integers are passed as parameters it is easy to mistype char for int etc..
The compiler probably won't notice this because no conversion is needed between
int and char. Characters are stored by their ASCII values. On the other hand if the
declaration is wrong:
function (ch)
int (ch);
{
}
type array[5];
and the you allow the program to write to array[6] or more, C will not complain.
However the computer might! In the worst case this could cause the program to crash.
Mathematical Error
Global variables and recursion should not be mixed. Most recursive routines work
only because they are sealed capsules and what goes on inside them can never affect
the outside world. The only time that recursive functions should alter global storage is
when the function concerned operates on a global data structure. Consider a recursive
function:
int GLOBAL;
recursion ()
{
if (++GLOBAL == 0)
{
return (0);
}
This function is treading a fine line between safety and digging its own recursive
grave. All it would take to crash the program, would be the careless use of GLOBAL in
the function alterGLOBAL() and the function would never be able to return. The
stack would fill up the memory and the program would plunge down an unending
recursive well.
Tracing Errors
• Locating a problem:
Locating a problem
Complex bugs can be difficult to locate. Here are some tips for fault finding:
1. Try to use local variables, in preference to global ones for local duties. Never
rely on global variables for passing messages between functions.
2. Check variable declarations and missing parameters.
3. Check that a program has not run out of private memory. (If it repeatedly
crashes for no apparent reason, this could be a cause.) Make the program stack
size bigger if that is possible.
4. Use statements like printf("program is now here") to map out the progress of a
program and to check that all function calls are made correctly.
5. Use statements like ch = getchar() to halt a program in certain places and to
find out the exact location at which things go wrong.
6. Try "commenting out" lines of suspect code. In other words: put comment
markers around lines that you would like to eliminate temporarily and then
recompile to pinpoint errors.
7. Check that the compiler disk has not been corrupted (make a new copy) -
getting desperate now!
8. Try retyping the program, or using a filter which strips out any illegal
characters which might have found their way into a program.
9. Get some sleep! Hope the problem has gone away in the morning.
Failing these measures, try to find someone who programs in C regularly on the
computer system concerned.
Pathological Problems
Problems which defy reasonable explanations are called pathological or `sick'.
Sometimes these will be the result of misconceptions about C functions, but
occasionally they may be the result of compiler bugs, or operating system design
peculiarities. Consider the following example which was encountered while writing
the simple example in the chapter on Files and Devices, subsection `Low Level File
Handling': in that program a seemingly innocent macro defined by
caused the C library functions creat() and remove() to fail is remarkable ways on
an early Amiga C compiler! The problem was that a single call to CLRSCRN() at the
start of the function DelFile() caused both of the library functions (in very different
parts of the program) above to make recursing function calls the function DelFile().
The deletion of CLRSCRN() cured the problem entirely! In general it is worth checking
carefully the names of all functions within a program to be sure that they do not
infringe upon library functions. For example, read() and write() are names which
everyone wishes to use at some point, but they are the names of standard library
functions, so they may not be used. Even capitalizing (Read() / Write()) might not
work: beware that special operating system libraries have not already reserved these
words as library commands.
It is almost impossible to advise about these errors. A programmer can only hope to
try to eliminate all possibilities in homing in on the problem. To misquote Sherlock
Holmes: "At the end of the day, when all else fails and the manuals are in the waste
paper basket, the last possibility, however improbable, has to be the truth."
Node:Porting Programs between computers, Next:Qu, Previous:Pathological
Problems, Up:Errors and debugging
• Assumptions about the size of data objects such as int and float can be risky.
• Check conversion characters in printf() and scanf() as some compilers
choose slightly different conventions for these.
• The stack size for (memory available to) a program is likely to vary between
systems. This can cause errors at run time if a program runs out of space, even
though there is nothing wrong with the code.
• Check for functions which rely on the speed of a particular computer. For
example, pause() or wait loops. Some computers may scarcely notice counting
to 50000, whereas others may labour at it for some time!
• Check for assumptions made about filenames. e.g. limited/unlimited size,
valid characters etc..
Questions
Spot the errors in the following:
1. function (string,i)
2.
3. {
4. char *string;
5. int i;
6.
7. }
8.
9.
10. while (a < b)
11. {
12. while (b == 0)
13. {
14. printf ("a is negative");
15. }
16.
17.
18. struct Name
19. {
20. int member1;
21. int member2;
22. }
23.
24.
Summary of C
• Reserved:
• Preprocessor Directives:
• Header Files and Libraries:
• Constants:
• Primitive Data Types:
• Storage Classes:
• Identifiers:
• Statements:
• Character Utilities:
• Special Control Characters:
• Input/Output Functions:
• print conversions:
• scanf conversions:
• Maths Library Summary:
• goto:
Reserved Words
auto
storage class specifier (declaration)
break
statement (escape from switch or loop)
case
option prefix within switch statement
char
typename
continue
statement (branch to start of next loop)
default
option in switch statement
do
statement
double
typename
else
statement
entry
(reserved for the future use)
extern
storage class specifier
float
typename
for
statement
goto
goto label
if
statement
int
typename
long
typename
register
storage class specifier
return
functional statement
short
typename
sizeof
compile time operator
static
storage class specifier
struct
partial typename
switch
statement
typedef
statement
union
partial typename
unsigned
typename
while
statement
enum
partial typename: ordinal types only
void
typename
const
storage class specifier(no storage allocated)
signed
typename
volatile
storage class specifier
Node:Preprocessor Directives, Next:Header Files and Libraries, Previous:Reserved,
Up:Summary
Preprocessor Directives
#include
include file for linking
#define
define a preprocessor symbol/macro
#undef
un-define a previously defnined symbol
#if
test for conditional compilation
#ifdef
(ditto)
#ifndef
(ditto)
#else
(ditto)
#endif
(ditto)
#line
debug tool
#error
debug tool
Node:Header Files and Libraries, Next:Constants, Previous:Preprocessor Directives,
Up:Summary
stdio.h
Standard I/O (libc).
ctype.h
Macro for character types.
math.h
Mathematical definitions (libm)
Node:Constants, Next:Primitive Data Types, Previous:Header Files and Libraries,
Up:Summary
Constants
Integer
Characters 0..9 only
Octal
Prefix 0 (zero) chars 0..7 only
Hexadecimal
Prefix 0x (zero ex) chars a..f A..f 0..9
Explicit Long
Integer/Octal or Hexadecimal types can be declared long by writing L
immediately after the constant.
Character
Declared in single quotes e.g. 'x' '\n'
Float
Characters 0..0 and one "." May also use scientific notation exponents with e
or E preceding them. e.g. 2.14E12 3.2e-2
Strings
String constants are written in double quotes e.g. "This is a string" and
have type pointer to character.
Node:Primitive Data Types, Next:Storage Classes, Previous:Constants, Up:Summary
Storage Classes
auto
Local variable (redundant keyword)
const
No variable allocated, value doesn't change
extern
Variable is defined in another file
static
Value is preserved between function calls
register
Stored in a register, if possible
volatile
Value can be changed by agents outside the program.
Node:Identifiers, Next:Statements, Previous:Storage Classes, Up:Summary
Identifiers
Idenitifiers may contain the characters: 0..9, A..Z, a..z and _ (the underscore
character). Identifiers may not begin with a number. (The compiler assumes that an
object beginning with a number is a number.)
a = 6;
printf ("I love C because...");
{
a = 6;
}
{ int a;
a = 6;
printf ("I love C because...");
}
Character Utilities
char ch;
isalpha(ch)
Is alphabetic a..z A..Z
isupper(ch)
Is upper case
islower(ch)
Is lower case
isdigit(ch)
Is in the range 0..9
isxdigit(ch)
Is 0..9 or a..f or A..F
isspace(ch)
Is white space character (space/newline/tab)
ispunct(ch)
Is punctuation or symbolic
isalnum(ch)
Is alphanumeric (alphavetic or number)
isprint(ch)
Is printable on the screen (and space)
isgraph(ch)
If the character is printable (not space)
iscntrl(ch)
Is a control character (not printable)
isascii(ch)
Is in the range 0..127
iscsym(ch)
Is a valid character for a C identifier
toupper(ch)
Converts character to upper case
tolower(ch)
Converts character to lower case
toascii(ch)
Converts character to ascii (masks off top bit)
Node:Special Control Characters, Next:Input/Output Functions, Previous:Character
Utilities, Up:Summary
\b
backspace BS
\f
form feed FF (also clear screen)
\n
new line NL (like pressing return)
\r
carriage return CR (cursor to start of line)
\t
horizontal tab HT
\v
vertical tab (not all versions)
\"
double quotes (not all versions)
\'
single quote character '
\\
backslash character \
\ddd
character ddd where ddd is an ASCII code given in octal or base 8. (See
Appendix C)
Node:Input/Output Functions, Next:print conversions, Previous:Special Control
Characters, Up:Summary
Input/Output Functions
printf ()
Formatted printing
scanf ()
Formatted input analysis
getchar()
Get one character from stdin file buffer
putchar()
Put one charcter in stdout file buffer
gets ()
Get a string from stdin
puts ()
Put a string in stdout
fprintf()
Formatted printing to general files
fscanf()
Formatted input from general files
fgets()
Get a string from a file
fputs()
Put a string in a file
fopen()
Open/create a file for high level access
fclose()
Close a file opened by fopen()
getc()
Get one character from a file (macro?)
ungetc();
Undo last get operation
putc()
Put a character to a file (macro?)
fgetc()
Get a character from a file (function)
fputc()
Put a character from a file (function)
feof()
End of file . returns true or false
fread()
Read a block of characters
fwrite()
Write a block of characters
ftell()
Returns file position
fseek()
Finds a file position
rewind()
Moves file position to the start of file
fflush()
Empties file buffers
open()
Open a file for low level use
close()
Close a file opened with open()
creat()
Create a new file
read()
Read a block of untranslated bytes
write()
Write a block of untranslated bytes
rename()
Rename a file
unlink()
Delete a file
remove()
Delete a file
lseek()
Find file position
Node:print conversions, Next:scanf conversions, Previous:Input/Output Functions,
Up:Summary
The letter l (ell) can be prefixed before these for long types.
d
Denary integer
ld
Long int
x
Hexadecimal integer
o
Octal integer
h
Short integer
f
Float type
lf
Long float or double
e
Float type
le
Double
c
Single character
s
Character string
Node:Maths Library Summary, Next:goto, Previous:scanf conversions, Up:Summary
Maths Library
These functions require double parameters and return double values unless otherwise
stated. It is important to include math.h.
ABS(x)
Return absolute (unsigned) value. (macro)
fabs(x)
Return absolute (unsigned) value. (Function)
ceil(x)
Rounds up a "double" variable
floor(x)
Rounds down (truncates) a "double" variable.
exp(x)
Find exponent
log(x)
Find natural logarithm
log10(x)
Find logarithm to base 10
pow(x,y)
Raise x to the power y
sqrt(x)
Square root
sin(x)
Sine of (x in radians)
cos(x)
Cosine of (x in radians)
tan(x)
Tangent of (x in radians)
asin(x)
Inverse sine of x in radians
acos(x)
Inverse cosine of x in radians
atan(x)
Inverse tangent of x in radians
atan2(x,y)
Inverse tangent of x/y in radians
sinh(x)
Hyperbolic sine
cosh(x)
Hyperbolic cosine
tanh(x)
Hyperbolic tangent
Node:goto, Previous:Maths Library Summary, Up:Summary
goto
This word is redundant in C and encourages poor programming style. For this reason
it has been ignored in this book. For completeness, and for those who insist on using
it (may their programs recover gracefully) the form of the goto statement is as
follows:
goto label;
label is an identifier which occurs somewhere else in the given function and is
defined as a label by using the colon:
Please note that this list is somewhat misleading. Many more words are out of bounds.
This is because most of the facilities which C offers are in libraries that are included
in programs. Once a library has been included in a program, its functions are defined
and you cannot use their names yourself.
C requires all of these reserved words to be in lower case. (This does mean that, typed
in upper case, the reserved words could be used as variable names, but this is not
recommended.)
auto d if
break int d
case long d
char d register d
continue return
default short d
do sizeof
double d static d
else struct
entry switch
extern d typedef d
float d union d
for unsigned d
goto while
enum d
void d
const d
signed d
volatile d
Node:Comparisons, Next:Character Conversion Table, Previous:reserved words list,
Up:Top
C Pascal BASIC
= := =
== = =
} end; NEXT x
do N/A N/A
{
}
while (..);
* ^ ? ! $
The conditional expressions if and switch are essentially identical to Pascal's own
words if and case but there is no redundant "then". BASIC has no analogue of the
switch construction. The loop constructions of C are far superior to those of either
BASIC or Pascal however. Input and Output in C is more flexible than Pascal, though
correspondingly less robust in terms of program crashability. Input and Output in C
can match all of BASICs string operations and provide more, though string variables
can be more awkward to deal with.
0 0 0 CTRL-@
1 1 1 CTRL-A
2 2 2 CTRL-B
3 3 3 CTRL-C
4 4 4 CTRL-D
5 5 5 CTRL-E
6 6 6 CTRL-F
7 7 7 CTRL-G
8 10 8 CTRL-H
9 11 9 CTRL-I
10 12 A CTRL-J
11 13 B CTRL-K
12 14 C CTRL-L
13 15 D CTRL-M
14 16 E CTRL-N
15 17 F CTRL-O
16 20 10 CTRL-P
17 21 11 CTRL-Q
18 22 12 CTRL-R
19 23 13 CTRL-S
20 24 14 CTRL-T
21 25 15 CTRL-U
22 26 16 CTRL-V
23 27 17 CTRL-W
24 30 18 CTRL-X
25 31 19 CTRL-Y
26 32 1A CTRL-Z
27 33 1B CTRL-[
28 34 1C CTRL-\
29 35 1D CTRL-]
30 36 1E CTRL-^
31 37 1F CTRL-_
32 40 20
33 41 21 !
34 42 22 "
35 43 23 #
36 44 24 $
37 45 25 %
38 46 26 &
39 47 27 '
40 50 28 (
41 51 29 )
42 52 2A *
43 53 2B +
44 54 2C ,
45 55 2D -
46 56 2E .
47 57 2F /
48 60 30 0
49 61 31 1
50 62 32 2
51 63 33 3
52 64 34 4
53 65 35 5
54 66 36 6
55 67 37 7
56 70 38 8
57 71 39 9
58 72 3A :
59 73 3B ;
60 74 3C <
61 75 3D =
62 76 3E >
63 77 3F ?
64 100 40 @
65 101 41 A
66 102 42 B
67 103 43 C
68 104 44 D
69 105 45 E
70 106 46 F
71 107 47 G
72 110 48 H
73 111 49 I
74 112 4A J
75 113 4B K
76 114 4C L
77 115 4D M
78 116 4E N
79 117 4F O
80 120 50 P
81 121 51 Q
82 122 52 R
83 123 53 S
84 124 54 T
85 125 55 U
86 126 56 V
87 127 57 W
88 130 58 X
89 131 59 Y
90 132 5A Z
91 133 5B [
92 134 5C \
93 135 5D ]
94 136 5E ^
95 137 5F _
96 140 60 `
97 141 61 a
98 142 62 b
99 143 63 c
100 144 64 d
101 145 65 e
102 146 66 f
103 147 67 g
104 150 68 h
105 151 69 i
106 152 6A j
107 153 6B k
108 154 6C l
109 155 6D m
110 156 6E n
111 157 6F o
112 160 70 p
113 161 71 q
114 162 72 r
115 163 73 s
116 164 74 t
117 165 75 u
118 166 76 v
119 167 77 w
120 170 78 x
121 171 79 y
122 172 7A z
123 173 7B {
124 174 7C |
125 175 7D }
126 176 7E ~
127 177 7F DEL
Node:Emacs style file, Next:Answers to questions, Previous:Character Conversion
Table, Up:Top
;;;
;;; C, perl and C++ indentation, Burgess style. (Thomas Sevaldrud)
;;;
(defconst burgess-c-style
'((c-tab-always-indent . t)
(c-hanging-braces-alist . ((substatement-open before
after)
(brace-list-open)))
(c-hanging-colons-alist . ((member-init-intro before)
(inher-intro)
(case-label after)
(label after)
(access-label after)))
(c-cleanup-list . (scope-operator))
(c-offsets-alist . ((arglist-close . c-lineup-
arglist)
(defun-block-intro . 1)
(substatement-open . 3)
(statement-block-intro . 0)
(topmost-intro . -1)
(case-label . 0)
(block-open . 0)
(knr-argdecl-intro . -)))
;(c-echo-syntactic-information-p . t)
)
"Burgess Programming Style")
;;;
;;; Lite hack for å slippe å skrive inn kompileringskommandoen i c,
;;; (hvis ikke Makfile eksisterer)
;;; samt en fancy heading hvis det er en ny fil.
;;;
(add-hook 'c-mode-hook
(lambda ()
; (local-set-key "\C-c\C-c" 'compile)
(cond ((not (file-exists-p (buffer-file-name)))
(insert-string
(concat "/*" (make-string 75 ?*) "*/\n"
"/*" (make-string 75 ? ) "*/\n"
(format "/* File: %-67s */\n" (buffer-
name))
"/*" (make-string 75 ? ) "*/\n"
(format "/* Created: %-64s */\n" (current-
time-string))
"/*" (make-string 75 ? ) "*/\n"
"/* Author:
> */\n"
"/*" (make-string 75 ? ) "*/\n"
"/* Revision: $Id$
*/\n"
"/*" (make-string 75 ? ) "*/\n"
"/* Description:
*/\n"
"/*" (make-string 75 ? ) "*/\n"
"/*" (make-string 75 ?*) "*/\n"
"\n#include <stdio.h>\n"))))
(outline-minor-mode 1)
(or (file-exists-p "makefile")
(file-exists-p "Makefile")
(set (make-local-variable 'compile-command)
(concat "gcc -o "
(substring
(file-name-nondirectory buffer-file-
name)
0
(string-match
"\\.c$"
(file-name-nondirectory buffer-file-
name)))
" "
(file-name-nondirectory buffer-file-
name))))))
;;;
;;; Samme for C++
(add-hook 'c++-mode-hook
(lambda ()
; (local-set-key "\C-c\C-c" 'compile)
(cond ((not (file-exists-p (buffer-file-name)))
(insert-string
(concat "/*" (make-string 75 ?=) "*/\n"
"/*" (make-string 75 ? ) "*/\n"
(format "/* File: %-67s */\n" (buffer-
name))
"/*" (make-string 75 ? ) "*/\n"
(format "/* Created: %-64s */\n" (current-
time-string))
"/*" (make-string 75 ? ) "*/\n"
"/* Author:
*/\n"
"/*" (make-string 75 ? ) "*/\n"
"/* Revision: $Id$
*/\n"
"/*" (make-string 75 ? ) "*/\n"
"/* Description:
*/\n"
"/*" (make-string 75 ? ) "*/\n"
"/*" (make-string 75 ?=) "*/\n"
"/*
*/\n"
"/*" (make-string 75 ?=) "*/\n"
"\n#include <iostream.h>\n"))))
(outline-minor-mode 1)
(or (file-exists-p "makefile")
(file-exists-p "Makefile")
(set (make-local-variable 'compile-command)
(concat "g++ -o "
(substring
(file-name-nondirectory buffer-file-
name)
0
(string-match
"\\.C$"
(file-name-nondirectory buffer-file-
name)))
" "
(file-name-nondirectory buffer-file-
name))))))
'(lambda()
(setq perl-indent-level 0)
(setq perl-continued-statement-offset 3)
(setq perl-continued-brace-offset -3)
(setq perl-brace-offset 3)
(setq perl-brace-imaginary-offset 0)
(setq perl-label-offset -3)
(define-key perl-mode-map "\C-m" 'newline-and-indent)
)
( setq java-mode-hook
'(lambda()
(setq java-indent-level 0)
(setq java-continued-statement-offset 3)
(setq java-continued-brace-offset -4)
(setq java-brace-offset 3)
(setq java-brace-imaginary-offset 0)
(setq java-label-offset -4)
(setq java-statement-block-intro . +)
(setq java-knr-argdecl-intro . 3)
(setq java-substatement-open . 0)
(setq java-label . 0)
(setq java-statement-case-open . 0)
(setq java-statement-cont . 0)
Answers to questions
Chapter 1
4) NO!
Chapter 3
Chapter 4
3) accounts.c
Chapter 5
2) stdio.h
3) No. Only macro names can be used if the header file is not
included.
4) Header file.
Chapter 7
Chapter 8
1) The compiler thinks the rest of the program is all one comment!
Chapter 9
1) function (a,b)
int a,b;
{
return (a*b);
}
2) No.
3) The value is discarded.
5) By using "return".
Chapter 10
2) a,c,f
3) int i,j;
5) int can have values + or -. Unsigned can only be + and can hold
slightly larger + values than int.
6) I = 67;
7) int
9) printf ("%d",(int)23.1256);
10) No.
Chapter 11
function (...)
<-- here
{
}
3) Yes.
4) No and it is illegal.
5) * means "the contents of" and & means "the address of"
6) No.
Chapter 12
3) Local variables cannot leak out. Nothing outside them can reach
local variables.
4) Variable parameters do. Value parameters use their own local
copies, so they do not.
5) int i,j;
main ()
{ float x,y;
another(x,y);
}
another(x,y)
float x,y;
{
}
Chapter 13
1) #define birthday 19
2) #include <math.h>
3) false
4) false
Chapter 14
Chapter 15
printf
1) #include <stdio.h>
main ()
{
printf ("%2e",6.23);
}
3) a) No conversion string
b) Conversion string without matching value
c) Probably nothing
d) Conversion string without matching value
scanf
5) true.
2) ch = getchar();
putchar (ch);
Chapter 16
3) rem = 5 % 2;
4) variable = 10 - -5;
5) if (1 != 23)
{
printf ("Thank goodness for mathematics");
}
Chapter 18
3) do..while
4) #include <stdio.h>
#define TRUE 1
main ()
{ char ch;
while (true)
{
ch = getchar();
putchar (ch);
}
Chapter 19
3) double array[4][5];
Valid array bounds from array[0][0] to array[3][4]
Chapter 20
Chapter 22
1) double
Chapter 23
Chapter 23
2) FILE *fp;
3) False. They are meant for comparitive purposes only. It does not
make
sense to do arithmetic with enumerated data.
5) volatile
6) typedef double real;
7) True.
Chapter 24
Chapter 26
3) fp = fopen ("filename","r");
4) fd = open ("filename",O_WRONLY);
6) fprintf ()
Chapter 27
3) x.mem
4) ptr->mem
5) False.
Chapter 28
1) A diagram which shows how structures are put together.
2) With pointers.
Chapter 29
Chapter 31
Index
• & operator: Pointers
• a.out: The compiler
• Address of variables: Pointers
• Array pointer: Function pointers
• Arrays: Arrays
• ASCII codes: Character Conversion Table
• Assignment, hidden: Example 28
• Binary tree: Example Structures
• Bit operations: Machine Level Operations
• Black boxes: Functions
• Braces: Form of a C program
• C library: Operating systems
• Calling functions: Functions
• case statement: switch
• Case, upper and lower: Use of Upper and Lower Case
• cast operator: Types and The Cast Operator
• Casting pointers: Example 8
• char: Variables
• Character classification: Character Identification
• Character constants: char
• Character conversion table: Character Conversion Table
• Comments: Comments
• Compiler: Basic ideas
• Compiler phases: The compiler
• Compiling a program: Filenames
• Conditional compilation: Other Preprocessor commands
• const, constants: const
• Constant expressions: Special Constant Expressions
• Constants and macros: Preprocessor
• Control characters: char
• Control characters, printf: Formatting with printf
• Conversion characters, scanf: Conversion characters
• Conversion table: Character Conversion Table
• Curly braces: Form of a C program
• Data structures: Data structures
• Debugging: Errors and debugging
• Decisions: Decisions
• Declarations: Use of Upper and Lower Case
• Devices: Files devices
• do while: do while
• End of file: feof
• enum type: FILE
• Enumerated data: FILE
• Environment variables: envp
• Environment variables in C: envp
• Eratosthenes sieve: Arrays and for loops
• Errors, diagnosing: Errors and debugging
• Errors, files: File Errors
• Errors, of purpose: Errors
• Errors, programming: Errors
• Escaping from an program: Command languages
• Example code: Example Programs chapter
• exit function: The exit function
• Expressions: Functions with values
• Extern class: Storage class register static and extern
• FILE: FILE
• File descriptors: File Handles
• File extensions: Filenames
• File handles: File Handles
• File, detecting end of: feof
• File, opening: Opening files
• Files: Files and Devices
• Files and devices: Files devices
• Files as abstractions: Standard Output and Standard Input
• Format specifiers, printf: Formatting with printf
• Formatting text and variables: printf
• Function names: Functions
• Functions: Functions
• Functions with values: Functions with values
• Game of life: Example 20
• gcc: The compiler
• getchar: getchar and putchar
• getenv() function: envp
• gets: gets and puts, gets()
• Global variables: Global variables
• Global variables and recursion: Advantages and Disadvantages of Recursion
• GNU compiler: The compiler
• Header files: Libraries
• Hidden assignment: Example 28
• High level: Levels
• Identifier names: Functions
• if: Decisions
• if statement: if
• Initialization of arrays: Initializing Arrays
• Initializing structures: Pre-initializing Static Structures
• Initializing variables: Declarations and Initialization
• int: integers, Variables
• Integer types: Variables
• Integers: integers
• Interrupting a program: Command languages
• Keyboard input: Standard Output and Standard Input
• Layout: Programming style
• Levels of detail: Levels
• Libraries: Reserved words & example
• Libraries of functions: Libraries
• Linked list: Example Structures
• Linker: The compiler
• Local environment: Levels
• Local variables: Where to declare things, Local variables
• Logical errors: Errors
• long: Variables, integers
• Loop variables: Choosing Variables
• Loops: Loops
• Low level: Levels
• Machine level operations: Machine Level Operations
• Macros: Preprocessor
• main function: Form of a C program
• Mainframe: Operating systems
• malloc: Creating Memory for Dynamical struct Types
• Math errors: Maths Errors
• Mathematical functions: Mathematical Functions
• Memory allocation, dynamical: Creating Memory for Dynamical struct Types
• Multidimensional arrays: Arrays Of More Than One Dimension
• Names, for identifiers: Functions
• Nested ifs: Nested ifs and logic
• Non-printable characters: char
• Opening a file: Opening files
• Operating system: Operating systems
• Operators: Assignments Expressions and Operators
• Operators, hidden: Hidden Operators
• Output, formatting: printf
• Panic button: Command languages
• Parameters to functions: Parameters
• Parsing strings: sscanf()
• Phases of compilation: The compiler
• Poem: Example 1
• Pointers: Pointers
• Pointers to functions: Function pointers
• Preprocessor: Preprocessor
• Prime number generator: Arrays and for loops
• printf function: printf, printf again
• Printing: printf
• Printing formatted to strings: sprintf()
• Prototyping: Value parameters
• putchar: getchar and putchar
• puts: gets and puts, puts()
• Records: Structures and Unions
• Records (structures): struct again
• Recursion: Recursion
• Recursion and global variables: Advantages and Disadvantages of Recursion
• Reserved words: Reserved words & example
• Returning values: Functions with values
• scanf: scanf
• scanf, dangers: The dangerous function
• Scope: Where to declare things, Scope
• Screen editor: Basic ideas
• Screen output: Standard Output and Standard Input
• Shell: Basic ideas, Operating systems
• short: integers, Variables
• Snakes and ladders: Program listing
• Special characters: Special Control Characters again, char
• Stack: Functions and The Stack
• Standard error: Files devices
• Standard input: Files devices
• Standard input/output: Standard Output and Standard Input
• Standard output: Files devices
• Static initialization of arrays: Initializing Arrays
• Static variables: Storage class register static and extern
• stderr: Standard Output and Standard Input
• stdin: Standard Output and Standard Input
• stdio.h: Libraries
• stdout: Standard Output and Standard Input
• strcmp: Handling strings
• strcpy: Handling strings
• Streams: Standard Output and Standard Input, How does scanf see the input
• String handling functions: Handling strings
• Strings: Conventions and Declarations
• strlen: Handling strings
• strstr: Handling strings
• Structure: struct again
• Structure of a C program: Form of a C program
• Structured data: Structures and Unions
• Structures: Structures and Unions
• Structures, initializing: Pre-initializing Static Structures
• Style: Programming style, if, Cautions about Style
• Style, global variables: Style note
• Substrings, searching for: Handling strings
• switch case: switch
• Syntax error: Errors
• Tables: Arrays
• Terminating a program: The exit function
• Tests: Decisions
• Type conversion: Types and The Cast Operator
• Types: Types and The Cast Operator
• Types, advanced: More on Data Types
• Union: Unions
• Unions: union, Structures and Unions
• Value parameters: Value parameters
• Variable names: Variables
• Variable types: Types and The Cast Operator
• Variables: Variables, Use of Upper and Lower Case
• Variables, declaring: Declarations
• Variables, initializing: Declarations and Initialization
• void: void
• volatile: volatile
• Whiet space: Comments
• while loop: while
• White space: Scope
Table of Contents
• C Programming Tutorial (K&R version 4)
• Preface
• Introduction
o High Levels and Low Levels
o Basic ideas about C
o The Compiler
o Errors
o Use of Upper and Lower Case
o Declarations
o Questions
• Reserved words and an example
o The printf() function
o Example Listing
o Output
o Questions
• Operating systems and environments
o Files and Devices
o Filenames
o Command Languages and Consoles
o Questions
• Libraries
o Questions
• Programming style
• The form of a C program
o Questions
• Comments
o Example 1
o Example 2
o Question
• Functions
o Structure diagram
o Program Listing
o Functions with values
o Breaking out early
o The exit() function
o Functions and Types
o Questions
• Variables, Types and Declarations
o Declarations
o Where to declare things
o Declarations and Initialization
o Individual Types
char
Listing
Integers
o Whole numbers
Floating Point
o Choosing Variables
o Assigning variables to one another
o Types and The Cast Operator
o Storage class static and extern
o Functions, Types and Declarations
o Questions
• Parameters and Functions
o Declaring Parameters
o Value Parameters
o Functions as actual parameters
o Example Listing
o Example Listing
o Variable Parameters
o Example Listing
o Questions
• Scope : Local And Global
o Global Variables
o Local Variables
o Communication : parameters
o Example Listing
o Style Note
o Scope and Style
o Questions
• Preprocessor Commands
o Macro Functions
o When and when not to use macros with parameters
o Example Listing
o Note about #include
o Other Preprocessor commands
o Example
o Questions
• Pointers
o & and *
o Uses for Pointers
o Pointers and Initialization
o Example Listing
o Types, Casts and Pointers
o Pointers to functions
o Calling a function by pointer
o Questions
• Standard Output and Standard Input
o printf
o Example Listing
o Output
o Formatting with printf
o Example Listing
o Output
o Special Control Characters
o Questions
o scanf
o Conversion characters
o How does scanf see the input?
o First account of scanf
o The dangerous function
o Keeping scanf under control
o Examples
o Matching without assigning
o Formal Definition of scanf
o Summary of points about scanf
o Questions
o Low Level Input/Output
getchar and putchar
gets and puts
o Questions
• Assignments, Expressions and Operators
o Expressions and values
o Example
o Output
o Parentheses and Priority
o Unary Operator Precedence
o Special Assignment Operators ++ and --
o More Special Assignments
o Example Listing
o Output
o The Cast Operator
o Expressions and Types
o Comparisons and Logic
o Summary of Operators and Precedence
o Questions
• Decisions
o if
o Example Listings
o if ... else
o Nested ifs and logic
o Example Listing
o Stringing together if..else
o switch: integers and characters
o Example Listing
o Things to try
• Loops
o while
o Example Listing
o Example Listing
o do..while
o Example Listing
o for
o Example Listing
o The flexible for loop
o Quitting Loops and Hurrying Them Up!
o Nested Loops
o Questions
• Arrays
o Why use arrays?
o Limits and The Dimension of an array
o Arrays and for loops
o Example Listing
o Arrays Of More Than One Dimension
o Arrays and Nested Loops
o Example Listing
o Output of Game of Life
o Initializing Arrays
o Arrays and Pointers
o Arrays as Parameters
o Questions
• Strings
o Conventions and Declarations
o Strings, Arrays and Pointers
o Arrays of Strings
o Example Listing
o Strings from the user
o Handling strings
o Example Listing
o String Input/Output
gets()
puts()
sprintf()
sscanf()
o Example Listing
o Questions
• Putting together a program
o The argument vector
o Processing options
o Environment variables
• Special Library Functions and Macros
o Character Identification
o Examples
o Program Output
o String Manipulation
o Examples
o Mathematical Functions
o Examples
o Maths Errors
o Example
o Questions
• Hidden operators and values
o Extended and Hidden =
o Example
o Hidden ++ and --
o Arrays, Strings and Hidden Operators
o Example
o Cautions about Style
o Example
o Questions
• More on data types
o Special Constant Expressions
o FILE
o enum
o Example
o Example
o Suggested uses for enum
o void
o volatile
o const
o struct
o union
o typedef
o Questions
• Machine Level Operations
o Bit Patterns
o Flags, Registers and Messages
o Bit Operators and Assignments
o The Meaning of Bit Operators
o Shift Operations
o Truth Tables and Masking
Complement ~
AND &
OR |
XOR/EOR ^
o Example
o Output
o Example
o Example
o Questions
• Files and Devices
o Files Generally
o File Positions
o High Level File Handling Functions
o Opening files
o Closing a file
o fprintf()
o fscanf()
o skipfilegarb() ?
o Single Character I/O
o getc() and fgetc()
o ungetc()
o putc() and fputc()
o fgets() and fputs()
o feof()
o Printer Output
o Example
o Output
o Converting the example
o Filing Errors
o Other Facilities for High Level Files
o fread() and fwrite()
o File Positions: ftell() and fseek()
o rewind()
o fflush()
o Low Level Filing Operations
o File descriptors
o open()
o close()
o creat()
o read()
o write()
o lseek()
o unlink() and remove()
o Example
o Questions
• Structures and Unions
o Organization: Black Box Data
o struct
o Declarations
o Scope
o Using Structures
o Arrays of Structures
o Example
o Structures of Structures
o Pointers to Structures
o Example
o Pre-initializing Static Structures
o Creating Memory for Dynamical struct Types
o Unions
Declaration
Using unions
o Questions
• Data Structures
o Data Structure Diagrams
o The Tools: Structures, Pointers and Dynamic Memory
o Programme For Building Data Structures
o Setting Up A Data Structure
o Example Structures
o Questions
• Recursion
o Functions and The Stack
o Levels and Wells
o Tame Recursion and Self-Similarity
o Simple Example without a Data Structure
o Simple Example With a Data Structure
o Advantages and Disadvantages of Recursion
o Recursion and Global Variables
o Questions
• Example Programs
o Statistical Data Handler
The Editor
Insert/Overwrite
Quitting Sections
The Program Listing
o Listing
o Variable Cross Referencer
Listing Cref.c
Output of Cross Referencer
Comments
• Errors and debugging
o Compiler Trappable Errors
Missing semicolon;
Missing closing brace }
Mistyping Upper/Lower Case
Missing quote "
Variable not declared or scope wrong
Using a function or assignment inside a macro
Forgetting to declare a function which is not type int
Type mismatch in expressions
o Errors not trappable by a compiler (run time errors)
Confusion of = and ==
Missing & in scanf
Confusing C++ and ++C
Unwarranted assumptions about storage
The number of actual and formal parameters does not match
The conversion string in scanf/printf is wrong
Accidental confusion of int, short and char
Arrays out of bounds
Mathematical Error
Uncoordinated Output using buffered I/O
Global Variables and Recursion
o Tracing Errors
Locating a problem
o Pathological Problems
o Porting Programs between computers
o Questions
• Summary of C
o Reserved Words
o Preprocessor Directives
o Header Files and Libraries
o Constants
o Primitive Data Types
o Storage Classes
o Identifiers
o Statements
o Character Utilities
o Special Control Characters
o Input/Output Functions
o printf conversion specifiers
o scanf conversion specifers
o Maths Library
o goto
• All the Reserved Words
• Three Languages: Words and Symbols Compared
• Character Conversion Table
• Emacs style file
• Answers to questions
• Index