Programming in ANSI C
Programming in ANSI C
Institutional Repository
Programming in ANSI C
This item was submitted to Loughborough University's Institutional Repository
by the/an author.
Additional Information:
This is a book.
Version: Published
ANSI
C
Third Edition
Ray Dawson
Group D Publications
2 Programming in ANSI C
All rights reserved. This online version of the book is provided for personal and
educational use only. No part of this book may be reproduced in any form, by
photostat, microfilm, retrieval system, or by any other means, without the prior
permission of the publisher except in the case of duplication by professional
educators for use by their students. Copies of this online version cannot be duplicated
and sold for profit by any organisation.
A catalogue record for this book is available from the British Library
Ray Dawson
Department of Computer Studies
Loughborough University
Loughborough, Leicestershire LE11 3TU
Telephone: 01509-222679
Fax: 01509-211586
Email: R.J.Dawson@Lboro.ac.UK
Preamble 3
Preamble
This third addition of this book has been published by popular demand. I am very
pleased by the way the book has been received by students, members of the teaching
staff, and by software professionals in industry. On the whole the "no nonsense"
approach of getting to the point without introducing hundreds of pages of basic
information on how to program has been well received. Only two serious criticisms
have been made about the first edition and these have been tackled in later editions.
Firstly, some lecturers and some students complained there were no exercises in the
book and secondly, some also complained there were not enough examples of C code.
By including a set of exercises and a set of sample solutions I believe I have satisfied
both requests together. Other improvements are relatively minor, an odd correction
here, an odd expanded explanation there, but I believe the net result will be an even
better book for students, teachers and software professionals alike. The only
difference between the second and third edition is in the binding - this new edition
should prove more robust.
Acknowledgements
I would like to thank Group D Publications for publishing this book. My thanks also
go to the Department of Computer Studies, and in particular, Professor Jim Alty, for
providing the money and resources to enable this book to be published. Finally I must
thank my colleague, Satish Bedi, for his helpful comments on the first edition of this
book, and for bringing to my attention the corrections required - he has made a
significant contribution towards the improved accuracy of this edition.
Dedication
I would like thank my wife, Dawn, and my sons, Matthew and Alex, for their support
while I was producing this book. I dedicate this book to them.
Ray Dawson
4 Programming in ANSI C
Contents
Programming in
ANSI
Third Edition
PART A
The C Language
Part A : The C Programming Language 7
Part A : Contents
Page
Page
Section 5 : Arrays 40
5.1 Arrays 40
5.2 Limitations and Dangers in the Use of an Array 40
5.3 Strings 41
5.4 The gets(chararray) Function for Reading Strings 42
5.5 Initialisation of Arrays 42
5.6 Two Dimensional Arrays 43
5.7 Arrays of Arrays 43
5.8 Using Individual Rows 44
5.9 Array Syntax Warning! 44
5.10 Multi Dimensional Arrays 44
5.11 Initialising Multi Dimensional Arrays 45
5.12 C Exercise 5 46
Page
Page
9.17 Declaring Function Return Types 85
9.18 The return Statement 86
9.19 Further Notes on Function Return Values 86
9.20 Structures as Function Parameters 87
9.21 Structure Return Values 88
9.22 Arrays Used With Functions 88
9.23 Unusual Properties of Array Parameters 89
9.24 C Exercise 9 90
Section 10 : Pointers 92
10.1 What is a Pointer and Why Use One? 92
10.2 Pointer Declaration 92
10.3 Assigning Values to Pointers, the Unary '&' Operator 93
10.4 Pointer Casts 93
10.5 Indirect Reference Using Pointers, the Unary '*' Operator 93
10.6 void Pointers 94
10.7 Initialising Pointers 94
10.8 Constant Pointers and Pointers to Constants 95
10.9 Adding Integers to, and Subtracting Integers from Pointers 96
10.10 Subtracting Pointers from Pointers 96
10.11 Pointer Arithmetic 96
10.12 Array Names Used as Pointers 97
10.13 Pointers Used as Arrays 98
10.14 Pointers and Text Strings 98
10.15 Single Characters and Character Strings 99
10.16 Common Mistakes With Strings 99
10.17 Pointers to Structures or Unions, and the -> Operator 100
10.18 Pointers to Structures or Unions Containing Arrays 100
10.19 Structure Pointers Within Structures 101
10.20 The Function malloc For Allocating Memory 101
10.21 Functions Needing More Than One Return Value 102
10.22 Pointers As Function Parameters 102
10.23 Arrays As Function Parameters 103
10.24 Alternative Declarations Of Array Parameters 104
10.25 C Exercise 10 104
Section 11 : Storage Classes 106
11.1 Storage Class Specifiers 106
11.2 Local Variable Storage Class: auto 106
11.3 Local Variable Storage Class: register 107
11.4 Local Variable Storage Class: static 107
11.5 Global Variable Storage Class: Default Global Variables 109
11.6 Global Variable Storage Class: extern 109
11.7 Global Variable Storage Class: static 110
11.8 extern and static Function Definitions 110
11.9 C Exercise 11 111
Part A : The C Programming Language 11
Page
It has been used in the development of the UNIX operating system and has
grown in importance with the growth of UNIX.
(1) The control constructs (eg. if, while) and structured data types (eg.
arrays, records) found in high level languages,
(2) Facilities normally only found in low level languages (eg. bit
manipulation and use of register variables) .
Unfortunately, its rather cryptic syntax does not make the code as 'readable' as
most other high level languages.
Part A : The C Programming Language 13
#include <stdio.h>
#define ONE 1
main() {
int localnum, sum; /* local data definitions */
globalnum=ONE; /* code statements */
localnum=ONE;
sum=globalnum+localnum;
printf("answer is %d\n", sum);
/* printf is a library function used for
outputting information to the screen */
return 0; /* this stops the program */
}
Notes:
3. Braces {} group statements together and are equivalent to the words "begin"
and "end" in other languages such as Pascal.
1. Comments
These can appear anywhere in a program between the symbols /* and */ , except
of course, a comment cannot appear in the middle of a variable or function name.
Part A : The C Programming Language 14
The pre-processor is the first part of the compiler to run. It takes control instructions
from the code such as include another file, or define a macro.
These occur on separate lines from other C language statements and always start with
a "#".
These define external (global) data items (variables) that are to be widely available
for use in the main program and program functions.
These will contain both data definitions and code instructions to be executed while
the program runs.
Every C program contains one function named main. When the program runs it starts
with the first code statement in main.
1.4 C Exercise 1
Examine any C program (for example, there are some in Part E) and answer the
following:
1. Are there any comments? If so, where? What would happen to the program if
the comments were removed?
4. Statements starting with the keyword int are data definition statements for
integer variables. Which of these are global data definitions and which are local
data definitions?
5. You will probably observe that some of the statements start with a number of
spaces. Why might this be? Does it help you understand the program?
Part A : The C Programming Language 15
This is so that:
char 8
int 16 or 32 (usually)
short int 16 (usually)
short 16 (usually)
long int 32 (usually)
long 32 (usually)
float about 32
double about 64
long float about 64
long double > 64
The types short int and short are identical. Similarly long int and
long are identical.
The char, int, short and long types can be preceded by the
qualifiers signed or unsigned. The default is signed. If used on their
own the type signed refers to type signed int and unsigned refers
to type unsigned int.
The number of bits for each type will vary from one compiler to the next, even
on the same type of computer. The only thing guarenteed is that long int
has more bits than short int, and double has more bits than float.
The number of bits for the type int is normally the most convenient size for
the computer to handle. This can be the same as either type short int or
long int or something in between. It is usually 16 or 32 bits.
The types long float, and long double are not available with all
compilers.
Each variable declaration statement consists of a type name followed by one or more
variable names.
3. Names can start with the "_", underscore character but this is not recommended
as many system macros and functions are given names in this format.
With C's cryptic syntax it is even more important that the names are meaningful to
make a program easier to follow and debug.
Examples:
char letter;
int overtime, day_of_month, UB40;
signed short int salary;
signed short salary;
short int salary;
short salary;
unsigned long hours;
float sigma_squared, X_times_2;
The 3rd, 4th, 5th and 6th examples are equivalent, since signed and int are
assumed if not specified.
These are global variables as they can be used in the main function and any
other function defined in the source file after the declaration.
These are called local variables and are declared following the opening { in the
function.
Note that the declarations must come before any other statements in the
function except following a further { as given below.
They can only be used in the function where they are declared.
In this case two memory locations are reserved with the same name, but the
local variable is always used in preference to the global variable where
possible.
Part A : The C Programming Language 18
1. start with a zero, the number is an unsigned octal integer. eg. 0456
3. are too big to fit into a signed integer (or unsigned integer if octal or
hexadecimal) then the constant type is a signed long integer (or unsigned long
integer if octal or hexadecimal).
4. have an L or l suffix, the number is a signed long integer. eg. 42L, 99l
6. have both a U and an L suffix (or u and l), the number is an unsigned long
integer. eg. 42UL, 99ul
8. contain a decimal point or scientific 'e' notation and an F or f suffix the number
is of type float eg. 7.3F, 42e-1f
9. contain a decimal point or scientific 'e' notation and an L or l suffix the number
is of type long float eg. 7.3L, 42e-1l
Can be either single characters in primes (eg: 'A') or portable representations of "odd"
characters.
eg:
'\n' newline
'\r' carriage return
'\f' form feed
'\t' tab
'\b' backspace
'\a' audible alarm (beep)
'\0' null
'\\' backslash
'\'' prime
Part A : The C Programming Language 19
Note that throughout this text the ASCII character set is assumed. Other systems may
be used in which case the number representations will have different values to the
examples shown.
Characters represented with the \ notation can also be used in strings. Strings are
enclosed between " " and have an implied null character at the end.
A string constant cannot be spread over more than one line. However, if the compiler
finds two string constants with only spaces or new lines in between it will
aotomatically concatinate the strings together.
Note that a single character given between ' ' is NOT the same as a string of one
character.
ie. 'A' is equivalent to '\101' or '\x41' and also 65, 0101 and 0x41.
Unlike Pascal and some other programming languages, C does not draw any
significant distinction between character values and integer numeric values. It is
perfectly acceptable in C to assign 'A' to an integer variable and 65 to a character
variable. It is even possible to use character constants in seemingly meaningless
expressions.
Note also that local variables designated as static or extern have different
initialisation properties. These are described later in section 11 on storage classes.
Once declared, these variables cannot have any value assigned to them or be changed
in any other way, but they can be used in expressions.
eg. pi = 1.234; /* illegal */
diam = 2*pi*rad; /* OK */
y = year_length; /* OK */
year_length = 366; /* illegal */
const variables are used to make a program more readable. They also make a
program easier to maintain if the constant needs to be changed in later versions of the
program. If the numeric value has been used the change may need to be made in
many places in the code some of which could possibly be missed. The use of a
const variable means that only one line needs to be changed to alter the constant
value throughout the program.
2.10 C Exercise 2
1. Which of the following data definition statements are illegal and why?
int Alf, Bert=4, Cleo=4.3, Doris='D';
char Eric=257, Eric_Again, 3rd_Eric;
short Default, Default_Value, default, default2;
long int, Fred=123456789;
Float x=123.456, Y=100 e-6;
unsigned negative = -1;
const int three = 4, Max, Eric=0;
unsigned float George = 1.234;
2. If a program has only the following data definitions, all of which are inside the
main function:
int Ada, Bill = 4, Cecil;
char letter='A', digit='1';
float Xvalue=258.34,Yvalue;
unsigned val;
const char Q='A';
which of the following assignments are illegal and why?
Bill = Ada;
Ada = 0xAda;
Bill = letter;
Ada = digit;
Bill = Cecil;
digit = 66;
digit = '\102';
digit = 0102;
letter = '\9';
letter = "A";
letter = Q;
Xvalue = 12.345;
Xvalue = 1234.5 e-2;
yvalue = Xvalue;
val = 0177777U;
Q = 100;
Q = 'Q';
What is the new value for the assigned variable after each of the legal
assignment statements above?
Part A : The C Programming Language 22
fred = joe;
count = count+1;
The right hand side of the assignment can be a constant, another variable or an
expression.
Arithmetic expressions can use any of the following operators (given in order of
precedence):
+ - addition, subtraction
With the exception of the 2 unary operators (- and ~), all arithmetic operators group
from left to right where precedence is equal, the unary operators group from right to
left.
The ~ operator gives the complement of an integer. ie. All bits reversed. This is
NOT the same as negating the value, though it is only different by a value of
one.
ie. ~x has the same value as -x - 1
The modulus operator, %, gives the remainder after one integer is divided by
another.
eg. 11%3 gives a result of 2.
+ - * / are the only arithmetic operators that can be used with float and
double quantities.
There are many other operators in 'C', a full list of C operators is given in
Appendix A.
The Division operator, / used with integers always yields an integer answer.
This is because C does the calculation first and decides where it will put the answer
afterwards. ie. It converts the answer to 2.0 from the integer result of 2 calculated
from the expression.
If / is used with real numbers or a mixture of real and integer the result is the
expected floating point value.
ie. If either fred or joe had been real or if both had been real the result in x
would have been 2.6667
The shift operators can only be used with integer variables (ie. char, int, short, long).
The use in an expression is as follows:
Where:
A left shift looses the left most bits and fills the right most bits with zeros
A right shift looses the rightmost bits and fills the leftmost bits with either zeros or
ones. If the original variable value is negative the leftmost bits are filled with ones
otherwise they are filled with zeros (though this may depend on the compiler). This
copies the sign bit ensuring the sign of the original value is preserved. Unsigned
integers should always have the left most bits filled with zeros.
These operators, like the shift operators are used for manipulating individual bits in
bytes and words. Like the shift operators they can be used on char variables, and
long, short or normal integers whether signed or not.
These operators are not available in most high level languages. They help give C its
power as an operating system language.
The ~ is a unary operator requiring only one operand, the other bit manipulation
operators require two operands.
It compares the bits of two variables or constants and gives a result with a bit set
where it is set in both the first and the second operand.
eg. The expression fred & 0xFF00 will give a result with:
all the bits in the left byte the same as those in fred.
all the bits in the right byte set to zero
Part A : The C Programming Language 26
It compares the bits of two variables or constants and gives a result with a bit set
where it is set in either the first or the second operand, or both.
It compares the bits of two variables or constants and gives a result with a bit set
where it is set in either the first or the second operand, but not both.
Pascal can be described as a strongly typed language. This means that it does not
normally allow variables and constants of differing types to be mixed in expressions
or assignments without explicitly calling a conversion function. C on the other hand is
a weakly typed language. Character, integer and floating point values can be freely
mixed in an expression or assignment, with automatic conversions from one type to
another taking place.
Part A : The C Programming Language 27
Whenever C handles any variable with fewer bits than an int the first thing it does is
work out the integer equivalent.
But if the leftmost bit is set the spare left bits are filled with 1s.
This ensures that negative char values are preserved in value when converted to an
int.
Values of type short are treated similarly if they have fewer bits than integers.
However, with many compilers short and int values have identical numbers of
bits.
If the variable is an unsigned char or unsigned short the left bits are
always filled with zeros.
If, when the processor has finished dealing with the numbers the result is assigned to
a char or short variable, the number must be converted once again.
Part A : The C Programming Language 28
ie. If the variable where a result is to be stored has not enough bits ... too bad ...
some will be lost!
It is one of the features of C that errors of this type can occur without warning. . .
Other languages such as Pascal would have either reported an error or not allowed the
operation in the first place!
If C has to handle the expression such as a+b+c then the following rules are applied
in order:
1. If any of the variables are char or short they will be converted to int.
2. If any of the variables are of type long then all char, short and int
variables will be converted to long.
This is done by adding either 0s or 1s to the left depending on the sign of the
variable.
3. If any of the variables are unsigned then all signed variables are
converted to unsigned.
This does not involve any extra or changed bits, just a change in how the
variable is designated.
4. If any variable is of type float all integer types are changed to the signed
float type.
5. If any variable is of type double, all integer and float variables are
changed to the signed double type.
6. Similarly if a long double type exists and is used in the expression then all
other variables are changed to long double.
NB. Many C compilers always convert all float numbers to double no matter
what else is in the expression.
Part A : The C Programming Language 29
2. If a "larger" integer type is assigned to a "smaller" type the surplus bits on the
left are lost.
In general:
C does what you would expect ...
but ...
beware of errors through information being lost where the assigned
variable is too "small" for the result.
then
u = -1; /* legal, but u now has the value 65535 */
i = u; /* legal, but i now has the value -1 ! */
d = 2.0 * i; /* d now has value -2.0 */
d = 2.0 * u; /* d now has value 131072.0 */
Part A : The C Programming Language 30
The general philosophy in C is that the compiler will ensure that the program carries
on as best as it can when types are mixed in any form of expression or assignment. C
converts the variables according to its own rules. If information is lost through an
assignment of one variable type to another, or if a negative value is assigned to an
unsigned variable, then C will give no errors either during the program compilation or
when the program runs. Clearly this can lead to misleading results if care is not taken.
1. variables are not used in any way that may loose information.
2. variables are used in a straight forward way with the minimal mixing of
types so that programs are clear to follow and are easily maintained.
3.18 Casts
In the example:
int i=7, j=2;
float x;
x = i/j;
the calculation on the right is done before the variable type of x is even looked at, so
the calculation is done with integer arithmetic.
ie. The result will be 3 which is then converted to 3.0 when it is put in x.
x = (float)i/j;
or
x = i/(float)j;
then the cast forces conversion of either i or j to type float before evaluation
of the expression.
The conversion rules would then convert the other integer in the expression to a
float variable and the result would be of type float and 3.5 would be
assigned to x.
Part A : The C Programming Language 31
In C, the = is not the only assignment operator, the following are also available:
A += 5; /* equivalent to A = A+5; */
A *= B; /* equivalent to A = A*B; */
A <<= 3; /* equivalent to A = A<<3; */
etc.
These assignment types allows C to compile certain types of statement into more
efficient code for the computer to execute.
but their meanings are different when used in embedded statements as explained in
the next section.
The value is that which is assigned. . . and can itself be used in an expression.
If abc starts with the initial value of 100 and xyz with a value of 10 then:
30 is assigned to xyz
and 130 is assigned to abc.
With simple assignments using = this does give a relatively intuitive meaning:
eg. abc = def = xyz = 2;
will set all of the variables abc, def, xyz to 2.
4. Although embedded statements are a powerful feature they should be used with
care, they can result in programs that are:
1. difficult to read,
2. difficult to follow,
and 3. difficult and costly to maintain.
Note: ++ and -- are used like any other unary operator and have the same high
precedence.
3.23 C Exercise 3
Assuming integers are stored in 16 bits and long integers in 32 bits, what is the value
in each variable after each of the following statements:
(1) Anne = Bob * 2 + Chris / Chris;
(2) Anne = 10 / Bob+Chris;
(3) Anne = 12 / Bob*Chris;
(4) Anne = Chris / 4;
(5) Anne = Chris % 4;
(6) Anne = Chris % 4-Chris;
(7) Anne = Bob << 2;
(8) Anne = Bob >> 2;
(9) Anne = Bob << 2 + Chris;
(10) Anne = 1 << 15 >> 15;
(11) Dave = 1 << 7 >> 7;
(12) Anne = Chris & 1;
(13) Anne = Chris | 1;
(14) Anne = Chris ^ 1;
Part A : The C Programming Language 34
Unlike most other programming languages, there are no in-built input or output
statements in the C language. This means that all input and output must be done by
calling functions, at least some of which must be written in a language other than C.
There is, however, a standard library of functions that allow I/O to be done in a
relatively uniform manner for all C implementations without the need to know how it
is being done in any particular case. This standard library is known as the "stdio"
library and can be considered to be an extension to the C language itself. To be able
to use the functions within this library it is necessary to insert the following pre-
processor statement at the start of the program source code:
#include <stdio.h>
Functions getchar, putchar and printf are described in this section and gets
in the next section as they are used extensively in the program examples in the rest of
this book. Further details of these and the many other input and output functions are
given in Part C of this book.
This will get a character from the keyboard, waiting as long as required for a key to
be pressed.
When the character is found it's corresponding bit pattern (usually the ASCII value) is
assigned to abc.
Part A : The C Programming Language 36
If the function encounters an error, such as an end of file marker, it returns the value
EOF (this type of error is unusual from a keyboard!). EOF is a constant defined in the
stdio.h header file described in section C
It is possible to use getchar without assigning the resulting value to any variable,
such as in the statement:
getchar();
This will wait for a key to be pressed but it will discard the value so obtained. This
could be used in a program that gives the message: "hit any key to continue".
4.3 putchar(character)
This will output to the screen whatever character has a bit pattern that corresponds to
the value of the specified parameter in the ( ).
For example: int fred = 65;
putchar('x'); /* outputs character 'x' */
putchar(fred); /* outputs character 'A' */
Note:
4.4 printf(format,values);
Notes:
1. A string is a collection of one or more characters with a hidden zero byte at the
end.
Double quotes, " " enclose a string, single quotes ' ' enclose a character.
3. The \n in the above example makes sure the output is on a new line . . .
4. If the string contains a %d then the output is modified by substituting the value
of the next parameter in place of the %d.
Wherever a % is found in the printf output string the next character will dictate
what will be substituted.
%c output as a character
%s output as a string
Part A : The C Programming Language 38
N.B. It is up to the programmer to ensure that printf has suitable parameters so that
sensible output can be achieved.
The normal %d output will substitute the minimum number of digits with a leading -
if it is negative.
This may not be neat or convenient if, say, a table of figures is to be output, but, a
field width modifier can be used to specify how many character positions the
substituted value will take up.
There will always be five characters substituted for the %5d, if the value is between
-999 and 9999 then the output will be padded with spaces on the left.
A further modifier can alter the number of decimal places output for real numbers
printed with a %f, %e or %g, in the form:
Where:
10 gives the total width of the field including digits, the decimal point and
possible - sign.
4.7 C Exercise 4
1. Write a program to write the message "Hello There!" to your computer screen.
(b) Output the binary representation for the character (eg. 01100001).
4. Amend the previous program, so that the binary representation of the character
is output as a sequence of characters using putchar.
Part A : The C Programming Language 40
Section 5 : Arrays
5.1 Arrays
Note that, as in the last example, array declarations can be mixed with ordinary
variable declarations.
The declaration int numbers[50] means there are 50 integers associated with
the name numbers.
Each array element can be used as any other variable of the same type.
eg. numbers[5] = 100 + counters[1]*2;
(The if statement is explained in detail in the next section and the meaning of
fred==joe in the if statement is explained in the section on pointers.)
'C' merely counts forward or backwards in memory from its starting point of element
0 . . . and uses whatever it finds there!
5.3 Strings
An array of characters with a null byte at the end is called a character "string".
A string can be read from the keyboard using the function gets:
eg. gets(mystr);
This will read a string from the standard input (usually the keyboard) until the user
types the return key. The function discards the newline character it has read and
replaces it with a null character so that the input stored in the array becomes a
properly terminated character string.
N.B. It is important that the array specified as the gets parameter is large enough to
hold whatever string the user may input plus the null byte string terminator. If the
user types in too many characters for the array the extra characters are inserted into
memory straight after the array. This can cause unexpected corruption of data and can
result in all sorts of errors, possibly crashing the program. It is better to allocate an
array which has far too many array elements than risk there being too few.
In technical terms the function gets returns a pointer to the array given as parameter.
The mechanisms for this are explained in sections 9 and 10.
If there are too few initialising values the remaining array elements will default to
zero.
.... but it is important that the array is big enough for the given characters and the null
byte terminator.
If an array is initialised the size can be left to default to the number of initialising
values.
eg. char greeting[]="Hello!";
int abc[]={1,2,3,4,5,6,7};
An array need not be a single "list" of values. It can be like a two dimensional grid of
values.
The first column of the first row could be used in the same way as any other integer
as in the example:
x = 2*ChessBoard[0][0] + 123;
The last element of the array would be the last column of the last row which is:
ChessBoard[7][7]
Single dimensioned arrays are used to store "lists" of data items. . . but what if each
item is a list?
This now has the names stored in a "table" with 100 rows, each with 30 columns and
with each name stored in a row of the table.
This copies the fourth letter of the forty second name into the char variable ch.
Part A : The C Programming Language 44
ie. If only one set of [] is given after a two dimensional array name it effectively
means a whole row of the array.
Some languages use a syntax of two numbers separated by a comma for two
dimensional array elements.
For example in Pascal, xyz[3,5] is equivalent to xyz[3][5] in C.
If xyz[3,5] is used in C the value 3,5 is calculated using the comma operator
(explained in the next section) to give the value 5 and so xyz[3,5] is the same as
xyz[5], which is a whole row! Fortunately this often results in invalid syntax and
the C compiler then usually gives either an error or a warning if this mistake is made.
. . . but what if there was a need for an array containing several name lists, one
list for each of 50 branches in a company division?
This could be declared as:
Part A : The C Programming Language 45
char BranchNames[50][100][30];
Then supposing we wanted an array to contain all this information for 10 different
company divisions. This would mean an array declared as:
char CompanyNames[10][50][100][30];
Clearly such arrays are often impractical as they use so much memory. Furthermore
much of this space will be wasted as not all names are 30 characters long, not all
branches will have 100 employees, etc.
This will initialise x[0][0] to x[0][3] with 3, 2, 10, 5 and x[1][0] and
x[1][1] to 6 and 2. All remaining array elements are set to zero.
and
y[0][0][0] to 3,
y[1][0][0] and y[1][0][1] to 2 and 10,
y[1][1][0] and y[1][1][1] to 5 and 6,
y[1][2][0] to 2,
y[2][0][0] to 13,
y[2][1][0] and y[2][1][1] to 1 and 9,
y[2][2][0] to 8.
Character arrays can have rows initialised with strings, providing each row is long
enough to take the longest name plus the null byte terminator.:
eg. ListOfNames[100][30] = {
"Fred Bloggs",
"Joe Brown",
"Ferdinand De Wombat Junior"
};
BranchNames[50][100][30] = {
{"Fred Bloggs","Joe Brown"},
{"Ferdinand De Wombat Junior"},
{"A.Ace","B.Beeston","C.Cedar"},
};
This will initialise the first three rows of ListOfNames to the names shown. The
first two names of BranchNames[0] are initialised to "Fred Bloggs" and "Joe
Brown", and similarly the first name of BranchNames[1] , and the first three
names of Branchnames[2] are also initialised. All other array elements are set to
null characters.
5.12 C Exercise 5
1. Write a program that will declare a 3 by 3 array initialised with the values 1 to
9 and then print out the numbers on the diagonal from the bottom left position
to the top right.
Output the names, each on a seperate line, using printf with the %s format
specifier for each row. Why is there a problem with one of the rows?
Part A : The C Programming Language 47
3. Modify your last program to set the array element at position [3][0] to zero
before your output statement. How does this affect the output and why?
4. Write a program that declares a 5 element character array initialised with the
name "Fred" with zero in the last position. Copy the array one character at a
time into an integer array of 5 characters. Output both arrays using printf
with the %s format specifier for each of the arrays. What happens and why?
5. Write a program to read three lines of characters from the keyboard then output
the three lines to the screen in reverse order.
Part A : The C Programming Language 48
Many programming languages have logical or boolean variables that can hold the
value "true" or "false".
Variable toobig now has a value of "true" or "false" and is used directly in a
conditional statement:
if (toobig) xyz-=100;
eg. int x;
x = getchar()=='\n';
This expression looks OK, and it is a perfectly valid statement . . . but it does not
behave as expected!
2. If the assigned value happens to be zero, z is not incremented, for any other
value it will be.
1. Consider: if (x == a || b || c) ...;
It reads OK and it is valid 'C'..... but this also does not behave as expected!
It is true because firstly x is compared with y giving a 1 for true or a 0 for false,
and then the 0 or 1 is compared with z!
To get the "correct" comparison in 'C' use: if (x<y && y<z) ....;
Part A : The C Programming Language 51
& & and | | always give a result of 0 or 1. . . this is not very useful for bit
manipulation.
The & and | operators will often work with conditions but they sometimes give the
wrong answer!
Eg. If toobig has a value of 2 and tooheavy a value of 8, then both are non
zero and are therefore "true".
It is advisable for all C programmers to get into the habit of using && and || for
conditional expressions.
Where multiple conditions are combined with && or ||, 'C' ensures that:
If num is zero the whole multiple condition cannot be true, therefore the
second condition is not evaluated.
ie. Division by zero is avoided!
Part A : The C Programming Language 52
It is not possible to have any other type of statement between the if and else.
The {} can group statements so that they act as one for use with the if, else and
other statements.
eg. ch = getchar();
if (ch>='0' && ch<='9') {
digit_count++;
printf("The character is a digit\n");
}
else {
other_count++;
printf("Not a digit character\n");
}
Part A : The C Programming Language 53
if (ch=='y' || ch=='n') {
printf("Answer is ");
if (ch=='y') {
printf("yes!");
x++;
}
else {
printf("no!");
x--;
}
}
else
printf("Please answer 'y' or 'n'");
There are a number of conventions used for laying out code in {}:
1. if (condition) {
statements;
statements;
}
2. if (condition)
{
statements;
statements;
}
3. if (condition)
{
statements;
statements;
}
These simple rules make the code much easier to read, and "bugs" much easier to
trace.
Part A : The C Programming Language 54
In the above circumstances the else belongs to the last unpaired if.
if (a==b) {
if (c==d) x++;
else x--; /* belongs to second if */
}
else x=0; /* belongs to first if */
Starting from the top the executed {} will correspond to the first condition
found to be true.
All other {} will then be skipped regardless of whether other conditions would
be true or not.
It is the only time that an else is not normally indented the same amount as the
associated if statement.
In general, a program is far easier to follow if the if and else statements are used,
the ?: operators are usually only used in macro definitions.
6.15 C Exercise 6
1. Write a program to read up to three characters from the keyboard using gets.
If no characters are input or if more than three characters are input an error
message should be output to the screen. If one, two or three characters are input
the program should output to the screen a message saying how many characters
have been input and what the characters are.
2. Modify the last program so that it also gives an error if any character other than
an upper or lower case letter is entered. If any lower case letters are entered the
program should convert them to upper case. The program should then output a
message giving the number of letters and what the letters are in capitals.
Part A : The C Programming Language 56
The while statement is a simple way to repeatedly execute one or more statements as
long as some condition is "true".
The syntax is:
while (expression) statement;
eg. i=0;
while (numbers[i]!=0)
i++;
In the above examples the statement following the while condition is simply a blank.
ie. it does nothing when it is executed. However, each time the program goes round
the loop the variable i is executed as there is an embedded i++ statement inside
the condition that is evaluated.
Note that the statement(s) following the condition in a while loop may never be
executed if expression evaluates to "false" the first time, but if there is a statement
embedded in the loop it is executed at least once while the program decides whether it
is true or false.
Once again, braces can be used to form a compound statement if more then one
statement is to be executed, repeatedly
eg. total=i=0;
while (numbers[i]!=0) {
total+=numbers[i];
i++;
}
Part A : The C Programming Language 57
eg. i=0;
do
letters[i++]=getchar();
while (i<10);
(will read characters from the standard input into the first ten elements of the array
letters)
NB. Use of braces around one statement in the do while loop, though not
essential, will improve program clarity, as the while line cannot then be confused
with the start of a while statement
eg. do {
letters[i++]=getchar();
} while (i<10);
switch (expression) {
case value_1: statement(s)
case value_2: statement(s)
......
case value_n: statement(s)
default: statement(s)
}
If the value of the expression does not have a corresponding case prefix, the program
jumps to statement labelled "default:" . If there is no default label and there is no
match for the switch expression then the whole switch block is skipped.
eg.
switch (letter) {
case 'a': letter_a++;
case 'b': letter_b++;
default: other_letters++;
}
The break statement causes a direct exit from the switch body to the statement
following the switch.
switch (letter) {
case 'a': letter_a++;
break;
case 'b': letter_b++;
break;
default: other_letters++;
}
One obvious consequence is that more than one statement (for one particular case)
need not be put inside braces (unlike the equivalent case construct in Pascal).
The for statement is a special case of the while statement, particularly useful for
processing a number of contiguous array elements. The syntax is:
expression1;
while (expression2) {
statement;
expression3;
}
eg. total=0;
for (i=0; i<10; i++)
total+=numbers[i];
total=0;
i=0;
while (i<10) {
total+=numbers[i];
i++;
}
for (;;) {
statements
}
This loop will continue forever unless there is some statement inside the loop that
causes the program to break out or halt.
eg. for(;;) {
ch=getchar();
if (ch=='\n') break; /* exits the loop */
arr[i++]=ch;
}
In general it is not a good idea to have more than one exit out of a loop. The break
statement should, therefore, only be used in "do forever" loops.
The continue statement can also be used within any loop to cause a break in
execution. It causes the particular iteration within the loop to terminate, but not the
loop itself.
In general this statement can easily be avoided. For example the above is equivalent
to:
while (i<10) {
i++;
if (arr[i]!='\n') { /* Output on same line */
putchar (arr[i]);
number_output++;
}
}
A possible, but rather poor example of the use of the comma operator would be:
Obviously the evaluation of abc<<4 and joe+1 in this example is useless and
many compilers will give an error for this statement.
The ( ) in the example were necessary as the , has a lower precedence that the
= assignment operator.
The comma operator is only of use if the expression before the , has some useful side
effect such as in an embedded assignment or function call.
this is not a nested loop but a single loop with two counters -
it will loop five times only.
in this loop two characters are read, the second of which is compared
with a newline character to determine when to terminate the loop.
Although it is well known that there is no need to use jumps or gotos in a well
structured program, every language has some form of goto instruction, and C is no
exception.
goto label;
........
label: statement;
The label is any alphanumeric name in the same form as variable or function names.
Part A : The C Programming Language 62
Restriction:
it is possible to have a goto from within a set of {} to a label either
inside or outside the same {},
but:
it is NOT possible to have a goto outside a set of {} to a label inside
the {}.
It is a well known rule that gotos are a bad thing and should be avoided.
Indeed, the use of a goto is usually an indication of poorly planned and badly
structured code. There are, however, valid uses of a goto! In general, a goto
should be used if the alternative is to produce much more complicated code to
avoid the goto. An example may be a panic exit out of deeply nested loop and
if blocks, where the alternative may be to set up a series of flags that need to
be tested at the exit to each block.
Beware of making excuses for using a goto - the chances are you will be making
excuses for badly written code!
7.8 C Exercise 7
1. Write a C program to read in lines of text from the keyboard until either a blank
line is entered or ten lines have been read. A count should be kept of the
number of lines entered, excluding the blank line. The program should print the
line count and then output the lines in reverse order.
2. Modify your last program so that a count is kept of all letters, upper and lower
case, in the text and also the total number of characters (excluding the newline
character). The program should give an additional line of output stating the
number of letters and total number of characters.
3. Modify your last program so that instead of printing the lines in reverse order
they are output in the order of input, except that every letter is encrypted so that
the rightmost two bits are swapped (ie. interchanged, not complemented). Other
non letter characters should be left unchanged.
Part A : The C Programming Language 63
A Structure type declaration lists the individual variables (members) that are to make
up any structure of that type.
The latter will occur when variables are defined to be of the structure type (just as
storage is allocated when variables are defined to be of the int data type).
struct tag {
member declarations
};
member declarations lists the members that will make up the structure.
This declares a structure data type with name "taxcode" consisting of an integer
named "number" and a character named "letter".
Note that no actual structure has been declared by this declaration, ie. no memory has
been reserved, the declaration has merely defined a type of structure for future use.
Part A : The C Programming Language 64
or:
struct personal {
char name[20];
int age, birthday[3];
} my_record; /* declares a structure with
the members as specified */
A declaration can define both a structure type and a variable at the same time:
This declares the structure type called taxcode and also declares structure
variables of this type called taxcode_X and taxcode_Y. If this is later followed
by:
The declaration:
struct personal friend[10];
will reserve memory for 10 structures, friend[0], friend[1], etc., each with
the same members as any other structure of type personal.
structure_variable_name.member_name
The structure members can be used in exactly the same way as any other variable of
the same type.
If the structures also each contain an array, the parts of the array are used as follows:
year_born = friend[4].birthday[2];
The index following friend indicates which structure is being referred to, the
index after birthday indicates which array element within that structure is being
referred to.
Most modern 'C' compilers (but not some older, pre-ANSI ones!) allow whole
structures to be assigned, one to another.
my_taxcode.number = employees_taxcode[42].number;
my_taxcode.letter = employees_taxcode[42].letter;
Simple assignment is the only possible binary operator that can be used on whole
structures. It is not possible to use other binary operators on whole structures so, for
example, it is not possible to test to see if two structures are the same or different by
comparing using == or !=.
Part A : The C Programming Language 66
Similarly, with one exception, it is not possible to use unary operators such as - or
++ on whole structures even if the structure only contains one component. The
exception is the address operator, & , described later in section 10 on pointers.
It is possible to give initial values to structures when they are declared, providing the
initial values are given in a list in {} after the structure variable name, not at the point
where each member is defined.
Note that the initialisation of a structure variable is much the same as for array
initialisation explained earlier.
It is possible to pack several data items into a computer word using structure bit
fields.
A structure "bit field" is a set of adjacent bits within a single int (it cannot overlap a
word boundary) defined by a field width (in bits) after a name in a structure
declaration,
eg. struct {
unsigned lowestbit: 1;
signed next3bits: 3;
unsigned next2bits: 2;
} bitset;
Bit fields do not have to be named (an unnamed field can be used to "pad out" a
structure). In addition, fields can be aligned on word boundaries by preceding them
with a named or unnamed field of zero field width. A zero field width tells the
compiler to allocate the remainder of the word to the field.
Note that different implementations may assign fields either from right to left or vice
versa within a machine word.
Structure bit fields can be used as any other integer type variable.
If a bit field value is assigned to a char or int variable or any structure bit field with
more bits, the extra bits at the left hand end of the word are filled with zeros if the bit
field is unsigned or either zeros or ones if the bit field is signed.
If a char or int variable or a wider struct bit field is assigned to a bit field the
rightmost bits are copied. Those bits on the left that cannot "fit" are lost.
eg. struct {
unsigned word0bit0: 1;
unsigned : 0;
unsigned word1bit0: 1;
} bitset;
int temp;
8.9 Unions
A Union is a named piece of storage that can contain one of a number of different
data types all of which overlay each other in memory (similar to the variant field of a
variant record in Pascal).
The 'C' syntax for union declarations and use is similar to that for a structure except
that initialisation if allowed is different and bit fields do not exist. The difference
between structures and unions is the way in which the memory is organised.
where:
tag is optional and gives a name for the type of union to allow later
declaration of unions without the need to specify all the members again.
union unumtype {
float fnum;
int inum;
};
The compiler will always allocate sufficient storage for the largest type in the
declaration list. For example,
eg. unumber.fnum=4.321;
value=uarray[6].inum;
The difference is that changing the value in unumber.fnum will also change the
value of unumber.inum as it occupies the same position in memory.
Unions, like structures, can be referenced as a whole for simple assignment on most
modern 'C' compilers.
Other operators, with the exception of the address operator, cannot be used on whole
unions.
A Union may be used if memory space is in short supply. This may be true if there
are large data arrays. If two of these data arrays are not used at the same time they
could be allocated to the same memory space using a union.
eg. union {
int numbers[5000];
char letters[10000];
} u;
In this example, if an integer takes two bytes, 10000 bytes are reserved in memory for
the array u.numbers but the same memory is also usable as the character array
u.letters. These arrays would have to be used at different times, however, or use
of one would corrupt the other.
A union may also be used if it is desirable to use same memory in two different ways.
To do this it is usually necessary to nest a structure within the union.
Part A : The C Programming Language 70
Structures and Unions can be nested within each other to build more complex data
types.
eg. union {
int param_array[8];
struct {
int num_employees, num_depts, num_managers;
int min_salary, max_salary;
int date[3];
} parameter;
} system;
The above example may be used for a program on personnel records. The program
would have certain system parameter values such as the number of employees stored
in a variable called system.parameter.num_employees and the company's
minimum salary stored in a variable called system.parameter.min_salary,
etc. These system values are all stored together in one array called
system.param_array. The same memory can be accessed in either way,
whichever is most convenient.
An example of nesting structures and unions might occur for some software that deals
directly with a hardware device. Such devices frequently have a "status word" which
can be read to indicate the current state of the device. Some or all of the bits in the
status word have separate meanings for each bit, such as a "busy" bit and a "done" bit.
This situation can be usefully handled by a union containing a structure with bit
fields.
eg. union {
int word;
struct {
unsigned busy : 1;
unsigned done : 1;
unsigned clear : 1;
unsigned interrupt : 1;
} bit;
} status;
Part A : The C Programming Language 71
The whole word can now be handled using status.word, and individual bits can
be handled using status.bit.busy, etc.
or individual bits may be accessed by name to make the program easier to follow:
More recent 'C' compilers will allow unions to be initialised, but it is only the first
member of the union that can be initialised.
eg. union {
int word;
struct {
unsigned busy : 1;
unsigned done : 1;
unsigned clear : 1;
unsigned interrupt : 1;
} bit;
} status = 014; /* Initialises status.word so that
only the clear and interrupt bits
are set, the rest are zeroed */
8.15 sizeof
The size of a structure may sometimes not be the sum of the sizes of its members as
on some computers it may be necessary to align certain types of data at even
addresses. This may mean there are unused spaces inside the structure.
'C' provides a unary operator sizeof, which will yield the size (in units the size of
characters, ie bytes) of any data type or variable. It is used a bit like a function except
that the parameter may be either a variable or a data type.
Examples:
number = sizeof(int);
number = sizeof(struct taxcode);
number = sizeof(my_taxcode);
number = sizeof(status);
if (sizeof(employees_taxcode)==400) ......;
When a union type is used as a sizeof parameter the size of the largest member is
returned.
Part A : The C Programming Language 72
8.16 C Exercise 8
2. Modify your last program so that an array of 5 structures are declared. The text
should be read and characters counted for each structure in the array until either
all five structures have been read. If a blank line is entered the structure should
be made identical to the previous structure. If the first line entered is blank the
program should terminate with an error message.
3. Modify your last program so that all letters in the text are encrypted with their
rightmost bits swapped (ie. interchanged) before they are output to the screen.
To do this declare a union containing a single byte and a structure containing
bit fields. Copy each letter in turn into the union's byte, swap the bits without
using any shift or mask operators, and then update the original letter by
copying back from the byte in the union. Non letter characters should be left
unchanged.
Part A : The C Programming Language 73
A function is a section of code that is separated from the rest of the program and
given a name. This function is then "called" by name elsewhere in the program, either
in the main program or from another function.
Each time the function is called the program jumps to the start of the function and
begins executing instructions in the function. When the function is finished it jumps
back to the instruction after the original function "call", and continues where it had
previously left off.
The function is in effect a mini program in itself, and it is sometimes called a sub-
program. The equivalent in other programming languages may be referred to as
"procedures", "subroutines" or just "routines".
Many languages distinguish between functions that return a value and those that do
not.
Eg. Pascal has "procedures" and "functions
Fortran has "subroutines" and "functions"
2. Separating out logically distinct sections of code that each have a single, self
contained purpose. This makes a program easier to modify and maintain.
3. Preventing any one part of the code getting to big. Smaller sections of code are
easier to debug and maintain.
ie. It helps to "modularise" the code.
A function call is where the function is used in the main program or in other
functions.
eg. z=average(x,y);
Part A : The C Programming Language 74
A function definition is where the internal statements that make up the function are
given.
A function declaration is where the function return value type and the parameter
types are specified.
2. A separate declaration at the start of the source file known as the prototype,
which gives the return type and parameter types of the function but no details
of the statements of the function that make up the function body.
There may be more than one declaration for the same function providing all the return
types agree.
#include <stdio.h>
void hello(void);
main() {
int x;
x=2;
hello();
printf("\nx = %d\n",x);
hello();
printf("\nEnd of program.\n");
}
void hello(void) {
int i;
for (i=0; i<3; i++)
printf("hello ");
}
Part A : The C Programming Language 75
2. The first, one line reference to the function hello is the function prototype. It
declares the function. This means it tells the compiler what parameters and
return type it has so the compiler knows how to use it in subsequent calls to the
function.
3. The keyword void is used in the example to show the function has no return
value and no parameters.
4. The reference to hello after main is the function definition which has the
function body between {} to define what the function does.
5. There can be more than one prototype for a function but they must all give
identical parameters and return types. Similarly the function definition must
also match any prototype. The function definition, in fact also counts as a
prototype itself.
6. The function hello could have been defined either before or after main, in
the example there would be no difference.
7. Each function must be separate - it is not possible to define one function inside
another as in languages such as Pascal.
8. The main program is also a function. It could in theory be called like any other
function (but it would make little sense to do so).
#include <stdio.h>
void hello(void);
main() {
int x;
x=2;
hello();
printf("\nx = %d\n",x);
hello();
printf("\nEnd of program.\n");
}
void hello(void) {
int i;
for (i=0; i<x; i++) /* Error! */
printf("hello ");
x=4; /* Error! */
}
The two lines marked with an error comment will cause the compiler to give a syntax
error of "undefined variable" for the variable x.
Similarly, the variable i is local to function hello, so it would have caused an error
if an attempt had been made to use it inside main.
Part A : The C Programming Language 77
#include <stdio.h>
void hello(void);
int x;
main() {
x=2;
hello();
printf("\nx = %d\n",x);
hello();
printf("\nEnd of program.\n");
}
void hello(void) {
int i;
for (i=0; i<x; i++) /* code OK */
printf("hello ");
x=4; /* code OK */
}
hello hello
x = 2
hello hello hello hello
End of program.
x is now declared before the start of main, outside and before any function, therefore
x is a global variable.
ie. It can be used in main or any other function provided it is not also declared
within the function as a local variable.
Part A : The C Programming Language 78
The following code is valid in 'C' but it will not work as expected:
#include <stdio.h>
void hello(void);
main() {
int x; /* x is local to main */
x=2;
hello();
printf("\nx = %d\n",x);
hello();
printf("\nEnd of program.\n");
}
void hello(void) {
int x; /* x is also local to hello
*/
int i;
for (i=0; i<x; i++) /* Deceptive! */
printf("hello ");
x=4; /* Deceptive! */
}
This means the use of x is valid in each function . . . but these are two different
variables.
The program will run but there is no telling how many times the word "hello" will
be printed. It will depend on whatever seemingly random value the function hello
has stored in its variable x, and it will probably be a different value for each call of
the function hello!
Part A : The C Programming Language 79
The following code will not cause any compilation errors but it will not work as
expected:
#include <stdio.h>
void hello(void);
int x; /* x is a global variable */
main() {
int x; /* x is also local to main */
x=2;
hello();
printf("\nx = %d\n",x);
hello();
printf("\nEnd of program.\n");
}
void hello(void) {
int i;
for (i=0; i<x; i++) /* Deceptive! */
printf("hello ");
x=4; /* code OK */
}
<blank line>
x = 2
hello hello hello hello
End of program.
Global variables have default starting values of zero (hence the blank line in the
output).
Local variables do not default to initial values of zero, but have seemingly random
initial values.
Part A : The C Programming Language 80
main() {
int x;
hello(4);
x=2;
printf("\nx = %d\n",x);
hello(x+1);
printf("\nEnd of program.\n");
}
hello(int num) {
int i;
for (i=0; i<num; i++)
printf("hello ");
}
1. The parameters are given between the () following the function name.
3. In the function definition the parameters are the names of special local
variables.
4. The type of each parameter variable must be declared between the () at the
start of the function. Other local variables are declared after the { in the usual
way.
5. In the function call the parameters are the values to be copied into the
function's parameter variables. Each time the function is called the parameter
variables are initialised by whatever values are given in the function call.
Part A : The C Programming Language 81
6. It is possible for the variable parameter to have the same name as a variable
used in the calling code.
In this case the parameter x in hello and the variable x in the main
program would have been two different variables.
7. Single variables (integer, character, etc.) and whole structures can be parameter
variables.
An array can also be a parameter variable but it has some apparently different
properties. The reasons for this will become clear in the section on pointers
used with functions.
As the parameter variable has a copy of the value given in the call, any change
made to the parameter variable cannot change any variable in the calling code.
hello(int num) {
int i;
for (i=0; i<num; i++)
printf("hello ");
num = 100; /* extra line */
}
x = 2;
hello(x);
printf("%d",x);
hello hello 2
ie. the change to num inside hello cannot affect the original value in x within
the main program.
Part A : The C Programming Language 82
1. The function prototype given at the start of the program source file, before the
definition of main or any other function, also specifies the parameter name and
type. This prototype informs the compiler what number and types of
parameters the function has for when it is used in main or elsewhere. The
prototype must correspond exactly with the actual definition of the function or
a compiler error will occur.
However, the use of the name often gives an indication of its purpose so the
name can improve the program readability, and in such cases it is
recommended that the name is given.
3. There may be more than one prototype for the same function but each must
correspond exactly with the first in terms of the number and types of parameter
and return type or an error will occur. If parameter names are given these too
must correspond with other prototypes where the names are given and with the
program definition .
void hello();
In this case the compiler will make no checks on the parameters when the
function is called. It could be called with different numbers of parameters or
different types of parameter without the compiler generating an error.
However, the function is unlikely to be able to cope with these different types
and unpredictable run time errors are likely to occur. It is up to the
Part A : The C Programming Language 83
programmer to ensure the right parameters are used in a function call for a
function with this type of prototype.
7. To specify a function prototype with full compiler checking for a function with
no parameters the keyword void should be used between the () as shown
earlier in this section in the form:
void hello(void);
8. The prototypes for getchar, putchar, printf and other input and
output functions are declared inside the include file stdio.h .
Sometimes a function is defined where some of the parameters can vary in number
and type. printf is just such a function, where the only fixed parameter type is the first
parameter. In such cases the elipses can be used to specify to the compiler that the
function will handle whatever parameters are given as in:
This tells the compiler that the first parameter is a pointer (this is indicated by the *
syntax which is explained later). After the first parameter the ... indicates that there
could be any number of parameters of varying types that could follow.
There can be any number of conventional parameters declared before the elipses as
long as there is at least one. There can be no further parameters following the elipses.
A function can have a value. The value is assigned to the function using a return
statement as in:
int getint(void) {
int ch, num=0;
while ((ch=getchar())>='0' && ch<='9')
num = num*10 + (ch-'0');
return num;
}
Part A : The C Programming Language 84
This function value can then be used in any assignment or expression as in:
#include <stdio.h>
int getint(void);
void main(void) {
int x;
printf("Please input a number: ");
x=getint();
printf("\nand another: ");
printf("\nSum is %d\n", x + getint() );
}
int getint(void) {
int ch, num=0;
while ((ch=getchar())>='0' && ch<='9')
num = num*10 + (ch-'0');
return num;
}
2. Read the number entered by the user, terminated with any non numeric
character.
The int before the function definitions of getint and difference could be
omitted as the default function type is int, however it improves the program
readability to specify the return type explicitly.
If a function returns a type other than int the type must be declared in the function
definition.
The function type must be declared before the function call if it is any type other than
int.
This must be done by either:
1. Putting the function definition before main and other functions where it may be
called:
# include <stdio.h>
main() {
int x=2, y=3, z;
z=average(x,y);
printf("Average is %8.3f\n",z);
}
# include <stdio.h>
main() {
int x=2, y=3, z;
z=average(x,y);
printf("Average is %8.3f\n",z);
}
The return statement sets the value of the function when it is used in an expression.
eg. y = 2 * square(x);
Somewhere inside the function square there should be a statement of the form:
return expression;
float square(float z) {
return z*z;
}
eg. return;
The expression in the return statement must be of the type of the function or
convertible to the function type, so it would be acceptable, for example, to return a
float or integer type but it would be an error to try and return a structure type in the
square function defined above.
If the function is defined with type void then an error is caused if any attempt is
made to:
eg. average(x,y);
2. If a return value is used where none has been supplied by the function an
unpredictable value is given.
eg. x=hello(3); /* x is given an unpredictable value */
If, however, the function has been specified as type void then the above
statement would give an error.
(On some computer systems a return value on main can be used to pass a
signal to the operating system.)
5. Although it is not necessary to declare all functions with an int return type it
is good practice to do so for program clarity.
struct person_type {
char name[5];
int age;
} fred = {"Fred",50};
void main(void) {
output_person_details(fred);
printf("retiring in %d years", diff(fred.age,65) );
}
A function can return a whole structure or it can return single values that can be
assigned to some part of a structure:
struct TimeOfDay {
int hr,min;
char am_pm;
} ;
void main(void) {
struct TimeOfDay Appointment, NextAppointment;
Appointment = GetStartOfDay();
NextAppointment = Appointment;
NextAppointment.am_pm = HalfDayLater(Appointment);
......
etc.
Whole arrays can also be passed to a function, but it is not possible to have an array
as a return value.
#include <stdio.h>
void main(){
int arr[2];
arr[0]=biggest(num);
arr[1]=square(arr[0]);
printf("%d %d\n", arr[0], arr[1]);
}
int square(int x) {
return x*x;
}
#include <stdio.h>
void ChangeName(char string[]);
char name[6] = "Barry";
main() {
ChangeName(name);
printf("Name is %s\n", name);
}
Name is Harry
Part A : The C Programming Language 90
This means that for an array parameter a new local variable does not appear to be
created. Instead, in effect, a new name is given for the original array.
Note that as no new memory is being reserved for the function parameter the array
size in the function definition is irrelevant. This means any array size value would do,
or even, the array size can be left out altogether as in the string parameter of
ChangeName given above!
The flexibility that allows the dimensioning of an array does not apply to arrays of
two or more dimensions! The compiler needs to know, for example, how long each
row is in a two dimensional array in order to know where each element can be found.
It does not need to know how many rows there are, however. This means that the
first dimension can be omitted but subsequent array dimensions must all be specified.
9.24 C Exercise 9
1. Modify your program from the last exercise so that the encryption of a
character is done in a function called "encrypt". This function should take a
single character as its only parameter and have as its return value the character
with the rightmost two bits swapped. (Any method for swapping the bits can be
used). No global variables should be used anywhere in the program.
2. Modify your last program so that instead of calling function gets, the program
calls a function called "getline". This function should have two parameters,
a character array to hold a line of text, and an integer giving the maximum
number of characters the array can hold. The function should not have any
return value. The function should use getchar to read characters and store
them in the array until a newline character is entered. If more characters than
given by the limit are entered before the newline the extra characters are
ignored. The newline character itself should not be stored in the array but a null
string terminator should be stored (ie. the array needs to be of a size one bigger
than the limit given to hold the terminator). Now alter the line structure
definition so that only the first 30 characters of a line will be stored. No global
variables should be used in the program.
Part A : The C Programming Language 91
The use of the getline function will be far safer than using gets as gets
has the danger that no matter how big an array is provided as parameter there is
always the possibility that the user will type in a line of characters too long for
the array. In practice it is recommended that a function such as getline is
always used in preference to gets.
3. Modify your last program so that a function called "output" is used to print
the information in the array of structures to the screen. This function should
have two parameters, the array of structures and an integer giving the number
of structures in the array. The function should not return any value. No global
variables should be used in the program.
Section 10 : Pointers
The exact form of the pointer is of no consequence to the 'C' programmer, it may be
similar to an unsigned integer or it may contain multiple parts, such as segment and
offset (as on some Intel processors).
Pointers can be used to indirectly reference data, often in a more compact and
efficient way than a direct reference. They are commonly used in C programming for
the processing of arrays, and are often essential for passing information out of a
procedure. It can be difficult to avoid the use of pointers for some programming
applications.
The indirect nature of pointers can lead to problems, however. Programs can be
difficult to follow and program faults can be obscure. Debugging a program that has
extensive use of pointers can be a difficult and time consuming operation!
The * before iptr indicates that iptr is a variable that will hold the address of an
integer. iptr, itself, is not an integer.
In this example ptrptr will hold the address of another pointer which, in turn, will
hold the address of an integer.
Pointers are declared with other types of data declarations, and can be declared in the
same statement as single variables and arrays.
A pointer can assign the address of a variable by using the & as a unary operator.
A pointer can be assigned a value from another pointer, possibly with an integer
offset to the address.
All compilers will allow a "nowhere" address to be assigned using the integer 0.
Tests can be made for equivalence to 0.
When assigning the value of one pointer to another, strictly speaking, the pointers
should be declared as pointing to the same data type. Some compilers will accept an
assignment of pointers to incompatible types, but most require a cast to be used. A
cast is usually necessary to assign an absolute value to a pointer.
ie. if iptr holds the address of x then *iptr will refer to x itself and *iptr
can be used wherever x could be used.
Part A : The C Programming Language 94
Where a pointer to a pointer has been declared then double indirection can be used,
ie. **ptrptr. This is an advanced C facility, only recommended for experienced
programmers!
NB. The use of *iptr serves no purpose unless iptr has a value assigned to it
beforehand. It will not necessarily cause an error message to be generated if it has no
value previously assigned, but as iptr will have a unpredictable value, memory can
seem to be randomly accessed and corrupted. This is a common source of erratic
program behaviour that can be difficult to debug.
This means the pointer is of undefined type. Any address value can be assigned to
this type of pointer but it cannot be said what type of variable it points to. ie. The
pointer cannot be used with the * operator:
void *ptr;
ptr = &x; /* This statement is OK */
x = *ptr; /* This statement is illegal! */
To dereference the pointer value directly its type must be changed with a cast.
Alternatively, the pointer can be assigned to a pointer of any other type which can
then be used in the normal way.
NB. Declaring a pointer on the same line as a data variable does NOT imply that the
pointer has the address of the variable.
The syntax of pointer initialisation can be a little confusing. Despite the way it may
appear it is not *ptr2 (what ptr2 points at) that is being initialised, but ptr2 itself.
Both constant pointers and pointers to constants can be defined. The syntax is as
follows:
A constant pointer means the pointer, once initialised, cannot be changed. It will
always point to the same place. The value of the variable pointed at may change but
the pointer itself can't.
A constant pointer to a constant means that the pointer itself cannot be changed and
nor can it be used to change whatever it is pointing at.
*ptr2 = 123; /* OK */
ptr2 = &y; /* illegal */
When an integer, n, is added to a pointer the pointer will move on in memory n data
items of the type that the pointer points to.
The above comments would also have been true if ip was a float pointer and ar
was a float array, or even if ip was a structure pointer and ar was an array of
structures of the same type.
If two numbers of the same type are subtracted, then the result is the number of data
items between the two addresses.
p1 = &ar[3];
p2 = &ar[5];
n = p2 - p1; /* n now equals 2 */
n would still be equal to 2 if p1, p2 and ar referred to a type other than integer.
Arithmetic operations on pointers are legal only where it makes logical sense, this
applies also to constant pointers such as given by the expression &i. Therefore the
following are valid:
(2) Subtracting pointers from pointers, but only if both pointers are of the same
type.
eg. n = p1 - p2; n = p1 - &i;
Note: When * and ++ (or --) are used together on a pointer they have the following
meaning:
*iptr++ - use what it points at, then increment iptr. ie. *(iptr++)
*++iptr - increment iptr, then use what it points at. ie. *(++iptr)
++*iptr - increment and use what iptr points at. ie. ++(*iptr)
An array name when not followed by [] has the value of the address of the start of
the array.
ar is equivalent to &ar[0]
*ar is equivalent to ar[0]
ar+3 is equivalent to &ar[3]
*(ar+3) is equivalent to ar[3]
The name of an array is a CONSTANT pointer, however, so it can not be used on the
left hand side of an assignment.
NB. An array automatically has the array name pointing at some reserved memory
when it is declared. This is not true for a pointer. If a pointer is to be used like an
array then it must be given the address of some suitable memory such as an array of
the same data type.
A constant text string, eg. "Hello There\n", can be replaced in any code
statement (such as a printf call) by a character array containing characters
followed by a null byte. As the character array name is an address it follows that a
constant string must also correspond to an address.
When the compiler comes across a constant string, such as "hello", it does the
following:
As far as printf is concerned the addresses could have been in any form, constant
string, array name or pointer.
Part A : The C Programming Language 99
It is important not to confuse single characters, eg. 'A', and one character strings, eg.
"A".
'A' - Corresponds to the ASCII value of the character (65 in this example).
This will compile OK but will not compare the contents of the array with the
characters F r e d as intended. Instead, the address of the array is
compared with the address where "Fred" has been stored by the compiler. It
will never be true whatever is in the array.
2. cha = "Fred";
This gives a compiler error as this will try to assign the address where "Fred"
is stored to the array name. The array name is constant and cannot have
anything assigned to it. Characters cannot be copied in this way.
chptr = "Fred";
This assigns the address where the characters F r e d are stored to the
character pointer. Note that the characters themselves are not copied
anywhere.
Part A : The C Programming Language 100
eg. struct {
int number;
char letter;
} *sptr; /* Valid structure pointer */
Once a pointer has been declared as a structure or union pointer and it has been
assigned a suitable address value, it it possible to access the components of the
structure pointed at using the -> operator.
This is, of course, meaningless unless sptr has been assigned the address of the
same type of structure beforehand. If not, the compiler will assume the seemingly
random value within sptr is the address of a structure and access it accordingly,
with the obvious errors as a result.
struct bufftype {
char buffer[80];
} *bptr;
then the array name bptr->buffer is still an address, in this case it is a character
pointer. This is effectively a constant, and cannot be assigned a value.
chptr = bptr->buffer;
It is then possible to step the character pointer through the array accessing each
element using *chptr .
Part A : The C Programming Language 101
Each of the structures within the pool array can now hold the address of another
structure within pool. In this way it is possible to make long chains of structures in
any order. This is an advanced use of the C language, but one that is often important
in real time programming as a means of queuing blocks of data. This can be a useful
method of passing information from one processing task to another.
A useful library function for allocating memory to provide extra "links" in a chain of
structures is malloc. This function has a single parameter giving the size of the new
memory required and it returns a void pointer to this memory. The function allocates
memory that has not be used before. Depending on the system it may allocate new
memory not previously allocated to the program or it may allocate part of a block of
memory already reserved for the program known as the "heap". If it is not possible to
allocate memory a zero pointer is returned.
As the function returns a void pointer the return value can be assigned to any pointer
type. A common use would be to allocate the memory for a new structure and assign
it to a structure pointer.
N.B.1. It is important to keep a pointer pointing at the new memory otherwise this
memory is "lost". ie. it is still allocated to the program but there is no way of
accessing it.
Part A : The C Programming Language 102
N.B.2 No assumptions can be made of the content of the new memory allocated - it
will not normally be zero
N.B.3 No assumptions can be made about the position of the new memory - two
consecutive memory allocations will not normally allocate consecutive
memory.
Memory allocated by malloc can be released by calling the function free with a
pointer to the memory to be released as its parameter.
eg. free(chainptr);
This function has a prototype: void free(void *ptr); This is also declared in
stdlib.h.
The following function may have been written to swap the values of two integers:
swap(fred,joe);
will cause a copy of fred to be made in x and a copy of joe in y, swapping the copies
has no effect on fred and joe!
The return value of the function cannot be used as two values need to be returned.
To get round this problem pointers must be used.
This will cause a copy of the addresses of fred and joe to be made.
The function swaps not the address copies, but what the addresses are pointing at.
This means the function swaps the main program variables fred and joe.
It does not matter how many times an address is copied, every copy must still point at
the same memory location.
By this method it is possible to pass information both in and out of a function via the
parameters.
An array name on its own, without [], is a pointer to the start of the array.
int numbers[10];
. . . .
bubblesort(numbers);
Because numbers has the value of the address of the start of the array, it is this
address that is copied.
The function could still be called with array numbers as a parameter exactly as
before.
When it is a function parameter a one dimensional array and a pointer are exactly
equivalent.
But . . .
This is not true for other local or global variables where a declaration of an array
means:
10.25 C Exercise 10
1. Modify your program from the last exercise so that the function encrypt
takes a pointer to the character to be encrypted as its parameter and has no
return value.
2. Modify your program from exercise 10, question 1 so that the function
getline has its first parameter declared as a character pointer. Declare a local
constant pointer variable in this function to point at the end of the storage area
where the input line is to be stored. Alter the code in this function as necessary
so that, other than this constant pointer and the two parameters, no other
variable is used in the function. (Do this by incrementing the first parameter
variable for each new character input.)
Part A : The C Programming Language 105
3. Modify your program from exercise 10, question 2 so that the function
encryptline takes a pointer to a line structure as its parameter and has no
return value. Declare a local character pointer in the function to be used to
point at each character in the line in turn. No other local or global variable
should be declared or used in the encryptline function.
4. Modify your program from exercise 10, question 3 so that the line structure
type also contains a pointer to a similar structure. Alter the main program so
that instead of an array of structures, a chain of structures is used to store the
input lines. Memory for each structure in the chain should be allocated with the
malloc library function. Input should continue until a blank line is entered.
The zero length line should then act as a marker to indicate the last structure in
the chain. The output function should be altered so that it has a single
parameter which is a pointer to the first structure in the chain and it has no
return value. Alter the output function so that if the first structure is blank an
error message is given. No other function should be altered.
5. This is an advanced question for the confident programmer only and can be
skipped if necessary!
Alter your program from the last question so that in the main function a
variable is declared that will hold the address of a pointer to a line structure. ie.
a pointer to a pointer to a structure. Use this pointer as the variable to hold the
address of the pointer that is pointing at the structure currently in use.
The program can now be changed such that the last structure in the chain
containing text has a zero link pointer. When a blank line is input the previous
line structure should have its link pointer set to zero and the current structure
containing the blank line should be discarded by calling the library function
free. If the first line entered is blank then the pointer to the start of the chain
should be set to zero.
The program can now also be altered so that if the malloc function cannot
allocate further memory and returns a zero pointer the input is terminated,
however, the program continues by encrypting and outputting whatever lines
have previously been successfully entered. This aspect of the program will be
difficult to test at this stage but simple testing will be possible after completion
of question 3 in the next exercise.
You will also need to change the output function but the other functions
should remain unchanged from exercise 10, question 4.
Part A : The C Programming Language 106
auto
register
extern
static
These effect the storage of local and global variables and also functions as follows:
This type of storage is known as auto (automatic) and is released as soon as the
function or compound statement is exited.
ie. Anything that can be used in an assignment can be used to initialise an automatic
local variable.
As the stack may get used for other purposes in between calls to a function, the values
of non-initialised local variables will be whatever happens to be in the stack memory.
ie. They will not be set to zero. Furthermore it is not possible for a function to
"remember" the previous value of a variable from the last call to the function.
As the variables are created when the function is entered this means a new a different
variable is created every time a recursive function (ie. a function that calls itself) is
called. This makes recursive functions much easier to program.
Part A : The C Programming Language 107
The register storage class specifier is used for local variables as a hint to the
compiler to allocate a register, if possible, as storage for a simple variable (eg. integer
or pointer).
Allocating a variable to a register rather than a memory location means that all action
associated with the variable, such assigning values to it or using it in an expression,
can be executed much more quickly and efficiently by the computer.
It will not necessarily allocate the variable to a register as there may not be any spare
registers available. Typically an 8 register computer will probably only have three
registers available for allocation to register variables, the other registers being used
for the execution of normal C statements. If there are not enough registers for all
variables declared with the register storage class the compiler will choose which
variables will be put into the registers and which will be stored on the stack like
ordinary auto variables.
Use of registers to hold loop counters or the pointers that step through large lists, for
example, can greatly improve program running speed in places.
N.B. A major restriction in the use of registers is that it is not possible to take the
address of the variable ... as the address does not exist!
The keyword register goes at the start of the variable definition, eg:
As for auto variables register variables are initialised every time the function is
entered, and if it is not initialised it will contain an unpredictable initial value on
entering the function.
Unlike auto and register variables static local variables have their own
permanent memory allocated to them. The memory so allocated is never used for
anything else.
If the variable is initialised it is initialised once only before the program starts.
It is not re-initialised each time the function is entered.
As the initialisation does not take place while the program is running it is
possible to initialise all variable types including arrays and structures in the
same ways as global variables can be initialised.
Part A : The C Programming Language 108
Any value left in the static variable when the function exits will still be
there if the function is re-entered.
The syntax for declaring static variables is similar to that for register variables.
Static variables are useful for variables that need to remember values of local
variables from one call to the next. Eg. A variable to count the number of times a
function has been called.
PrintErrorMessage will only be accessible within its own source code file.
CallCount will be allocated permanent storage (initialised to zero) and will
maintain a count of the number of times PrintErrorMessage has been called.
Part A : The C Programming Language 109
Ordinary global variables without any storage class specifier have the following
properties:
They have their own memory allocated that is not used by anything else.
If initialised the initialisation takes place once only, before the program starts.
They can be used in any function following the declaration in the same source
file or in any other source file if they are declared as extern.
There can only be one declaration of a global variable of this type for each
variable name in the entire program.
This variable storage specifier is used for variables that need to be used in the current
source file, but are also declared in another source file as the normal global type. ie.
There is no storage allocated for them as this has been done in another source file.
There can be as many extern declarations of a variable as required, but there can
only be one default declaration as that is where the associated memory is reserved. It
is possible to have more than one extern declaration for the same variable in the same
source file and it is even possible to have an extern declaration in the same source file
as the default global definition of the variable. Obviously it is important that the
variable type in all declarations of any one variable must correspond.
Part A : The C Programming Language 110
When used on a global variable its use is similar to that of the default type in that
The memory is reserved for each variable and is not used by anything else.
All variables can be initialised and the initialisation takes place before the
program starts.
They can be used in any function in the same source file following the
declaration.
There can only be one global variable declaration of each name in the source
file.
However, they can not be accessed from different source files. ie. The scope of the
variables is limited to the source file in which they are declared.
If a global variable is declared as static in one source file and is also declared in
another source file with the same name, there are two different variables created. Any
change made to one will not effect the other.
The storage specifiers extern and static can also be put on the front of a function
definition:
extern or default class functions can be used anywhere in the same source file as
they are declared and in any other source file. However, if the function is called in a
preceding function in the same source file as it is defined or it is called in a different
source file it is necessary to give a declaration prototype at the top of each such
source file. There can only be one function for any particular name of the class
extern (or default) anywhere in any source file of the program.
static functions can only be called from within the same source file as they are
defined.
Part A : The C Programming Language 111
If a function is specified as static within a source file a function of the same name
may also exist in another source file. Two or more different functions with the same
name can exist in this way. In any source file where a static function is defined,
that function will be used when it is called. In any other source file any extern or
default class function with that name will be used.
Static functions are used to "hide" a function in a source file. This ensures that it
cannot be called from elsewhere in the program outside that file - the function is
effectively "local" to the source file.
11.9 C Exercise 11
1. Examine your program from the last exercise. Which variables, if any, would
be suitable to be designated as register variables?
2. Alter your program from the last exercise (either question 4 or question 5 of
exercise 10) so that a global constant character variable called returnkey is
declared and initialised to equal the newline character '\n'. Alter the
getline function so that it checks for the end of a line by comparing the input
character with the returnkey variable.
Divide your program into three source files. File 1 should contain the main
function and the initialisation of the returnkey variable. File 2 should
contain the functions getline and output. File 3 should contain the
functions encrypt and encryptline. Alter the encrypt function so that it
could not be accessed from any other source file.
3. This question is for those who have completed the the advanced program for
exercise 10, question 5 and have divided the program into three source files as
required in exercise 11, question 2.
Write your own version of the malloc function such that it allocates memory
from its own internal array of 500 characters. Write your own version of the
free function so that it is a dummy function that does nothing. The malloc
function should also send an error message to the screen if it has run out of
memory to allocate (after about 12 line stuctures are allocated).
Put your versions of the malloc and free functions at the end of the first
source file (after the main function) and make them inaccessable from other
source files. Making them inaccessable from other sources may be essential as
these functions are often called from other library functions that need to access
the original versions. Your versions of these functions will then automatically
be called from main instead of the library functions. No changes should be
necessary to any of the rest of the program.
Part A : The C Programming Language 112
In the same way that there is no input and output to the terminal in C there is no input
or output to files in C. All input and output must be done using functions some of
which must be written in a language other than C. There is, however, a standard C
library with ready made file input and output functions available on most systems.
This library is in fact the same library used for terminal input and output.
Further details of the input and output functions to file or to the user terminal are
given in Section C of this book.
12.2 Variable Type FILE, File Pointers and the fopen Function
All I/O access to files in "C" is performed using file pointers. A file pointer variable
is declared using the type FILE defined in <stdio.h>. For example the
declaration statement :
FILE *fileptr;
will define a file pointer that may then be used to access a file for reading, for writing
or for both.
To be able to use a file pointer it must be allocated to a file which at the same time is
said to "open" the file. This is normally done using the library function fopen as
follows:
fileptr = fopen("MYFILE","r");
This opens the file with the name MYFILE for reading only. If the file did not exist
or could not be opened for any other reason fileptr would be set to 0.
The first parameter to fopen can be any constant string, character array or character
pointer, the format for the file name will depend on the operating system.
Part A : The C Programming Language 113
The second parameter must also be a string despite usually being a single letter. The
second parameter string can be any of:
If an existing file is opened for writing the previous contents are usually overwritten.
Once a file has been opened all further access to that file would be done using
functions that use the file pointer. The file's operating system name is never used
again.
This would read a character from the file MYFILE into the integer or character
variable ch.
getc is the file function equivalent of getchar. It has the same purpose except that
it reads consecutive characters from a file rather than the keyboard. Unlike getchar
it needs a single parameter which is a file pointer to tell it which file to read, the file
must have been previously opened for reading and associated with the file pointer
using fopen.
Once the end of the file has been reached, getc will return the value EOF.
EOF is a macro defined in stdio.h that usually equates to the value -1.
The function putc is the file equivalent of putchar, and fprintf is the file
equivalent of printf and are used as follows:
putc(ch, fileptr);
fprintf(fileptr, "\nAnswer = %d", x);
Note that each has an extra file pointer parameter but that this extra parameter comes
after the character in putc but before other parameters in fprintf!
NB. The file pointer is not incremented by a call to getc, putc or fprintf when
used as above, and no attempt by the programmer should be made to increment the
file pointer, as it should always remain the same for a given file. This is because the
type FILE is actually defined as a structure that is set up by fopen to contain all the
Part A : The C Programming Language 114
information about the file, such as the type of access, etc. Any records of position
within the file are kept within this structure.
Further details of these and other file input and output functions are given in Section
C of this book.
Three file pointers are defined automatically by the system and are defined in
<stdio.h>. These are:
stdin - The standard input file pointer
stdout - The standard output file pointer
stderr - The standard error output file pointer
For normal interactive use these file pointers are set to the keyboard input for stdin
and the computer screen output for stdout and stderr. These file pointers are
used by the terminal I/O routines getchar, printf etc. Terminal I/O could also be
performed using the standard file pointers with the file I/O functions.
eg. ch = getc(stdin);
would be equivalent to ch = getchar();
There is nothing special about the standard file pointers. Other than the fact they are
automatically set up without the programmer needing to call fopen they are identical
to any other file pointer variable. They can be copied, reassigned or corrupted just as
any variable can. For example:
fileptr = stdout Any file output using file pointer
fileptr will now go to the
computer screen.
stdout = fopen("DATAFIL","w"); Any output from putchar or
printf will now go to the file
DATAFIL.
stdin = 0; It is now impossible to read from
the keyboard (without reopening
access to the terminal).
A standard UNIX facility that is also available with many other command line
operating systems is to allow redirection of either or both of stdin and stdout
from the the terminal to a file or other device. This is done using the command line
with extra parameters <filename to redirect the input and >filename to
redirect the output to the given files.
eg. MyProg <MyInFil
Part A : The C Programming Language 115
This operating system command would cause the program MyProg to be executed
taking all standard input using stdin (including getchar) from the file MyInFil
instead of the keyboard.
MyProg >MyOutFil
This operating system command would cause the program MyProg to be executed
with all standard output using stdout (including putchar and printf) going to
the file MyOutFil instead of the computer screen. Output to stderr will still go to
the computer screen however.
This command will cause the program MyProg to be executed with standard input
coming from file MyInFil and standard output going to MyOutFil.
If there are other parameters to the program in the command line the redirection
parameters may appear anywhere between the parameters after the program name.
They are not included in the count of parameters in argc and are not put in the argv
array (described in Section 13). They are, in fact, invisible to the program.
In both these cases the program will only "be aware" of the two program parameters,
Param1 and Param2. Access to these program parameters is described in the next
Section.
12.6 C Exercise 12
1. Modify your program from the previous exercise so that the program prompts
the user for the name of a file from which to read the input text and then, if the
file can be opened for reading, it reads the text from the input file terminated by
a blank line or the end of the file. If the user leaves the filename blank or the
file cannot be opened for reading it should read the text from the keyboard as
before.
Similarly the program should prompt the user for the name of a file to which
the encrypted text is to be sent. It should then output the number of letters and
characters to the screen as before but write the encrypted text to the specified
file. If the filename is left blank or the file cannot be opened for writing it
should print the text to the screen as before.
2. If your system is such that the standard output can be redirected to a file instead
of the screen, modify your program such that all error messages will still be
sent to the screen regardless of any redirection of the standard output.
Part A : The C Programming Language 116
By default, the C compiler will give the first named constant (ie. red) a value of zero,
the next a value of one, and so on.
The named constants can then be used anywhere in a program where constants are
valid.
eg. number = red;
The default values for the named constants can be altered by presetting them in the
declaration,
eg. enum billiard_ball {red=10, white=5, spot};
In this example, red will be given the value 10, white 5 and spot 6.
C, not being a strongly typed language like Pascal, does not protect you against
simple errors like assigning billiard ball colours to character variables etc. There is
nothing to prevent any other variable being assigned a constant from the enum
constant list or to prevent an enum variable being assigned ordinary integer values:
This lack of protection takes away much of the advantage of declaring enumerated
type variables. However, enumerated types can be a useful aid to improving program
clarity so, for example, the use of enum as in the example:
if (ball_in_play==white) ...
is more informative than the equivalent:
if (ball_in_play==1) ...
The program could then be ported to a 16-bit processor by changing int to long in
the typedef statement.
would allow integer pointers and twenty element character arrays to be defined using:
POINT first_pointer, second_pointer;
ARRAY name, job_title, department;
Extensive use of typedef has been made in the UNIX operating system in C
source code.
This rather off putting declaration defines fnptr to be a pointer that holds the
address of a function that has an integer as its return value and two parameters, an
integer and a character.
The () round *fnptr are necessary as the declaration without brackets, ie. int
*fnptr(); would define fnptr to be a function that has an integer pointer as its
return value.
Function pointers may be declared with other variables locally or globally such as in
the following:
int x,arr[20],(*fnptr)(int,char),y;
Part A : The C Programming Language 119
It is important that both the return type and the parameter types of the function pointer
match the return type and parameter types of the function it is assigned to. A compiler
error will be generated if there is no match. An exception is if the parameters are
omitted in the function pointer declaration, in which case there is no parameter
checking when an assignment is made to the pointer or when the pointer is used to
call a function.
The above example would not have worked for either getchar or putchar as these
are macros and not true functions. Several standard library functions are not what
they first seem, but macros in disguise defined in stdio.h or some other header file.
It is not possible to have a pointer to a macro.
Once a function pointer has been assigned an address of a function it can be used to
call that function by using (*FunctionPointerName) in place of the true
function name.
The ( ) round *funptr are necessary as otherwise the compiler would try and treat
funptr as a function returning a pointer. This would generate a compiler error.
It is NOT possible to do arithmetic with these function pointers. For example it is not
possible, and makes no logical sense, to increment or decrement a function pointer.
int (*funtable[20])(char[10]);
This would declare a table of 20 function pointers, each pointing to a function with an
integer return and an array of characters as the only parameter.
Unfortunately all entries in the table would also have to be declared separately
beforehand, if the table is to be initialised.
Some operating systems, such as the UNIX operating system will allow any
number of arguments can be passed from the operating system to a C program, by
giving the main program function two parameters, in the form:
argc, an integer, will hold a count of the number of arguments passed to the
program. This will include the name by which the program was invoked, so argc
will have a minimum value of 1.
argv[] is an array of character pointers. The first character pointer, argv[0], will
point to the first program argument (ie. the name of the program). argv[1] will
point to the second program argument, and so on.
eg. /*
This program is called "echo" and it prints
out any arguments passed to it.
*/
#include <stdio.h>
If the program is called from a command line operating system with > as its prompt
such as in the following:
Argument 0 is echo
Argument 1 is Hi
Argument 2 is there
Argument 3 is Mum!
Part A : The C Programming Language 122
13.10 C Exercise 13
2. Modify your program for the last question such that the encryptline
function has a second parameter which is a pointer to a function with void
return type and a single character pointer parameter. Modify the code for the
encryptline function so that instead of calling the encrypt function it
calls the function pointed at by this function pointer. In the main program pass
a pointer to the e n c r y p t function as the second parameter to the
encryptline function.
Introduce an enumerated type variable in your main function that can take the
values odd or even and is initialised to odd. In the input loop replace the call
to encryptline with a switch statement with the enumerated type variable
as the control variable. In the odd case the enumerated variable should be
changed to even and the encryptline function should be called with a
pointer to the old encrypt function as parameter, and in the even case the
enumerated variable should be changed to odd and the encryptline
function should be called with a pointer to the new change function as
parameter. The encryptline function itself and the other functions in the
program should not need to be changed in any way.
4. If your system supports command lines with program arguments for starting a
program, modify your last program so that the input and output text files are
specified as program arguments. If the arguments are not given or if the
program cannot open either file the input should be taken from the keyboard or
output sent to the computer screen as necessary.
Part A : The C Programming Language 123
Programming in
ANSI
Third Edition
PART B
The C Pre-processor
Part B : The C Pre-Processor 125
PART B : Contents
Page
Each source file will normally have a few preprocessor commands, these are, strictly
speaking, not part of the C language itself, though they are associated with the
language. The preprocessor commands each start with a # and must either fit on a
single source line or must be extended with the use of a - at the end of each line to be
continued.
eg.: #include -
<stdio.h>
Part B : The C Pre-Processor 127
occurs, if sooner.
Although not essential, by convention macro identifiers are written in upper case.
A number of macros are predefined, existing at compile time without the need for
any #define pre-processor statement. These macros each have a double underscore
character at the beginning and end of their names. It is not uncommon for other
macros or variables to be defined in the standard header files with either a leading
single or double underscore character. To save confusion and possible error, it is
recommended that programmers do not name any of their own variables or macros
with leading underscore characters.
__TIME__ This corresponds to a string giving the current time of day in hours,
minutes and seconds using a 24 hour clock as in the example
"01:20:30".
__DATE__ This corresponds to a string giving the current date as in the example
"Dec 25 1996".
128 Programming in ANSI C
__FILE__ This corresponds to a string giving the name of the source file being
compiled as in the example "myprog.c". This value can be altered
using the #line pre-processor statement described below.
Note that it is not possible to delete the macros definitions of any of these standard
predefined macros using the #undef pre-processor statement.
This will define a macro with parameters. In the macro expansion, the <macro
text>, when replacing the <macro name>, will have all instances of <param1>,
<param2>,... replaced by corresponding arguments from the macro call.
Note that the # must, in theory, occur in column 1 of any line though most compilers
will recognise it providing it is preceded by only spaces or tabs.
Although they look like functions when macros are used with parameters they are not
the same as functions. The macro text gets substituted for every single macro call, so
Part B : The C Pre-Processor 129
the corresponding code gets repeated in memory as many times as the macro is used.
Functions are only stored in memory once so that a jump is made to the function code
every time a call is made, with a jump back again on the return.
This property of looking like functions is used in many of the standard libraries.
getchar and putchar, for example are implemented by many C systems as
macros defined in stdio.h rather than true functions.
To get round this problem the #define statement has the facility to turn any macro
parameter into a string by inserting a # in front of the parameter in the macro
definition. This still does not allow the macro parameter to be embedded directly in
the string but this can be achieved indirectly using the concatination of adjacent
strings.
To get round this problem the #define pre-processor statement has the facility to
recognise the macro parameter when it is seperated from other names by a ##. The
## itself is eliminated in the expansion.
Problem 1:
If the macro is used in: joe = square(fred + 1);
This becomes: joe = fred + 1*fred + 1;
Not what was intended!
Problem 2:
If the macro is used in: joe = 1/square(fred);
This becomes: joe = 1/(fred)*(fred);
which is equivalent to: joe = (1/fred) * fred;
Not what was intended!
Part B : The C Pre-Processor 131
This can be cured by putting the whole macro text in () in the macro definition.
eg. #define square(x) ((x)*(x))
If used in: joe = 1/square(fred);
there is no problem as the macro expands to:
joe = 1/((fred)*(fred));
Problem 3:
If the macro is used in: joe = square(fred++);
This becomes: joe = ((fred++)*(fred++));
which will increment fred twice! - Not what was intended!
This last problem cannot be cured! Care must be taken when using this type of
embedded assignment on a function parameter just in case the function is really a
macro - in which case errors are possible.
Problem 4:
Another problem can occur if a ";" is accidently put on a macro definition:
eg. #define square(x) ((x)*(x));
The whole of the text from another file can be inserted into the source by using
#include.
Both examples cause inclusion of the file with the name filename at this line in the
source code file.
132 Programming in ANSI C
If the first form is used, the file is first searched for in the current directory.
If the second form is used, the file is first searched for in a standard directory.
By convention all included files have a ".h" extension on their name by this is not
essential, any name could be used.
It is possible for an include file to itself have another #include within it up to any
depth of nesting.
It is not uncommon for novice C programmers to divide their larger programs into
separate source files, but then use the #include facility to insert the different
sources containing the function definitions into the source containing the main
function. This means all the source code is compiled together and whenever a change
is made to the code of one source file the whole program must be recompiled. This is
bad practice. It removes one of the principle reasons for splitting the source code in
the first place - the code could just as well have been left in a single source file.
The purpose of the header file used in this way is to ensure consistency across all
source files so that, say, no mistake accidentally made in the definition of structure
components in different source files.
The way the different source files are compiled into one program will depend on the
computer and operating system involved, but it will often involve putting the names
of each file into some form of "project" file or "make" file to signal that each piece of
code belongs to the same program.
Part B : The C Pre-Processor 133
the preprocessor will check to see if the macro has been defined in the case of
#ifdef or has not been defined in the case of #ifndef.
In both cases, if the test evaluates to false, all subsequent source lines are ignored by
the compiler until either of the pre-processor commands, #else or #endif
occur.
If FLAG had already been defined, the current directory would be searched for the file
proj.h, otherwise the standard directory would be searched.
FLAG could be defined earlier in the source file (using #define) or it could be
defined with a compiler command. (eg. Using using the D switch in the compiler
command line on a UNIX system.)
This will include the code up to the line starting with #endif in the compilation
providing <expression> evaluates to a non zero result. The #else option may be
used between the #if and #endif in the same way that it is used with #ifdef and
#ifndef.
134 Programming in ANSI C
One or more #elif statements may also be used between a #if and #endif. These
represent else-if options such that the following:
ie. Only one of statements1, 2, 3 or 4 would be included in the resulting source code.
Examples
#if DEBUG
printf("num now = %d\n",num);
#endif
In these examples MAX, DEBUG, LEN and BOUNDS would have had to have been
previously defined using a #define preprocessor statement or by the use of a
compiler switch. The #error statement is described in the next section.
1. Alter your program from the last exercise to create a macro called LINELEN
which will expand to an integer constant equal to the number of characters on a
line that will be accepted, encrypted and output (ie. further characters on the
line are ignored). Use this macro in your program where appropriate. Test your
program by altering it to a number such as 20, that is lower than was
previously used in the program.
2. Alter your previous program to include two macros called ENCRYPT and
CHANGE. These macros should both take a character value as a parameter and
should equate to an expression giving an encrypted value of that character. The
ENCRYPT macro should encrypt the character by swapping the rightmost two
bits and the CHANGE macro should encrypt by complementing the third bit
from the left. Use these macros within the encrypt and change functions
respectively. Why would it not have been possible to completely replace the
encrypt and change functions with macros?
3. Alter your previous program to include a macro called TRACE which takes the
name of a function as the single parameter. The macro should output a message
to the screen with the words "Now reached function xxx" where xxx is the
function name given as the parameter value. Modify the g e t l i n e ,
encryptline and output functions so that this macro is the first thing
executed in each function.
Now alter the program so that if the macro DEBUG is defined the TRACE macro
is defined as above but if DEBUG is not defined the TRACE macro becomes a
dummy macro that does nothing. If your compiler enables a macro to be
defined using compiler switches use it to test the program with and without the
DEBUG macro being defined. If not, insert a definition for the DEBUG macro at
the top of your program so it can be tested.
4. Alter your previous program so that a suitable common header file is used in
each of the three source files.
136 Programming in ANSI C
Programming in
ANSI
Third Edition
PART C
The Standard C Library
Part C : The Standard C Library 137
PART C : Contents
Page
Page
Page
1.1 Standardisation
A standard C library has been defined for UNIX systems and this is usually
implemented on other systems. How full an implementation of the standard
library is available will depend on the system used. Some routines that interact
with the UNIX system would make no sense with other operating systems
and are unlikely to be available on these systems. Most of the commonly used
routines, however, are relatively "standard" and are generally available, though
minor differences may exist.
Some of the library functions are not true functions at all, but are defined as
macros. These macros are defined in header files, such as stdio.h, and
should be included in the C source file using
#include <stdio.h>
stdio.h also has other important macros for input and output. It is normally
necessary to include this header in all sourcefiles that contain any I/O code,
whether to a terminal or to a file.
Use: putchar(ch);
Examples:
putchar('x'); /* Output the letter x */
putchar(27); /* Output the escape character */
putchar(i); /* Output character in variable i */
putchar('\n'); /* Output a newline */
Use: puts(addr);
Examples:
puts("Hello"); /* Output string Hello and a
newline character */
puts(arr); /* Output string in array arr and
a newline character */
if (puts(cptr) == EOF) errcount++;
/* Output string with address in
the character pointer cptr and
a newline.
If the output fails increment
variable errcount */
Part C : The Standard C Library 143
There is no check that the parameters given are the right number or type. It is
up to the programmer to ensure that printf is called with sensible
parameters.
Other format variations that are not available on all systems are:
(all but the first available in release 3.0 UNIX):
%D %U %O %X - may be equivalent to %ld %lu %lo %lx.
%+d %+<n>d - forces a + to be output before +ve numbers.
%.<m>d - gives the minimum number of digits for output giving
leading zeros if necessary. Can also be used with field
width in the form %<n>.<m>d
%*d %*. *f - the number values to be substituted for * are taken from
the parameter list preceding the parameter to be output.
%X - as %x except letters are output in capitals.
%E %G - as %e and %g except the e is output as E.
Examples:
printf("\nHello%3.2s/%20c/%d/%-5x/%6.2f/\n",
"Edward",32,123,0177777,12.3456);
printf("\n%-4d/%2d,%4s,%7.3e/",
32,123,1234,"hello",-12.3456);
Although most systems will automatically echo characters there may well be
differences in how the RETURN key is echoed. Although it is passed to the
input as a newline character '\n' many systems will give only a carriage
return and no line feed as an echo.
3.2 getchar - Routine to get a single character from the standard input.
Use: ch = getchar();
Examples:
ch = getchar(); /* Read a character into variable ch */
getchar(); /* Read and discard a character */
while ((ch=getchar()) != '\n'){....
/* Keep reading a character until a
RETURN is input */
146 Programming in ANSI C
3.3 gets - Routine to get a line of input from the standard input.
Use: gets(addr);
Examples:
gets(buf); /* Reads a line into the character array
buf */
gets(cptr); /* Reads a line into the memory pointed at
by the character pointer cptr. */
printf("\nYour input string is: %s\n",gets(buf));
/* Reads a line into the char array buf
and outputs it with a preceding
"Your input string is: " */
3.4 scanf - Routine to read formatted input from the standard input.
(It is the analogue of printf)
Use: n = scanf(format,p1,p2,p3.....);
Where format is the control string giving the format specifiers for the input
and the parameters p1, p2 etc. are pointers to variables to be set from the
input.
The value returned, n, is the number of variables that have successfully been
assigned values from the input. EOF is returned if end of file is detected before
anything is read. If input is found to be of an unexpected form scanf
terminates at the start of that input and a number less than the number of
parameter pointers may be returned.
All "whitespace" characters in the control string are ignored and, except when
characters are read with %c or %[], whitespace characters are ignored in the
input (ie spaces, tabs, newlines and formfeeds). Therefore, if scanf cannot
find all it wants from the current input line it will continue reading on the next
line.
Part C : The Standard C Library 147
Non whitespace characters in the control string must exactly match those read
from the input except following a % in the format control string where format
control characters are expected similar to those of printf. If the input
characters do not match the control characters or are of the wrong type for a
formatted input scanf terminates leaving the offending character as the next
character to be read.
Please note that following the format control parameter string ALL
PARAMETERS MUST BE POINTERS!!! This is one of the most common
errors when using scanf. Remember that array names are already pointers.
If the last non whitespace characters in the control string is a format control
such as a %d then there may be side effects when reading from a terminal.
scanf will be forced to read one character too many in order to determine
when the number terminates. This extra character is "put back" using ungetc,
ie. it is stored in a buffer even when the input is normally unbuffered. This
character is the first character "read" on the next input request. This can affect
interactive I/O making the I/O appear in the wrong order. To get round this
problem use such as scanf("%d%c",&n); Alternatively if the input is line
oriented it may be better to use a combination of gets and sscanf to read a
line before processing it. This last method is often the most useful when a
buffered input system is used.
Example:
If the following input:
Use:
len = sprintf(addr,format,value1,value2,value3....);
Where addr is the address of the memory where the string is to be placed.
format is the control string and value1, value2, etc. are the values to be
formatted as for printf
The return value placed into len is the length of the generated string
excluding the null terminator.
The prototype for this function would be:
int sprintf(char *, char *, ...);
Examples:
sprintf(&buff[8],"%2d/%2d/%4d",day,month,year);
/* Code to generate the ASCII for the date in
the array buff from buff[8] onwards. The
null terminator will be in buff[18]. */
Where addr is the address of the memory string to be "read". format is the
control string and p1, p2 etc. are pointers to the variables to be set from the
"input" values.
As for scanf the return value, placed in num, is the number of variables that
have been successfully assigned values from the input string.
The prototype for this function would be:
int sscanf(char *, char *, ...);
150 Programming in ANSI C
Example:
n = sscanf(datestr,"%d/%d/%d",dayptr,monthptr,yearptr);
/* Takes the date given by the ascii string at
datestr and sets the three integers pointed
at by dayptr, monthptr and yearptr with the
day, month and year values. n is set to 3
if the date was in a satisfactory format
for interpretation. */
4.3 atoi, atol and atof - Functions to convert ASCII strings to integer,
long integer, and floating point numbers.
Use: i = atoi(intstring);
l = atol(longstring);
f = atof(floatstring);
Examples:
static char buff[] = "\n\t -1.2345e2";
int i,atoi(char *);
long l,atol(char *);
double f,atof(char *);
i = atoi(buff); /* i now has value of -1 as the
terminates the number */
l = atol(buff); /* l now has value of -1 */
f = atof(buff); /* f now has value of -123.45 */
Part C : The Standard C Library 151
5.1 fopen - Function to open a file for reading or writing and return a file
pointer associated with the file.
If a file is opened with "w" or "w+" and it already exists the previously
existing file may be deleted or the file may fail to open. Where the operating
system allows different generations of a file to exist (as on DEC systems)
the next generation of the file is normally created.
The returned value, put into fileptr, is a file pointer to be associated with
the opened file. If the file cannot be opened a value of 0 is returned.
This function has a prototype: FILE *fopen(char *, char *);
Example:
if ((fileptr = fopen("DATA.FIL","r")) == NULL)
puts("Cannot open data file for reading!");
5.2 fclose - Function to close a file associated with a given file pointer.
Where fileptr is the file pointer associated with the file to be closed.
The return value put into flag is zero if the file is successfully closed or EOF
otherwise.
As all files are closed automatically when the program terminates, this
function is often not required.
This function is useful to close a file before reopening with different file
access, or to allow other users access to the file. As there is a practical limit to
the number of files that may be opened at one time it may be necessary to close
some files before opening others if a lot of files are being handled.
152 Programming in ANSI C
It may be necessary to call fflush to ensure all buffered output goes to the
file before it is called.
Example:
fclose(fileptr);
5.3 freopen - Function to close a file associated with a file pointer and open
another file to associate with the file pointer. This is more
efficient than using fclose followed by fopen.
(Not available on some systems).
Use: fflush(fileptr);
Where fileptr is the file pointer associated with the output file.
This routine may be necessary for output files before the file is closed by
fclose or the program terminates as, except for stdout and stdin, this
flushing of buffers may not be done automatically.
Example: fflush(Myfileptr);
Part C : The Standard C Library 153
6.1 fputc and putc - fputc is a function and putc is a macro to write
a character to a file.
They are identical in operation.
Where ch is the character to be written and fileptr is the file pointer for
that file.
The return value, put into flag is the character value, or if the write fails,
EOF is returned.
As putc is usually a macro it may cause an error if it is declared in a
prototype.
Examples:
fputc('\n',fptr); /* Write a newline to the file */
putc(ch1,stderr); /* Output character ch1 to standard
error output */
6.2 fgetc and getc - fgetc is a function and getc is a macro to read a
character from a file.
getc can be used with the function ungetc.
Use: ch = fgetc(fileptr);
ch = getc(fileptr);
Example:
do {ar[i]=getc(filptr);} while(ar[i++] != '\n');
if ((ch=fgetc(fptr)) == EOF) puts("End of input data");
154 Programming in ANSI C
6.3 ungetc - Function to 'unread' a character back into a file opened for
reading.
Where ch is the character to be put back into the file and fileptr is the file
pointer for that file.
Later reads by getc will take the 'unread' character before reading from the
file. fgets and fscanf will also get the unread character. fscanf calls
ungetc to return characters that terminate digit strings etc.
The return value, put into flag, is EOF if it cannot unread the character.
There is no need to unread a character that has been previously read. ch may
have any character value.
ungetc may be used with stdin, in which case getchar will read the
'unread' character before reading from the keyboard.
UNIX C only guarantees that one character can be unread from a file at a
time. Other implementations may allow a series of characters to be unread on
a last-in-first-out basis.
NB. At least one character must have been read from the file before ungetc
will work.
Any attempt to unread EOF is rejected with an error message.
Example:
c1 = getchar(); /* c1 will be read from the
keyboard */
ungetc('x',stdin); /* 'unread' an 'x' to stdin */
c2 = getchar(); /* c2 will be set to 'x' */
c3 = getchar(); /* c3 will be read from the
keyboard */
Use: fputs(addr,fileptr);
Where addr is the address of a null terminated string and fileptr is the
file pointer for the file.
Unlike puts this function does NOT append a newline character.
EOF is returned is there is a write error.
Example:
if (fputs("Memo from Fred\n",fileptr)==EOF) {
fputs("Cannot write to output file!\n",stderr);
}
Part C : The Standard C Library 155
6.5 fgets - Function to read a line of characters from a file. The read is
terminated by a newline or end of file.
Use: fgets(addr,max,fileptr);
Where addr is the destination string address, max is the maximum number
of bytes to be assigned including a null terminator, and fileptr is a pointer
to the file.
Unlike gets a maximum number of characters to be assigned must be given.
As this includes the null byte terminator, at most max-1 characters will be
read from the file.
Unlike gets the terminating newline character is included in the assigned
string.
The return value is the destination address given by addr or 0 if EOF is the
first 'character' encountered on a line.
If the return value is used a prototype is required as follows:
char *fgets(char *, int, FILE *);
Example:
char *fgets(),buff[82];
.....
/* Copy text line by line from one file to another. */
while (fgets(buff,82,file1) != NULL) fputs(buff,file2);
6.6 fprintf and fscanf - Functions for formatting text file output and
input, respectively.
Use: fprintf(fileptr,format,p1,p2,p3 .... );
fscanf(fileptr,format,p1,p2,p3 .... );
These functions are identical to printf and scanf except that they write to
and read from the file given by the extra fileptr parameter.
Unlike most other C file handling functions the fileptr parameter comes
before the other parameters.
The fread and fwrite functions read from and write to a binary data file.
They are identical in format and differ only in the direction of data transfer.
Where addr is the address of the buffer in memory which contains number
data items each of size bytes. fileptr is the pointer to the file.
The return value, actual, is the actual number of data items transferred.
This will normally be equal to number unless an I/O error occurs, or in the
case of fread, it could mean the end of the file was encountered.
156 Programming in ANSI C
Example:
#include <stdio.h>
main() {
FILE *Infil, *Outfil;
int Actual, buff[64];
infil = fopen("OLD.DAT","r");
outfil = fopen("NEW.DAT","w");
while ((actual=fread(buff,sizeof(int),64,infil))>0)
fwrite(buff,sizeof(int),actual,outfil);
fflush(f2ptr);/* Make sure everything is written */
}
6.8 fseek - Function to alter the current position within a file without
having to read or write.
Use: fseek(fileptr,offset,mode);
Where fileptr is the pointer for the file, and offset and mode give the
position in the file as follows:
offset is measured in terms of the record size. For a sequential text file
offset is measured in characters.
offset must be a long integer! To move to the beginning of the file, for
example, use fseek(fileptr,OL,O);
On non UNIX systems there may be restrictions on the use of this function.
On some DEC operating systems, for example, this function can only work
on files of fixed length records.
fseek returns 0 if successful, EOF otherwise.
Part C : The Standard C Library 157
Examples:
Use: rewind(fileptr);
Where fileptr is the pointer for the file, and position is set to the current
position in the file.
The return value is a long integer, therefore ftell requires a prototype
long ftell(FILE *);
If there is any possibility that the file will be of such a size that position
will be greater than 64K, then position should be a long integer to receive
the full return value.
EOF (-1) is returned on error.
This function may not be available on non UNIX systems or may only be
available on certain types of file.
Example:
FILE fptr;
long pos,ftell(FILE *);
....
pos = ftell(fptr); /* Save the file position so we
can return to this point later
using: fseek(fptr,pos,0); */
158 Programming in ANSI C
Example:
if (feof(fptr)) puts("End of input file reached");
6.12 ferror and clearerr - Functions to indicate if a file I/O error has
occurred and to clear the error if it has.
Example:
if (ferror(fptr)) clearerr(fptr); /* Clear any error */
Part C : The Standard C Library 159
File access via file pointers is relatively "high level" in operation. File input
and output buffering, for example, is handled by these routines automatically
without the programmer needing to know what is happening. The functions
that use these file pointers in turn call various system level routines to access
these files.
The system routines access the file via a file descriptor number. Every file,
therefore, has a file descriptor number. It is not necessary for a file to have an
associated file pointer if it has been opened by a system level call, but it will
have a descriptor number.
Although it is not necessarily the case the standard file pointers stdin,
stdout, and stderr are usually associated with the file descriptor numbers
0, 1 and 2 respectively.
Note that using the system call 'read' it is possible to by-pass the C file
buffering. This will not by-pass any operating system buffering, however.
7.2 fileno - Gives the file descriptor number for a file with a specified file
pointer.
Example:
7.3 fdopen - Associates a new file pointer to a file given by a file descriptor
number.
These are the system I/O functions that use or allocate file descriptor numbers.
All will return EOF, (-1) if an error is encountered. Most have a direct
equivalent file pointer function but note that lseek is the equivalent of both
fseek and ftell.
close(descrptr);
Closes the file. Returns 0 if successful.
Copies str2 to str1, including the null byte terminator. The return value, if
used, is the address of the destination string str1.
Copies at most n characters from str2 to str1. If str2 has less than n
characters str1 is padded with null bytes. The return value, if used, is the
address of the destination string str1.
Appends str2 on the end of str1 leading a single null byte at the end of the
whole string. The return value, if used, is the address of the destination string
str1.
Compares strings lexicographically. The return value is equal to, less than or
greater than zero depending whether str1 is equal to, less than or greater than
str2.
Eg.
if (isdigit(ch)) break; /* Break if ch is a digit */
ch = toupper(ch); /* Make ch a capital letter */
NB. As these functions may really be macros, they should not be declared in
any prototype before use, and any possible side effects should be avoided.
The statement, for example: flag = isdigit(ch++); may cause errors!
9.5 isalnum - True if the character is an upper or lower case letter or a digit.
9.11 iscntrl - True for control characters (octal 0 to 37, and 177).
164 Programming in ANSI C
9.14 toupper - Returns the upper case equivalent of the given character. If the
character given is not a lower case character then it itself is
returned.
Eg. ch = toupper('a');
/* Will assign 'A' to ch */
9.15 tolower - Returns the lower case equivalent of the given character. If the
character given is not an upper case character then it itself is
returned.
Eg. ch = tolower('A');
/* Will assign 'a' to ch */
Part C : The Standard C Library 165
With the exception of abs all the functions take parameters of type double
and have return values of type double. Prototypes for these functions are
provided in the header file <math.h>. This header file may include macro
versions of some of the functions (such as abs).
Functions other than those given here (such as the Bessel functions) are often
available depending on the system used.
10.2 abs - Function (or macro) to return the absolute value of an integer.
Use: j = abs(i);
Example:
newptr = malloc(100);
/* allocates 100 new bytes of memory */
11.2 calloc - Function to allocate and clear new memory of size given by the
number of data items.
Where the number of bytes allocated is number times the size of each data
item.
The return value is the address of the new memory, or 0 if more memory
cannot be allocated.
As for malloc, consecutive calls do not in general allocate consecutive
blocks of memory.
Unlike malloc, calloc zeros the new memory.
The prototype is normally declared in the standard header: stdlib.h
The prototype is: void *calloc(size_t, size_t); where size_t
is defined in stdlib.h and usually equates to unsigned long.
As the return type is a void pointer, the return value can be assigned to any
pointer type without the need for a cast.
Example:
pool = calloc(100,sizeof(struct stype));
/* Allocates and clears enough memory for
100 structures of type stype */
168 Programming in ANSI C
Where number is the new size in bytes for the memory block. oldptr is
the previous memory position and newptr is set to the new memory position.
The block of memory may have moved! 0 is returned if memory cannot be
reallocated.
Whether or not the memory block is moved, the data in that memory is
preserved up to the common data size.
The prototype is normally declared in the standard header: stdlib.h
The prototype is: void *realloc(void *, size_t); where size_t
is a macro defined in stdlib.h and usually equates to unsigned long.
As the return type is a void pointer, the return value can be assigned to any
pointer type without the need for a cast.
As the first parameter type is also a void pointer, any pointer type can be
used as a parameter without the need for a cast.
Use: free(ptr);
In the standard "C" library there are many functions for use with the UNIX
operating system. These are obviously system dependent though there are
often routines provided with other systems that perform similar or identical
tasks. The functions given in this section are usually provided on other
systems though sometimes with slight differences. The only function that can
be relied on to exist is the function "exit", though differences may exist as to
how the parameter is handled.
12.2 exit - Function to exit the program. All open files are closed and any
output in the buffers for stdout and stderr is flushed.
A condition code is passed to the calling operating system.
Use: exit(n);
Example:
if (status==panic) exit(-1); /* Exit with error -1 */
exit(0); /* Normal exit */
170 Programming in ANSI C
Where addr is the address of the null terminated string containing the system
command. stat will be set to the exit condition code given by that command.
If the command is to execute a program written in "C" on a UNIX system
then stat will be set to the value of the parameter of any exit function call
in the called program, or zero on a normal program termination.
Examples:
system("DIR *.C"); /* Give a directory listing of all
.C files on a VAX/VMS system */
if (system("cc prog.c") != 0)
puts("\nCompilation failure!");
/* Compile prog.c on a UNIX
system and give a message
if the compilation fails.*/
Use: sleep(n);
Example:
sleep(60); /* Pause for one minute before
continuing with the program. */
Use: srand(init);
x = rand();
y = rand();
Where init is a starting integer to start the random sequence. x and y are
set to pseudo random values in the range 0 - 28158.
The numbers generated are not truly random but will follow a set sequence
depending on the starting value.
Part C : The Standard C Library 171
Where a call to setjmp will mark the destination for a future long distance
jump using longjmp. The savearray is an integer array to hold 'the
environment' to be restored when the longjmp is executed.
A normal call is necessary to setjmp before longjmp can be called. For
this normal call the return value of setjmp, put into val, is 0.
A subsequent call to longjmp will return not to the statement following the
longjmp call, but to the statement following the setjmp call. This time,
however, the return value will be the integer given by the return value
parameter of longjmp.
There may be more than one longjmp call to the same setjmp destination
providing all longjmp calls have the same setjmp savearray parameter.
There may be more than one setjmp destination providing each destination
has its environment saved in separate savearray variables.
The size of the savearray parameter will vary from system to system.
However there will normally be defined a type jmp_buf to give an array of
the appropriate size. To define the type #include <setjmp.h> needs to
be included at the head of the file.
There may be the important restriction that the function that contains the call
to setjmp should not be exited before the corresponding call to longjump.
setjmp and longjmp need not be declared before they are called.
Example:
#include <setjmp.h>
.......
jmp_buf save; /* declare the save array */
int v;
.......
v = setjmp(save); /* setup save for longjmp */
switch(v) {
case 0: puts("Got here from normal setjmp call");
break;
case 1: puts("Got here from first longjmp call");
break;
case 2: puts("Got here from second longjmp call");
}
.......
longjmp(save,1); /* jump to setjmp and set v to 1 */
.......
longjmp(save,2); /* jump to setjmp and set v to 2 */
172 Programming in ANSI C
12.7 link - Function to create a new link name for an existing file.
Use: link(oldname,newname);
Where oldname is the name of the existing file and newname is the new
link name. Both names are given as character strings.
It is not necessary to open the file before linking the new name.
This function will only be available on operating system that support file link
names, such as UNIX.
0 is returned if successful, -1 (EOF) if not.
Example:
if (link("FRED.DAT","JOE.DAT")==EOF)
puts("ERROR - Cannot link FRED.DAT to JOE.DAT");
12.8 unlink - Function to unlink a file name and delete a file if there are no
other names linked to it.
Use: unlink(filename);
Example:
if (unlink("FRED.DAT")==EOF)
puts("ERROR - Cannot delete file FRED.DAT");
Part C : The Standard C Library 173
Programming in
ANSI
Third Edition
PART D
C Program Accuracy and Style
Part D : C Program Accuracy and Style 175
Part D : Contents
Page
1.1 Introduction
There is nothing more annoying than a program that is fully working in that it
compiles and runs, but it fails to do what is expected. The errors in the program are
run time errors which are by their nature usually far harder to trace than errors that
prevent the program compiling. One of the distinguishing features of C, compared
with Pascal and most other procedural languages, is that with a little experience it is
relatively easy to get a C program to compile. Programs in C tend to have a far higher
proportion of run time errors than their equivalents written in other languages with
the consequence that C programs can take more time and effort to debug.
This section gives a check list for some of the more common C run time errors. It is
certainly not a complete list but it may prove to be worthwhile checking to see
whether a program has any of the errors given first before any more elaborate
debugging techniques.
Has the assignment operator = been confused with the comparison operator ==?
This must be the most common run time error in C! Even very experienced C
programmers will make this mistake from time to time. It is worth checking for
this error before any other step is taken!
Consider the statement:
if (xyz = 100) .....
It "reads" as though it was correct but in fact the expression will always be
true. The value 100 is assigned to xyz so whether xyz was equal to 100
before does not matter - it is certainly equal afterwards. The value of the
assignment is 100 which as a non zero value always equates to true. The
statement should be written:
if (xyz == 100) .....
Some programmers carelessly use & when they require && or | when they
require || in logical expressions. Usually, but not always this causes no
problem, but consider:
if (xyz == 100 & testflag) .....
Part D : C Program Accuracy and Style 177
&& and || always give either 0 or 1 as the result so their use when the bitwise
& or | is required is unlikely to give the correct result.
Has the bitwise complement operator '~' been confused with the logical "not"
operator '!' ?
In English the terms complement and "not" can be used for bitwise and logical
operations but these operators should not be confused.
This will always be true as the bitwise complement of all bits of the value 0 or
1 will give a non zero answer.
Whereas: if ( !(xyz == 100) )
is equivalent to: if (xyz != 100) .
Has a bitwise operation been used with a comparison expression without using ()?
The bitwise operators have a lower precedence than the comparison operators
such that:
if (ch & 0x7F == 'x') ......
will always evaluate to false. This is because the mask is compared with the
letter x which will give a result of false (ie. zero) which is then used in a
178 Programming in ANSI C
bitwise & with ch to give a further result of zero. To mask before comparing
with the letter x use:
if ((ch & 0x7F) == 'x') ......
Has the operator '*' been assumed to have a greater precedence than the operator
'/' ?
Accessing an array beyond its bounds must be the secondmost common C error
(after the confusion of = and ==). It must be remembered that even array
elements with negative indices will be accepted in C. No run time error is
caused by such action, C takes whatever it finds in memory. This can lead to
seemingly random corruptions of memory which can be very difficult to trace.
Has a loop that steps through an array gone one element to far?
Such a loop will access arr[10] which does not exist. The terminating
condition of the loop should be i<10 .
Has a character array been declared large enough for a null string terminator?
To hold the character string "Hello" an array must have at least 6 elements
even though there are only 5 letters in "Hello" as there is an implied null
character terminator to the string.
Part D : C Program Accuracy and Style 179
Usually such action will cause a compiler error, but this will not be the case if
the assigned array is a function parameter such as in:
void setup(int arr[4]) {
static int setter[4] = {1,2,3,4};
arr = setter;
}
This function will not give a compiler error as the parameter is really a pointer
to the array. The function will not have any effect on the array passed to the
function in the calling code, however, as the assignment merely changes the
pointer parameter, it does not copy the array elements. To copy an array a loop
must be used.
Has an attempt been made to compare array contents with a simple comparison
operator?
This will always be false regardless of whether the content of the two arrays
are the same or not. It is the address of each array that is being compared. A
loop will be required to compare the contents of the arrays.
The assignment will not work as it will be the address of the string "Hello" that
is being assigned to the strg array pointer. This will usually cause a compiler
error, but it will not do so if the array is a function parameter. In this case, as in
the array assignment, the array pointer is changed, the elements of the string
180 Programming in ANSI C
are not assigned. Use the standard library functions strcpy or strncpy to
copy a string as in:
strcpy(strg,"Hello");
Has a comparison been made between a character array and a constant string?
"A" and 'A' are not the same thing. "A" is a string stored in memory where
the A is followed by a null character terminator. When used in an expression
"A" has the value of the address where the string is stored. 'A' is a character,
equivalent to the value 65 if the ASCII character set is used. These cannot
normally be interchanged without causing a compiler error but beware of
functions such as printf where parameter type checking is omitted.
eg. printf("%c","A"); /* wrong */
printf("%s",'A'); /* wrong */
printf("%s %c","A",'A'); /* right */
If a function's prototype does not specify the parameter types or contains the ...
ellipses then parameter type checking is not carried out when the function is
called. This may be the case in some older header files created before the ANSI
standard. It is up to the programmer to ensure such functions have the right
types. A common error is to use integer constants when the function requires a
long, float or double value.
eg. double x,sqrt();
x = sqrt(2);
x will not have the square root of two if a float or double parameter is
required. Wherever possible specify the parameter types in a function
prototype.
If the use of a global variable is intended, ensure the variable is not redeclared
as a local variable. Remember that function parameters also count as local
variables. If redeclared locally a new variable is recreated so that two variables
of the same name exist. All reference to the variable name within the function
where it is declared locally will be to the local variable. It will not be possible
within that function to access the global variable of the same name.
182 Programming in ANSI C
It does not matter whether the return is via the function value or via a
parameter the function must not return a pointer to a local automatic variable.
The following function illustrates the problem:
char *getname(void) {
char namestring[80]; /* A local buffer */
printf("\nEnter a name:"); /* Prompt for a name */
gets(namestring); /* Read in a name */
return namestring;
}
This function appears to return a pointer to an array containing the string read
from the keyboard. In fact it will not work as the array namestring only
exists while the function is executed. On exit from the function the memory
space for namestring is likely to be reused for other purposes, overwriting
the characters in namestring.
Pointers cause more headaches than any other part of the C language. If a
program behaves in a really obscure manner that defies normal debugging
techniques the chances are that it is a pointer error at fault. C relies on the use
of pointers far more than most other procedural languages such a Pascal,
Modula 2, Fortran or Cobol. Indeed some languages do not have pointer types
at all. Most C programs of any size make extensive use of pointers because of
their power and elegance despite their being prone to error.
The problems with pointers are many, here are some examples:
Fortunately ANSI C does have sufficient type checking to catch most of this
type of error but there can be problems particularly when passed to a function
without a full prototype or with a prototype containing the ... ellipses:
eg. int i,*iptr = &i;
scanf("%d", &iptr);
This will not read a value into i but into the pointer which will then point to
somewhere else in memory. Any subsequent reference to *iptr will give
quite unpredictable and possibly disastrous results.
Part D : C Program Accuracy and Style 183
Has a structure pointer been confused with a pointer to the structure contents?
Consider:
This code has confused the pointer to the structure with a character pointer to
the contents of the structure. Incrementing stptr does not move the pointer to
the next character in the structure buffer but to the start of an assumed next
structure in memory. The above code will have unpredictable results!
Has a pointer to a pointer been confused with a two dimensional array pointer?
Pointers to pointers are confusing at the best of times, but particular care must
be taken to use these pointers correctly with two dimensional arrays:
Unlike the single dimensional array's simple relation to pointers, the two
dimensional array's relationship with pointers to pointers is not so straight
forward. In the loop assignment it will be assumed that iptrptr points at an
array of pointers of length rows each of which points at an array of integers of
length cols. As no such array of pointers has been set up C will take whatever
values it finds and will use these values as address of arrays for the eventual
assignment. This will produce quite unpredictable damage to the program's
memory!
In the above example to use iptrptr as a two dimensional array pointer with
row length 5 it should be declared: int (*iptrptr)[5];
184 Programming in ANSI C
Consider:
while (ch != 'Y' && ch != 'N');
{
printf("\nPlease answer 'Y' or 'N':");
ch = getchar();
}
This error can be difficult to spot. The statements enclosed in the {} compound
statement brackets are in fact not part of the while loop. The ; at the end of
the while means the loop contains a single blank statement. If the condition is
true the program will keep executing this blank statement forever and the
program will "hang". There should be no ; before any such compound
statement.
Consider:
if (x>y) y=x; x=0;
while (x<y)
arr[x] = y;
x++;
printf("\nAll done!");
The layout of the if statement implies that both y=x; and x=0; are
executed only if x>y. In fact the statement x=0; is not connected with the
if and will be executed regardless of the if condition. The indenting
following the while statement implies both the following two statements are
part of the while loop. In fact the statement x++; is not part of the loop so
the loop will keep going forever as x is unchanged in the loop. The layout of a
program can never affect the program in this way. If more than one statement
needs to be associated with an if, while, for or do-while then the
statements must be enclosed in {}.
Consider:
#define LOOP_FOREVER for(;;) ;
LOOP_FOREVER {
ch = getchar();
if (ch=='Y' || ch=='N') break;
printf("Please answer 'Y' or 'N':");
}
This loop will indeed go on forever as the compound statement is not part of
the loop, the loop contains only a single blank statement as described in 1.11
above. Pre-processor statements should not be terminated by a ; !
Consider:
#define square(x) (x)*(x)
joe = 1/square(fred);
Have brackets been omitted round the macro parameters in the macro expansion?
Consider:
#define square(x) (x*x)
joe = square(fred + 1);
Consider:
#define square(x) ((x)*(x))
joe = square(fred++);
joe = ((fred++)*(fred++));
which will increment fred twice! - Not what was intended! Macro
parameters with side effects should be avoided. Care must be taken when using
this type of embedded assignment on a function parameter just in case the
function is really a macro - in which case errors are possible.
Consider:
while (num[i]!= 0) {
if (num[i] <0 ) /* Convert all numbers to
num[i] = -num[i]; their absolute value */
i++;
}
Consider:
x = a[i++] + b[i];
Has it been assumed the left hand side of an assignment is handled before the right
hand side?
Consider:
a[i++] = b[i];
Consider:
myfunc(i++, arr[i]);
Which value of i is used when accessing the array arr? It cannot be assumed
that the parameters are evaluated left to right. Indeed it is very common for
compilers to work the other way round. How the above statement is evaluated
will vary from one compiler to another.
188 Programming in ANSI C
Does the system buffer input from the keyboard until a newline is given?
Many systems will buffer input from the keyboard presenting problems for
single character input though getchar or scanf with a %c format. The
system may insist that all input is terminated with the return key before the first
character is passed to the program for processing. The remaining characters up
to and including the newline character are kept in a buffer. When a subsequent
character read is executed instead of getting the character from the keyboard
the next character is taken from the buffer without any user interaction. This
can make some interactive programs unworkable. Solutions are to try other
input functions to see if a facility exists for "raw" input, or to write the program
so it expects a return after each input from the user.
If the input is line oriented it is often better to read a line at a time into an array
using the function gets and then process the line with sscanf. This brings all
buffering under the programmer's own control.
2.1 Introduction
The following is a check list to be used with any 'C' program to give a guide as to
whether the program is of a sufficient standard. These points are a guide only, even if
a program passes all the given tests it does not imply that the code is of a high quality.
However, if it fails to satisfy any of these tests it is a very strong hint that there is
something wrong!
In some cases there may be very good reasons for not satisfying these tests. This is
perfectly acceptable providing it is properly justified in the supporting
documentation.
2.2 Specification
Is there a well defined (the more formal the better) specification for the program?
It is just as much an error for the code to do too much as too little. If the extra
code is really necessary the specification should be changed. The specification
and code must match. Watch out for unspecified error handling etc.
These are blocks of comment at the start of the program and each routine. They
are often put into 'boxes' of * or other characters to make them stand out.
These are the most important comments in the program and can often be as
long or longer than the code itself. It does not matter if the comment header is
much greater than the code for any function, it is far more important that the
header gives full information in a consistent way.
The idea of a comment header block is that it should allow the routine to be
treated as a black box. As long as the reader can see what the overall effect of
the routine is, (ie. what comes in and what comes out) there should be no need
to delve into the inner workings of the routine.
Does each comment header have a short statement of the function's purpose?
This statement should give a simple statement as to what the function does as a
whole. It should not have any reference as to how it does it.
Does each comment header explain any restrictions or assumptions about the input
parameters?
Does each comment header give details of any output changes made to parameters?
Changes should include indirect changes made via pointers as well as any
direct changes.
Does each comment header list any restrictions or assumptions about global variable
values?
Does each comment header give any direct or indirect changes made to global
variables?
Does each comment header list any files accessed for reading?
Does each comment header give any restrictions or assumptions about input file
data?
Does each comment header give details of any data written to file?
Does each comment header give details of the function return value?
Does each comment header give details of any abnormal exits from the function?
Does each comment header give a full list of all other functions called?
Part D : C Program Accuracy and Style 191
Does each comment header give a full list of all other functions which call the
function?
This is not essential and is often omitted because of the difficulty in keeping it
up to date. If it is given it is essential the list includes all calling functions.
This may not be necessary if the code is fairly readable itself. If it is included it
should be kept quite separate to the statement of purpose.
If there is more than one possible author, does the comment header give the author of
each routine?
If the program is developed from an earlier program, does each comment header give
the date that the routine was created?
Does each comment header give the date that the routine was last modified?
If any modifications to a routine are made by someone other than the original author
does the comment header include the name of this person?
Is there a comment explanation of all data variables including local variables used in
each routine?
This could be either in the comment header or alongside the data declarations.
Does each comment give extra information that can't easily be deduced from the code
itself?
This is particularly important for comments at the end of each line. A comment
such as /* set x to zero */ is obvious from the code itself.
Does each unusual or devious piece of code have a comment to explain its action?
Be sure about this one - make sure you are not just making excuses. Most
unusual or devious, often called 'clever', code can be rewritten to render any
comment explanation unnecessary!
192 Programming in ANSI C
Are all comments laid out neatly so they do not make the code hard to follow?
Don't put in so many comments that it is sometimes difficult to find the code
between the comments. It is usually neater and easier to follow if comments are
brought together into little comment blocks whether they appear in the header
or part way through the code.
Are all comments complete where the include any lists in whatever form?
2.5 Layout
Have all nested if, while, switch blocks etc. been indented?
Have the {} brackets been positioned in one of the accepted layout conventions?
Examples are:
(1) (2) (3)
if (a==b) { if (a==b) if (a==b)
x = 0; { {
y = 1; x = 0; x = 0;
} y = 1; y = 1;
else { } }
x = 1; else else
y = 0; { {
} x = 1; x = 1;
y = 0; y = 0;
} }
Stand back and look at the program as a whole - is it easy to see where one function
ends and the next starts?
Have spaces, newlines and () brackets been used to help clarify long and
complicated expressions?
Have spaces, newlines and () brackets been used to help clarify ambiguous
expressions?
Adjacent operators can appear ambiguous even if the compiler always takes a
consistent action.
eg. Is x+++y equivalent to x++ + y or x + ++y ?
A space between operators will prevent either the compiler or a reader from
misinterpreting the intended meaning of a statement.
Has a space between a function or macro name and the associated () been
avoided?
A space between the name of a macro and the associated () will confuse some
compilers. Although this should not happen with a function, not all functions
are what they seem - many are macros in disguise (eg. getchar). A space in
this position should be avoided in all circumstances to avoid confusion and
possible error in the future maintenance of the program.
It can make a surprising difference as to how well the code can be followed if
the code looks neat and tidy.
2.6 Naming
Have all variables, functions and files been given meaningful names?
Single letter names are rarely acceptable, except possibly for loop counters in
'for' loops.
194 Programming in ANSI C
Have all variables, functions and files been given self explanatory names?
Has the use of upper and lower case in names been consistent?
This is important not only to make the program more readable, but also more
adaptable.
Every extra variable is something else to keep track of, a possible source of
misunderstanding and error.
Have variables that are only used locally been declared locally?
Have global variables that are only used within one source file been declared as
static?
This is a common source of error, especially when the first use of a variable is
in a loop. If in doubt the variable should be initialised when it is declared, or in
the first statements of a routine. It does no harm to initialise a variable even
though the first use of a variable is to assign a value to it.
Part D : C Program Accuracy and Style 195
It may be acceptable to reuse 'for' loop counters. All other reuse of variables
are a source of misunderstanding and error.
If too many variables are declared as type register then the compiler will
make an arbitrary selection for register use. This reduces the point of using
register variables in the first place.
Have all variables that are to remain as constant been declared as constant?
Has each variable been declared with the most suitable type for the purpose for
which it is to be used?
Most type changes are the direct result of a "fudge" in the program. A good
choice of the type and use of a variable should make any type changes
unnecessary.
Have any assumptions been made about the memory size of a variable?
These assumptions are often made within unions or when using memory
allocation functions, eg. one long integer takes the same space as two ordinary
integers. Assumptions of this type are unreliable and should be avoided.
Has sizeof been used for variable size when allocating memory using a memory
allocation function?
The statement might do what is intended, but the slightest change may effect
the way the statement is evaluated. This type of ambiguity should be avoided.
Has a for loop been used for each loop with a known number of iterations?
Is each while loop used where the loop could in some circumstances have zero
iterations?
Is each do while loop used where the loop body must be executed at least once?
This is a common source of error. Beware of any break statement that does
not exit from an unconditional, "forever" loop.
Part D : C Program Accuracy and Style 197
Has the convention of using capital letters for macro constants been used?
Are there any frequently used expressions that could be clarified by the use of a
macro?
Would any of the macros defined to represent constants be better defined as constant
variables?
Does each macro expansion put ( ) round the whole expression if necessary?
If it is a large program, has the source been split into separate files?
Do the functions grouped together in a source file have any common factor grouping
them together?
Is there a comment in each source file to say to which program(s) the routines belong
and to give any common factors between the routines in the file?
Are those global variables and functions referenced solely within a single source file
declared as static ?
198 Programming in ANSI C
Does the grouping of functions within each source file enable as many global
variables and functions as possible to be declared as static ?
Have all the macro definitions, function prototypes, external variable declarations,
typedef definitions and structure, union and enumerated type definitions that are
common to more than one source file been grouped together in a header file?
Header files provide consistency in these declarations across all source files.
Is the header file free from any function code and any global variable declarations
without extern specifiers?
Header files should only contain statements that do not cause the compiler to
use or reserve memory in the program. Source files containing code and global
variable definitions should not be combined with #include in the style of
header files - they should be linked using the system "project" or "make" files
or whatever equivalent exists for the system being used.
2.11 Functions
No cheating by using the word "and"! A good function should do one thing,
and one thing well. Functions that do a bit of this and a bit of that do not "hang
together" are an indication of poor design.
Control functions have a few if and while type statements with all the
real work done by the functions called. Processing functions are the low level
functions that do the work. Well designed functions tend to be of one type or
the other. Any function with a mixture may be a result of poor design (though
not necessarily so).
Part D : C Program Accuracy and Style 199
Are there too many parameters and/or global variables accessed by any function?
Although there is no absolute maximum size for a function, any function above
50 statements must be suspect. A large function is an indication that the
function is trying to do too much and should be split into smaller units.
The complexity is some measure of the number of different paths through the
code. If the number of conditional expressions in if, while and
do while statements plus the number of implied conditions in for and
switch statements is greater than 10, then the function is too complex and
should be split into simpler units.
A level of nesting greater than five is an indication that the function should be
split into simpler units.
It is possible to give a function prototype that does not give the parameter
types. This is a facility left over from pre-ANSI implementations of C that has
been incorporated into the standard for compatibility. Code which is converted
to the ANSI standard may still have this type of prototype declaration. Full
prototypes should be used wherever possible.
Has each function return value been declared with the most suitable type for the
purpose for which it is to be used?
Most type changes, whether for function return values or for individual
variables, are the direct result of a "fudge" in the program. A good choice of
the type and use of a function return value should make any type changes
unnecessary.
If it is really necessary, is there a comment to explain the reason for each instance of
type change of a function return value?
Does the program check for every error of input data type?
Does the program check for all out of range input data values?
Does the program give the correct response for every error input value?
Does each routine check for invalid parameter values, non local variables and file
data?
These checks should be built into the code even where such invalid parameters,
etc. are "impossible". This will help identify the source of an error as well as
preventing some types of error from remaining undetected.
Has self testing code been used wherever possible and practical?
2.13 Testing
Has the program been structured so that sub-systems and functions can be tested
individually?
Has each routine been checked for its response to invalid parameters, non local
variables and file data?
Has every path through the code been included in at least one test?
Has every branch been tested by trying boundary values on either side of the branch
test?
Part D : C Program Accuracy and Style 201
Where a test is for a range of values, have mid-range values been tested as well as
boundary values?
Does the program check for every error of input data type?
Does the program check for all out of range input data values?
Does the test plan include formal validation and verification techniques?
Does the test plan include inspections, walk-throughs or other types of review?
Has an integration plan been devised to allow the program to be built in well tested
stages?
The split between test plan and integration plan is not necessarily well defined.
The integration plan must obviously include the testing of the partly built
systems at each stage. The final test of the whole system could be considered to
be part of either or both the test plan and integration plan.
Have the tests undertaken and the results obtained been documented for each of the
following:
All formal validation and verification?
All inspections/walk-throughs/reviews?
All tests involving execution of parts or all of the code?
All user trials?
Has the use of any temporary test programs also been documented?
Has self testing code been used wherever possible and practical?
Have all other references to the limits been made using the mnemonics?
ie. The code is designed for new facilities to be added. It avoids building in
limitations within the code. Advantage is not taken of any coincidental
similarities or differences when storing data or making tests within program
control statements.
2.15 Portability
All programs should be written to be portable unless there are good reasons for
not doing so and these reasons are given in the documentation.
Has every deviation from the standard been listed in the documentation?
Has every deviation from the standard been justified in the documentation?
Have all non standard parts of the program been brought together in a restricted set
of functions?
Part D : C Program Accuracy and Style 203
Has the __STDC__ standard predefined macro been used to check the compiler
conforms to the ANSI C standard?
204 Programming in ANSI C
Programming in
ANSI
Third Edition
PART E
Sample Solutions to the Exercises
Part E : Sample Solutions to the Exercises 205
Part E : Contents
Note on Software
The sample programs in this section have, with only a couple of exceptions, been
tested using an Apple Macintosh computer with the Think C compiler version
5.0. Think C is a product of the Semantec Corporation. The exceptions were
exercise 12, question 2 and exercise 13, question 4 relating to a command line call of
the program. Every effort has been made, however, to use code that will run on all
compilers that claim to conform to the ANSI C standard.
Page
Comments may be found anywhere in the program between /* and */ and may be
on part of a line, a whole line or spread across several lines.
You may find a comment starting with // and continuing to the end of the line.
This form of comment is borrowed from C++. It is not ANSI C and not every C
compiler will support it.
If the comments are removed the program will still compile and run in exactly the
same way as before. However, the program will become much harder to understand
and, consequently, any modifications will become much harder to make.
The program will start immediately following the main function header. This may
not be identical to that given in section 1.2 in Part A as there may be a word such as
int before the word main and there may be something between the ().
The program will usually terminate either at the return statement within main
or when program execution reaches the final } within the main function.
Any line that starts with a # is a pre-processor statement. There is often a pre-
processor statement at the very top of the program with #include <stdio.h>
Any data definition that occurs between a { and its matching } is a local data
definition statement. If it is not between { and } it is a global data definition
statement.
Well laid out programs start each line between { and its matching } with an indent of
3 or 4 spaces. If there is a further {} pair nested inside the outer {} then the lines
within the innermost {} have double the indent, and so on for each further nested {}
pair. This convention helps to identify which } belongs to which {. This makes the
program easier to understand and maintain.
208 Programming in ANSI C
All the above are legal, though the fraction part of 4.6 cannot be stored in an integer
so Cleo will have the initial value 4. Doris has the ASCII value of the character 'D'
which is 69.
The definition of Eric is legal though the number 257 is too big to store in the usual
eight bits allocated for a character. Only the right most 8 bits will be stored in Eric, so
as 257 is 100000001 in binary the bits 00000001 will be stored, which has a value of
1. Eric_Again is a legal definition, but 3rd_Eric is illegal as variable names must
start with a letter, not a digit.
These are legal apart from default as this is a reserved word. Default with a
capital "D" is OK as C is case dependent.
This is illegal as the "," should not be there. There should be no comma before the
first variable name.
Float x=123.456, Y=100 e-6, z=1234;
This is illegal as float should not have a capital "F". With a lower case "f" the
statement would be legal.
This is perfectly legal though it appears to be an error. If the system allocates 16 bits
for an unsigned integer then all 16 would be set to ones giving a value of 65535.
The const int three = 4 is legal but bad coding practice as the name of the
variable is so confusing. The constant Max is legal but it is useless as there is no
initial value assigned to it and constants cannot be assigned values except when
initialised. If this is a local data definition Max will have an unknown initial value
that cannot be changed throughout the program. Some compilers may give an error
Part E : Sample Solutions to the Exercises 209
for a non initialised constant data definition. This declaration of Eric is illegal as it
is previously declared as a character variable.
3.1 C Exercise 3
#include <stdio.h>
main() {
printf("Hello there!\n");
return 0;
}
#include <stdio.h>
main() {
int number=87;
printf("Number 87 as a character is %c\n",number);
printf("Number %d in decimal is %o in octal and %x in hex\n",
number,number,number);
printf("Number 87 in hex shifted left 4 places is %x\n",
number<<4);
printf("Number 87 in hex with bits 0 & 5 cleared is %x\n",
number & ~0x21);
return 0;
}
#include <stdio.h>
main() {
char ch;
printf("Please input a character: ");
ch = getchar();
printf("\nThe binary equivalent of this character is: ");
printf("%d",(ch>>7)&1);
printf("%d",(ch>>6)&1);
printf("%d",(ch>>5)&1);
printf("%d",(ch>>4)&1);
printf("%d",(ch>>3)&1);
printf("%d",(ch>>2)&1);
printf("%d",(ch>>1)&1);
printf("%d\n",ch&1);
}
214 Programming in ANSI C
#include <stdio.h>
main() {
char ch;
printf("Please input a character: ");
ch = getchar();
printf("\nThe binary equivalent of this character is: ");
putchar(((ch>>7)&1)+'0');
putchar(((ch>>6)&1)+'0');
putchar(((ch>>5)&1)+'0');
putchar(((ch>>4)&1)+'0');
putchar(((ch>>3)&1)+'0');
putchar(((ch>>2)&1)+'0');
putchar(((ch>>1)&1)+'0');
putchar((ch&1)+'0');
putchar('\n');
}
Part E : Sample Solutions to the Exercises 215
#include <stdio.h>
main(){
int Diag[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
printf("Diagonal values are: %d,%d,%d\n",
Diag[2][0],Diag[1][1],Diag[0][2]);
return 0;
}
#include <stdio.h>
main() {
char Names[5][4] = {"Al","Bob",{'C','l','e','o'},"Des","Eve"};
printf("Names are:\n%s\n%s\n%s\n%s\n%s\n",
Names[0],Names[1],Names[2],Names[3],Names[4]);
return 0;
}
/* The problem with the name "Cleo" is that there is no room for the
null byte terminator in a row of the array. This means (1) the
string form of initialiser won't work and (2) output using printf
and %s continues until a null byte is found, so the name Cleo has
Des appended to it as there is no null byte until the null at the
end of Des. */
216 Programming in ANSI C
#include <stdio.h>
main() {
char Names[5][4] = {"Al","Bob",{'C','l','e','o'},"Des","Eve"};
Names[3][0] = 0;
printf("Names are:\n%s\n%s\n%s\n%s\n%s\n",
Names[0],Names[1],Names[2],Names[3],Names[4]);
return 0;
}
#include <stdio.h>
main() {
char cName[5] = "Fred";
int iName[5];
iName[0] = cName[0];
iName[1] = cName[1];
iName[2] = cName[2];
iName[3] = cName[3];
iName[4] = cName[4];
printf("Character name is: %s\nInteger name is: %s\n",
cName,iName);
return 0;
}
/* The output of the integer array will depend on the computer. Each
integer will consist of two or more bytes so every character
stored in an integer will have at least one zero byte stored with
it. Some computers store the leftmost byte first which will be
zero, so the output of the integer name will immediately
terminate without printing any character. Other computers store
the rightmost byte first so the first character of the name will
be printed, however the zero byte belonging to the leftmost
byte(s) of the first integer will then terminate the output
before the second character in the name is output. ie. Depending
on the computer, no more than zero or one character of the
integer array will be output. */
Part E : Sample Solutions to the Exercises 217
#include <stdio.h>
main() {
char lines[3][100]; /* Allow for up to 99 characters a line */
printf("Enter 3 lines of text:\n");
gets(lines[0]);
gets(lines[1]);
gets(lines[2]);
printf("\nIn reverse order the lines are:\n%s\n%s\n%s\n",
lines[2],lines[1],lines[0]);
return 0;
}
218 Programming in ANSI C
#include <stdio.h>
main() {
char wrd[100]={0}; /* Make sure its big enough for any input */
int numch; /* Variable to hold number of characters */
if (wrd[1] == 0) numch = 1;
else if (wrd[2] == 0) numch = 2;
else if (wrd[3] == 0) numch = 3;
else {
printf("\nError: A maximum of 3 characters permitted\n");
return 0;
}
#include <stdio.h>
main() {
char wrd[100]={0}; /* Make sure its big enough for any input */
int numch; /* Variable to hold number of characters. */
if (wrd[1] == 0) numch = 1;
else if (wrd[2] == 0) numch = 2;
else if (wrd[3] == 0) numch = 3;
else {
printf("\nError: A maximum of 3 letters permitted\n");
return 0;
}
#include <stdio.h>
main() {
char text[10][100]; /* To hold the input */
int linecount=0; /* count of lines */
int i; /* loop counter */
char finished; /* boolean variable */
/**/ int alphacount=0; /* count of letters */
/**/ int totalcount=0; /* character count */
/**/ char ch; /* temporary variable */
#include <stdio.h>
main() {
char text[10][100]; /* To hold the input */
int linecount=0; /* count of lines */
int i; /* loop counter */
char finished; /* boolean variable */
int alphacount=0; /* count of letters */
int totalcount=0; /* character count */
char ch; /* temporary variable */
/* Program to put a line of text and the count of the characers and
letters into a structure, and then to output this information */
#include <stdio.h>
main() {
struct linetype {
int total,letters; /* counters */
char text[100]; /* input line */
} line = {{0,0}};
int i; /* loop counter */
char ch; /* temporary character */
#include <stdio.h>
main() {
struct linetype {
int total,letters; /* counters */
char text[100]; /* input line */
} line[5] = {{0,0}};
int i,j; /* loop counters */
char ch; /* temporary variable */
/**/ union {
/**/ char byte;
/**/ struct {
/**/ unsigned other: 6; /* N.B. These bit fields */
/**/ unsigned next: 1; /* may need to be declared */
/**/ unsigned right: 1; /* in reverse order on a */
/**/ } bits; /* different computer */
/**/ } overlap;
/**/ int temp; /* used for swapping */
printf("Please enter 5 lines of text:\n");
for (j=0; j<5; j++) {
gets(line[j].text);
if (line[j].text[0]==0) {
if (j==0) {
printf("Error: First line cannot be blank\n");
return 0; /* Terminate the program! */
}
line[j]=line[j-1];
}
else {
for (i=0; line[j].text[i]!=0; i++) {
line[j].total++;
ch = line[j].text[i];
if((ch>='a' && ch<='z')||(ch>='A' && ch<='Z')) {
line[j].letters++;
/**/ overlap.byte = ch; /* Now swap the bits */
/**/ temp = overlap.bits.right;
/**/ overlap.bits.right = overlap.bits.next;
/**/ overlap.bits.next = temp;
/**/ line[j].text[i] = overlap.byte;
}
}
}
}
for (j=0; j<5; j++)
printf("Line %d has %d letters out of %d characters\n",
j+1,line[j].letters,line[j].total);
printf("\nThe lines of text are:\n");
for (j=0; j<5; j++) printf("%s\n",line[j].text);
return 0;
}
224 Programming in ANSI C
At the end of the program insert the encrypt function as given in section 9.4.
Alter the size of the text array in the structure definition to 31.
At the end of the program insert the getline function as given in section 9.4.
Move the structure declaration of the linetype structure to just before the prototype
declarations and then change the structure definition in main accordingly as in
section 9.4
At the end of the program insert the output function as given in section 9.4.
Part E : Sample Solutions to the Exercises 225
#include <stdio.h>
/* The structure type (but not the definition of the actual instance
of the structure) is placed here as it is used in more than one
function and function prototype. */
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
};
/*------------------------------------------------------------------
The main function now has much less to do, the real work taking
place in the functions it calls. getline is called to read each line
and in the same loop encryptline is called to encrypt the line.
The output function is then called to output all lines together.
The return value that main passes back to the operating system or
whatever has called it is zero for successful completion or minus
one for the error termination if the first line is blank.
------------------------------------------------------------------*/
int main() {
struct linetype line[5] = {{0,0}};
int j; /* Loop counter */
/*------------------------------------------------------------------
Function encrypt takes a single character and encrypts it by
swapping (ie. interchanging) the rightmost two bits.
The return value is the encrypted character
------------------------------------------------------------------*/
char encrypt(char c) {
return (c & ~3) + ((c&1)<<1) + ((c&2)>>1);
}
/*------------------------------------------------------------------
Function getline reads a line of text from the keyboard and puts it
into the character array given as the first parameter. The second
parameter gives a limit on the number of characters that can be put
into the array - characters on the line after this are ignored.
A null string terminator is put on the end of the string.
There is no return value to this function.
------------------------------------------------------------------*/
void getline(char txt[], const int limit) {
int i=0; /* keeps position of character on the line */
char ch;
while ((ch=getchar()) != '\n') {
if (i<limit) txt[i++]=ch;
}
txt[i] = 0; /* Add a null terminator */
}
/*------------------------------------------------------------------
Function output prints to the screen the content of the array of
line structures given as the first parameter. First the number of
letters and characters in each line structure is printed and then
the text held in the structure. The second parameter gives the
number of structures in the array.
There is no return value to this function.
------------------------------------------------------------------*/
void output(const struct linetype outputlines[], const int num) {
int j; /* loop counter */
for (j=0; j<num; j++)
printf("Line %d has %d letters out of %d characters\n",
j+1,outputlines[j].letters,outputlines[j].total);
printf("\nThe lines of text are:\n");
for (j=0; j<num; j++) printf("%s\n",outputlines[j].text);
}
/*------------------------------------------------------------------
Function encryptline encrypts the line of text in the single
structure given as the only parameter. It does so by calling the
encrypt function to encrypt each letter in the text. The non letter
characters are not encrypted. At the same time counts are made of
the number of characters and letters and these are put in the in the
appropriate integer parts of the structure.
The return value to this function is the encrypted structure.
------------------------------------------------------------------*/
struct linetype encryptline(struct linetype oneline) {
int i; /* loop counter */
char ch; /* temporary variable */
oneline.total = oneline.letters = 0; /* zero counters */
for (i=0; oneline.text[i]!=0; i++) {
oneline.total++;
ch = oneline.text[i];
if ((ch>='a' && ch<='z')||(ch>='A' && ch<='Z')) {
oneline.letters++;
oneline.text[i] = encrypt(ch);
}
}
return oneline;
}
228 Programming in ANSI C
#include <stdio.h>
#include <stdlib.h>
/* The structure type (but not the definition of the actual instance
of the structure) is placed here as it is used in more than one
function and function prototype. */
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
The main function now has much less to do, the real work taking
place in the functions it calls. malloc is called to allocate space
for a new structure and in the same loop getline is called to read
each line and encryptline is called to encrypt the line.
The output function is then called to output all lines together.
------------------------------------------------------------------*/
int main() {
struct linetype *startptr,*newptr;
printf("Please type lines of text ending with a blank line\n");
startptr = newptr = malloc(sizeof(struct linetype));
for(;;) {
getline(newptr->text,30);
if (newptr->text[0]==0) { /* Check for a blank line */
newptr->total = 0;
break; /* Exit the loop if blank */
}
encryptline(newptr);
newptr->linkptr = malloc(sizeof(struct linetype));
newptr = newptr->linkptr;
}
output(startptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
/*------------------------------------------------------------------
Function encrypt takes a pointer to a single character and encrypts
the character by swapping (ie. interchanging) the rightmost two
bits. There is no return value for this function.
------------------------------------------------------------------*/
void encrypt(char *cptr) {
*cptr= (*cptr & ~3) + ((*cptr & 1)<<1) + ((*cptr & 2)>>1);
}
/*------------------------------------------------------------------
Function getline reads a line of text from the keyboard and puts it
into the character array pointed at by the first parameter. The
second parameter gives a limit on the number of characters that can
be put into the array - characters on the line after this are
discarded by writing them one on top of the other at the end of the
string and finally overwriting them with the null string terminator.
There is no return value to this function.
------------------------------------------------------------------*/
void getline(char *txtptr, const int limit) {
char *const endptr = txtptr+limit;
while ((*txtptr = getchar()) != '\n') {
if (txtptr < endptr) txtptr++;
}
*txtptr = 0; /* Add a null terminator */
}
/*------------------------------------------------------------------
Function encryptline encrypts the line of text in the single
structure pointed at by the only parameter. It does so by calling
the encrypt function to encrypt each letter in the text. A temporary
character pointer is used to access each character in the structure
text. The non letter characters are not encrypted. At the same time
counts are made of the number of characters and letters and these
are put in the in the appropriate integer parts of the structure.
There is no return value to this function.
------------------------------------------------------------------*/
void encryptline(struct linetype *lineptr) {
char *cptr; /* temporary pointer to point at the text */
lineptr->total = lineptr->letters = 0; /* zero counters */
for (cptr=lineptr->text; *cptr != 0; cptr++) {
lineptr->total++;
if ((*cptr >= 'a' && *cptr <= 'z')
||(*cptr >= 'A' && *cptr <= 'Z')) {
lineptr->letters++;
encrypt(cptr);
}
}
}
/*------------------------------------------------------------------
Function output prints to the screen the content of the chain of
line structures with the start pointer given as the first parameter.
First the number of letters and characters in each line structure is
printed and then the text held in each structure. A message is given
if there are no lines of input.
There is no return value to this function.
------------------------------------------------------------------*/
void output(struct linetype *const startptr) {
int j=1; /* j is used only to output a line count */
struct linetype *lineptr = startptr;
if (startptr->total == 0) {
printf("Error: There are no lines of text to output.\n");
return;
}
while (lineptr->total != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
printf("\n\nThe lines of text are:\n");
while (lineptr->total != 0) {
printf("%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
232 Programming in ANSI C
#include <stdio.h>
#include <stdlib.h>
/* The structure type (but not the definition of the actual instance
of the structure) is placed here as it is used in more than one
function and function prototype. */
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
The main function now has much less to do, the real work taking
place in the functions it calls. malloc is called to allocate space
for a new structure and in the same loop getline is called to read
each line and encryptline is called to encrypt the line.
The output function is then called to output all lines together.
A pointer to the pointer that points at the structure currently in
use is employed so that it can set the link pointer of the last
structure in the chain to zero as required.
------------------------------------------------------------------*/
int main() {
struct linetype *startptr,**newptrptr = &startptr;
printf("Please type lines of text ending with a blank line\n");
for (;;) {
*newptrptr = malloc(sizeof(struct linetype));
if (*newptrptr != 0) {
getline((*newptrptr)->text,30);
if((*newptrptr)->text[0]!=0) encryptline(*newptrptr);
else { /* blank line found */
free(*newptrptr);
*newptrptr = 0; /* end of chain */
}
}
if (*newptrptr == 0) break; /* exit if end of chain */
newptrptr = &(*newptrptr)->linkptr;
}
output(startptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
Part E : Sample Solutions to the Exercises 233
/******************************************************************/
/* */
/* Functions encrypt, getline and encryptline are as for the */
/* last question and should be inserted here. */
/* */
/******************************************************************/
/*------------------------------------------------------------------
Function output prints to the screen the content of the chain of
line structures with the start pointer given as the first parameter.
First the number of letters and characters in each line structure is
printed and then the text held in each structure. A message is given
if there are no lines of input. The chain of structures terminates
with a zero link pointer.
There is no return value to this function.
------------------------------------------------------------------*/
void output(struct linetype *const startptr) {
int j=1; /* j is used only to output a line count */
struct linetype *lineptr = startptr;
if (startptr == 0) {
printf("Error: There are no lines of text to output.\n");
return;
}
while (lineptr != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
printf("\nThe lines of text are:\n");
while (lineptr != 0) {
printf("%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
234 Programming in ANSI C
#include <stdlib.h>
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
The real work of the program takes place in the functions called.
malloc allocates space for each structure and in the same loop
getline reads the line and encryptline does the encryption of the
line. The output function then outputs all the lines together.
------------------------------------------------------------------*/
int main() {
register struct linetype *startptr,*newptr;
printf("Please type lines of text ending with a blank line\n");
startptr = newptr = malloc(sizeof(struct linetype));
for(;;) {
getline(newptr->text,30);
if (newptr->text[0]==0) { /* Check for a blank line */
newptr->total = 0;
break; /* Exit the loop if blank */
}
encryptline(newptr);
newptr->linkptr = malloc(sizeof(struct linetype));
newptr = newptr->linkptr;
}
output(startptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
Part E : Sample Solutions to the Exercises 235
#include <stdio.h>
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
Function getline reads a line of text from the keyboard and puts it
into the character array pointed at by the first parameter. The
second parameter gives a limit on the number of characters that can
be put into the array - characters on the line after this are
discarded by writing them one on top of the other at the end of the
string and finally overwriting them with the null string terminator.
There is no return value to this function.
------------------------------------------------------------------*/
void getline(char *txtptr, const int limit) {
register char *const endptr = txtptr+limit;
while ((*txtptr = getchar()) != returnkey) {
if (txtptr < endptr) txtptr++;
}
*txtptr = 0; /* Add a null terminator */
}
/*------------------------------------------------------------------
Function output prints to the screen the content of the chain of
line structures with the start pointer given as the first parameter.
First the number of letters and characters in each line structure is
printed and then the text held in each structure. A message is given
if there are no lines of input.
There is no return value to this function.
------------------------------------------------------------------*/
void output(struct linetype *const startptr) {
register int j=1; /* j is used only to output a line count */
register struct linetype *lineptr = startptr;
if (startptr->total == 0) {
printf("Error: There are no lines of text to output.\n");
return;
}
while (lineptr->total != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
printf("\nThe lines of text are:\n");
while (lineptr->total != 0) {
printf("%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
236 Programming in ANSI C
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
Function encrypt takes a pointer to a single character and encrypts
the character by swapping (ie. interchanging) the rightmost two
bits. There is no return value for this function.
------------------------------------------------------------------*/
static void encrypt(char *cptr) {
*cptr= (*cptr & ~3) + ((*cptr & 1)<<1) + ((*cptr & 2)>>1);
}
/*------------------------------------------------------------------
Function encryptline encrypts the line of text in the single
structure pointed at by the only parameter. It does so by calling
the encrypt function to encrypt each letter in the text. A temporary
character pointer is used to access each character in the structure
text. The non letter characters are not encrypted. At the same time
counts are made of the number of characters and letters and these
are put in the in the appropriate integer parts of the structure.
There is no return value to this function.
------------------------------------------------------------------*/
void encryptline(struct linetype *lineptr) {
register char *cptr; /* temporary pointer */
lineptr->total = lineptr->letters = 0; /* zero the counters */
for (cptr=lineptr->text; *cptr != 0; cptr++) {
lineptr->total++;
if ((*cptr >= 'a' && *cptr <= 'z')
||(*cptr >= 'A' && *cptr <= 'Z')) {
lineptr->letters++;
encrypt(cptr);
}
}
}
Part E : Sample Solutions to the Exercises 237
Modify file 1 of your program to add the following at the end of the file:
/*------------------------------------------------------------------
Function malloc is a home grown version of the library function of
the same name. It is declared as static so that it is used by main
but not by any library function. It allocates memory from its own
internal array called "memory" with the pointer "memptr" pointing
at the next available free space in the memory array. if there is
not enough memory available an error message is given and the
function returns a zero pointer.
------------------------------------------------------------------*/
static void *malloc(size_t numbytes) {
static char memory[500], *memptr = memory;
if (memptr-memory + numbytes > 500) {
printf("\nError: Memory Unavailable");
return 0;
}
memptr += numbytes;
return memptr-numbytes;
}
/*------------------------------------------------------------------
Function free is a home grown version of the library function of
the same name. It is declared as static so that it is used by main
but not by any library function. It is a dummy function that does
nothing and is included simply to maintain compatibility with the
home grown version of malloc.
------------------------------------------------------------------*/
Files 2 and 3 and the remainder of file 1 should not be changed in any way.
238 Programming in ANSI C
#include <stdio.h>
#include <stdlib.h>
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
The real work of the program takes place in the functions called.
malloc allocates space for each structure and in the same loop
getline reads the line and encryptline does the encryption of the
line. The output function then outputs all the lines together.
This version of main gives a choice of input from a file or from
the keyboard if a blank filename is given or the specified file
cannot be read. Similarly on the output a choice is given of
sending the output to file or to the screen if a blank filename is
given or the specified file cannot be opened for writing.
------------------------------------------------------------------*/
int main() {
char filename[41];
FILE *fptr;
register struct linetype *startptr,*newptr;
/*----- First get the filename and open the input file ------*/
printf("Please enter the input text file name: ");
getline(filename,40,stdin);
if (filename[0] == 0) fptr = stdin;
else {
if ((fptr = fopen(filename,"r")) == 0) {
printf("\nError: Cannot read file %s\n",filename);
fptr = stdin;
}
}
If the advanced program for exercise 10, question 5 has been completed the changes
to the main function will be similar to the above.
File 2 will need to be changed as given on the next page, File 3 should not need to be
changed in any way.
240 Programming in ANSI C
#include <stdio.h>
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
struct linetype *linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
Function getline reads a line of text from the file identified by
the file pointer given by the third parameter and puts it
into the character array pointed at by the first parameter. The
second parameter gives a limit on the number of characters that can
be put into the array - characters on the line after this are
discarded by writing them one on top of the other at the end of the
string and finally overwriting them with the null string terminator.
The input line can be terminated by either a newline character or
the end of the file. There is no return value to this function.
------------------------------------------------------------------*/
void getline(char *txtptr, const int limit, FILE *fptr) {
register char *const endptr = txtptr+limit;
while ((*txtptr = getc(fptr)) != returnkey && *txtptr != EOF) {
if (txtptr < endptr) txtptr++;
}
*txtptr = 0; /* Add a null terminator */
}
/*------------------------------------------------------------------
Function output prints to the file identified by the file pointer
given by the second parameter the content of the chain of line
structures with the start of chain pointer given as the first
parameter.
First the number of letters and characters in each line structure is
printed to the screen and then the text held in each structure is
sent to the identified file which could also be the screen.
A message is given if there are no lines of input.
There is no return value to this function.
------------------------------------------------------------------*/
void output(struct linetype *const startptr, FILE *fptr) {
register int j=1; /* j is used only to output a line count */
register struct linetype *lineptr = startptr;
if (startptr->total == 0) {
printf("Error: There are no lines of text to output.\n");
return;
}
while (lineptr->total != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
/*----- Now send the output to file -----------------------*/
if (fptr == stdout) printf("\nThe encrypted lines are:\n");
else printf("\nNow sending the lines of text to file.\n");
while (lineptr->total != 0) {
fprintf(fptr,"%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
If the advanced program for exercise 10, question 5 has been completed the changes
to the output function will be similar to the above.
Modify your previous program to change each error message printf function call to
the corresponding fprintf function call with the file pointer stderr as the first
parameter.
Eg. In the main function in your last program replace both instances of the line:
printf("\nError: Cannot open file %s\n",filename);
with: fprintf(stderr,"\nError: Cannot open file %s\n",filename);
Similarly, change the "no lines of text to output" error message in the output
function and, if you have completed exercise 11, question 3, change to the "memory
unavailable" message in the malloc function.
242 Programming in ANSI C
and should be inserted before the linetype structure definition in each file. The
variable declarations, including the component in the structure itself, and the
prototype and function headers can then be changed as shown in 13.3.
and in the main function the call to encryptline should be changed to:
encryptline(newptr,encrypt);
In file 2 the encrypt function definition should be modified to remove the static
specifier, and the encryptline function should be modified as given in 13.3.
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
linkpointer linkptr; /* pointer to next in chain */
};
/*------------------------------------------------------------------
Note the latest changes are the use of the linkpointer type, the
enumerated variable, the switch statement and the extra function
pointer parameter to encryptline.
------------------------------------------------------------------*/
int main() {
char filename[41];
FILE *fptr;
register linkpointer startptr,newptr;
enum {odd,even} linenum = odd;
/*----- First get the filename and open the input file ------*/
printf("Please enter the input text file name: ");
getline(filename,40,stdin);
if (filename[0] == 0) fptr = stdin;
else {
if ((fptr = fopen(filename,"r")) == 0) {
fprintf(stderr,"\nError: Cannot read file %s\n",
filename);
fptr = stdin;
}
}
if (fptr==stdin) printf("Please type in the lines of text\n");
/*----- Now get and encrypt the input text ------------------*/
startptr = newptr = malloc(sizeof(struct linetype));
for(;;) {
getline(newptr->text,30,fptr);
if (newptr->text[0]==0) { /* Check for a blank line */
newptr->total = 0;
break; /* Exit the loop if blank */
}
switch (linenum) {
case odd: encryptline(newptr,encrypt);
linenum = even;
break;
case even: encryptline(newptr,change);
linenum = odd;
break;
}
newptr->linkptr = malloc(sizeof(struct linetype));
newptr = newptr->linkptr;
}
/*----- Now get the filename for output and send the text ---*/
if (fptr != stdin) fclose(fptr);
printf("Please enter the output text file name: ");
getline(filename,40,stdin);
if (filename[0] == 0) fptr = stdout;
else {
if ((fptr = fopen(filename,"w")) == 0) {
fprintf(stderr,"\nError: Cannot write to file %s\n",
filename);
fptr = stdout;
}
}
output(startptr,fptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
244 Programming in ANSI C
#include <stdio.h>
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
linkpointer linkptr; /* pointer to next in chain */
};
Function getline should remain unchanged from exercise 12. Function output
should be modified to the following:
/*------------------------------------------------------------------
Function output prints to the file identified by the file pointer
given by the second parameter the content of the chain of line
structures with the start of chain pointer given as the first
parameter.
First the number of letters and characters in each line structure is
printed to the screen and then the text held in each structure is
sent to the identified file which could also be the screen.
A message is given if there are no lines of input.
There is no return value to this function.
------------------------------------------------------------------*/
void output(const linkpointer startptr, FILE *fptr) {
register int j=1; /* j is used only to output a line count */
register linkpointer lineptr = startptr;
if (startptr->total == 0) {
fprintf(stderr,
"Error: There are no lines of text to output.\n");
return;
}
while (lineptr->total != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
/*----- Now send the output to file -----------------------*/
if (fptr == stdout) printf("\nThe encrypted lines are:\n");
else printf("\nNow sending the lines of text to file.\n");
while (lineptr->total != 0) {
fprintf(fptr,"%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
Part E : Sample Solutions to the Exercises 245
struct linetype {
int total,letters; /* counters */
char text[31]; /* input line */
linkpointer linkptr; /* pointer to next in chain */
};
Function encrypt should remain unchanged from exercise 12. Function change
should be introduced and function encryptline should be modified to the
following:
/*------------------------------------------------------------------
Function change takes a pointer to a single character and changes
the character by complementing the bit third from the left.
There is no return value for this function.
------------------------------------------------------------------*/
void change(char *cptr) {
*cptr= *cptr ^ 0x20;
}
/*------------------------------------------------------------------
Function encryptline encrypts the line of text in the single
structure pointed at by the only parameter. It does so by calling
the encrypting function identified by the function pointer given as
the second parameter to encrypt each letter in the text. A temporary
character pointer is used to access each character in the structure
text. The non letter characters are not encrypted. At the same time
counts are made of the number of characters and letters and these
are put in the in the appropriate integer parts of the structure.
There is no return value to this function.
------------------------------------------------------------------*/
void encryptline(linkpointer lineptr,void (*fn)(char *cptr)) {
register char *cptr; /* temporary pointer */
lineptr->total = lineptr->letters = 0; /* zero the counters */
for (cptr=lineptr->text; *cptr != 0; cptr++) {
lineptr->total++;
if ((*cptr >= 'a' && *cptr <= 'z')
||(*cptr >= 'A' && *cptr <= 'Z')) {
lineptr->letters++;
(*fn)(cptr);
}
}
}
246 Programming in ANSI C
The main function should be modified to the following. No other part of the program
needs to be changed.
/*------------------------------------------------------------------
The program has now been changed so that the names of the input and
output files are passed as the program arguments. Once again, if the
file names are blank the standard input or output is used.
------------------------------------------------------------------*/
int main(int argc, char *argv[]) {
FILE *fptr;
register linkpointer startptr,newptr;
enum {odd,even} linenum = odd;
/*----- First get the filename and open the input file ------*/
if (argc < 2 || *argv[1] == 0) fptr = stdin;
else {
printf("Opening file %s for input\n",argv[1]);
if ((fptr = fopen(argv[1],"r")) == 0) {
fprintf(stderr,"\nError: Cannot read file %s\n",
argv[1]);
fptr = stdin;
}
}
if (fptr==stdin) printf("Please type in the lines of text\n");
/*----- Now get and encrypt the input text ------------------*/
startptr = newptr = malloc(sizeof(struct linetype));
for(;;) {
getline(newptr->text,30,fptr);
if (newptr->text[0]==0) { /* Check for a blank line */
newptr->total = 0;
break; /* Exit the loop if blank */
}
switch (linenum) {
case odd: encryptline(newptr,encrypt);
linenum = even;
break;
case even: encryptline(newptr,change);
linenum = odd;
break;
}
newptr->linkptr = malloc(sizeof(struct linetype));
newptr = newptr->linkptr;
}
/*----- Now get the filename for output and send the text ---*/
if (fptr != stdin) fclose(fptr);
if (argc < 3 || *argv[2] == 0) fptr = stdout;
else {
printf("Opening file %s for output\n",argv[2]);
if ((fptr = fopen(argv[2],"w")) == 0) {
fprintf(stderr,"\nError: Cannot write to file %s\n",
argv[2]);
fptr = stdout;
}
}
output(startptr,fptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
Part E : Sample Solutions to the Exercises 247
In the main function alter the call to the getline function to:
getline(newptr->text,LINELEN,fptr);
Alter file 3 of your previous program to insert at the top of the file:
#define ENCRYPT(c) ( ((c)&~3) + (((c)&2)>>1) + (((c)&1)<<1) )
#define CHANGE(c) ((c)^0x20)
The functions encrypt and change could not themselves be replaced with macros
as it is not be possible to point a function pointer at a macro.
248 Programming in ANSI C
Alter file 2 and file 3 of your previous program to insert at the top of the file:
#ifdef DEBUG
#define TRACE(fn) printf("Now reached function %s\n",#fn);
#else
#define TRACE(fn) /* No trace */
#endif
Insert into the getline function the following as the first executable statement:
TRACE(getline); /* debug printout */
and similarly insert as the first executable statement in the output function:
TRACE(output); /* debug printout */
If the compiler does not have the facility to define macros using compiler switches
you will need to insert into your program before the #ifdef statement:
#define DEBUG 1
Alter your previous program to insert a header file as shown on the next page. Apart
from replacing the declaration and definition statements at the top of the program no
further changes are required and no function need be modified. However, as this is
the final exercise the complete program is given.
Note that this program is based on the previous versions of the program that do not
have the advanced code of pointers to pointers (from exercise 10, question 5), the
rewritten version of the malloc function (from exercise 11, question 3) or the use of
the program arguments (from exercise 13, question 4).
Part E : Sample Solutions to the Exercises 249
#include <stdio.h>
#include <stdlib.h>
#define DEBUG 1
#define LINELEN 20
#define ENCRYPT(c) ( ((c)&~3) + (((c)&2)>>1) + (((c)&1)<<1) )
#define CHANGE(c) ((c)^0x20)
#ifdef DEBUG
#define TRACE(fn) printf("Now reached function %s\n",#fn);
#else
#define TRACE(fn) /* No trace */
#endif
struct linetype {
int total,letters; /* counters */
char text[LINELEN+1]; /* input line */
linkpointer linkptr; /* pointer to next in chain */
};
#include "lineprog.h"
/*------------------------------------------------------------------
Note this program is based on the version of exercise 13 that does
not use program arguments.
The change in this function is the use of the macro LINELEN in the
call to getline.
------------------------------------------------------------------*/
int main() {
char filename[41];
FILE *fptr;
register linkpointer startptr,newptr;
enum {odd,even} linenum = odd;
/*----- First get the filename and open the input file ------*/
printf("Please enter the input text file name: ");
getline(filename,40,stdin);
if (filename[0] == 0) fptr = stdin;
else {
if ((fptr = fopen(filename,"r")) == 0) {
fprintf(stderr,"\nError: Cannot read file %s\n",
filename);
fptr = stdin;
}
}
if (fptr==stdin) printf("Please type in the lines of text\n");
/*----- Now get and encrypt the input text ------------------*/
startptr = newptr = malloc(sizeof(struct linetype));
for(;;) {
getline(newptr->text,LINELEN,fptr);
if (newptr->text[0]==0) { /* Check for a blank line */
newptr->total = 0;
break; /* Exit the loop if blank */
}
switch (linenum) {
case odd: encryptline(newptr,encrypt);
linenum = even;
break;
case even: encryptline(newptr,change);
linenum = odd;
break;
}
newptr->linkptr = malloc(sizeof(struct linetype));
newptr = newptr->linkptr;
}
/*----- Now get the filename for output and send the text ---*/
if (fptr != stdin) fclose(fptr);
printf("Please enter the output text file name: ");
getline(filename,40,stdin);
if (filename[0] == 0) fptr = stdout;
else {
if ((fptr = fopen(filename,"w")) == 0) {
fprintf(stderr,"\nError: Cannot write to file %s\n",
filename);
fptr = stdout;
}
}
output(startptr,fptr); /* Prints out the whole chain */
return 0; /* Normal program termination */
}
Part E : Sample Solutions to the Exercises 251
#include "lineprog.h"
/*------------------------------------------------------------------
Function getline reads a line of text from the file identified by
the file pointer given by the third parameter and puts it
into the character array pointed at by the first parameter. The
second parameter gives a limit on the number of characters that can
be put into the array - characters on the line after this are
discarded by writing them one on top of the other at the end of the
string and finally overwriting them with the null string terminator.
The input line can be terminated by either a newline character or
the end of the file. There is no return value to this function.
------------------------------------------------------------------*/
void getline(char *txtptr, const int limit, FILE *fptr) {
register char *const endptr = txtptr+limit;
TRACE(getline); /* debug printout
*/
while ((*txtptr = getc(fptr)) != returnkey && *txtptr != EOF) {
if (txtptr < endptr) txtptr++;
}
*txtptr = 0; /* Add a null terminator */
}
/*------------------------------------------------------------------
Function output prints to the file identified by the file pointer
given by the second parameter the content of the chain of line
structures with the start of chain pointer given as the first
parameter.
First the number of letters and characters in each line structure is
printed to the screen and then the text held in each structure is
sent to the identified file which could also be the screen.
A message is given if there are no lines of input.
There is no return value to this function.
------------------------------------------------------------------*/
void output(const linkpointer startptr, FILE *fptr) {
register int j=1; /* j is used only to output a line count */
register linkpointer lineptr = startptr;
TRACE(output); /* debug printout */
if (startptr->total == 0) {
printf("Error: There are no lines of text to output.\n");
return;
}
while (lineptr->total != 0) {
printf("Line %d has %d letters out of %d characters\n",
j++,lineptr->letters,lineptr->total);
lineptr = lineptr->linkptr;
}
lineptr = startptr; /* back to start to output lines */
/*----- Now send the output to file -----------------------*/
if (fptr == stdout) printf("\nThe encrypted lines are:\n");
else printf("\nNow sending the lines of text to file.\n");
while (lineptr->total != 0) {
fprintf(fptr,"%s\n",lineptr->text);
lineptr = lineptr->linkptr;
}
}
252 Programming in ANSI C
#include "lineprog.h"
/*------------------------------------------------------------------
Function encrypt takes a pointer to a single character and encrypts
the character by swapping (ie. interchanging) the rightmost two
bits. It does this by using the ENCRYPT macro that takes the
character to be changed as parameter and expands to give an
expression with the value of the encrypted character.
There is no return value for this function.
------------------------------------------------------------------*/
void encrypt(char *cptr) {
*cptr= ENCRYPT(*cptr);
}
/*------------------------------------------------------------------
Function change takes a pointer to a single character and changes
the character by complementing the bit third from the left. It does
this by using the CHANGE macro that takes the character to be
changed as parameter and expands to give an expression with the
value of the changed character.
There is no return value for this function.
------------------------------------------------------------------*/
void change(char *cptr) {
*cptr= CHANGE(*cptr);
}
/*------------------------------------------------------------------
Function encryptline encrypts the line of text in the single
structure pointed at by the only parameter. It does so by calling
the encrypting function identified by the function pointer given as
the second parameter to encrypt each letter in the text. A temporary
character pointer is used to access each character in the structure
text. The non letter characters are not encrypted. At the same time
counts are made of the number of characters and letters and these
are put in the in the appropriate integer parts of the structure.
There is no return value to this function.
------------------------------------------------------------------*/
void encryptline(linkpointer lineptr,void (*fn)(char *cptr)) {
register char *cptr; /* temporary pointer */
TRACE(encryptline); /* debug printout */
lineptr->total = lineptr->letters = 0; /* zero the counters */
for (cptr=lineptr->text; *cptr != 0; cptr++) {
lineptr->total++;
if ((*cptr >= 'a' && *cptr <= 'z')
||(*cptr >= 'A' && *cptr <= 'Z')) {
lineptr->letters++;
(*fn)(cptr);
}
}
}
Index 253
Index
_ atol function 150
auto variables 106
_ character 16 Auto-decrement 31-33
__DATE__ macro 127-128 Auto-increment 31-33
__FILE__ macro 128 Automatic variables 106
__LINE__ macro 128
__STDC__ macro 127,203 B
__TIME__ macro 127-128
_exit function 169 Background 12
Bit fields 66-67
# Bit manipulation operators
24-26,51,123,176-178
# symbol 126,129 Boolean variables 48-49
## symbol 130 break statement 57-58,60,186
#define 127,185-186
#else 133-134 C
#elif 133-134
#endif 133-134 calloc function 167
#error 134-135 case 57-58
#if 133-134 Casts 30,93,123
#ifdef 133 char variables 15,27-28
#ifndef 133 Character constants 18-19
#include 131-132 Character functions 163-164
#line 127-128 Characters 15,18-19,27-28,163-164
#undef 127 clearerr function 159
close function 160
A Comma operator 60-61,123
Command line redirection 114-115
abs function 165 Comments 13,186,189-192
acos function 165 Compound statements
Address operator & 93,123 13,17,52-53,184,192
Arithmetic operators 22-24,123,178 Conditional Compilation 133-134
Array declaration 40,42-43,45 Conditional operator ?: 55,123,196
Array initialisation 42,45 Conditional statements 48-55
Arrays 40-46,64-65,81,88-90, const type qualifier 20-21,95
97-100,103,108,120,178-180 Constant pointers 95
Arrays as pointers 97 Constant variables 20-21
asin function 165 Constants 18-19
Assignment 22,29-33,40-41, continue 57,60
49,119,123,176,179 Control flow statements
Assignment operators 48,52-62,196-197
22,31-33,123,176 Conversion functions 149-150
atan function 165 cos function 165
atan2 function 165 cosh function 165
atof function 150 creat function 160
atoi function 150 ctype.h 141,163
254 Programming in ANSI C