html article
1 1
' n'
0
C
1 ' 1'
int
long
char
int
int
typedef
struct
if
switch
while
1 1
The Standard Library
March 1995
Barry McMullin
Introduction
It is well known that one should not waste time "re-inventing the
wheel" . In Engineering, this means not redesigning something one
has already got a perfectly satisfactory design for. In Software
Engineering, it means not rewriting software to perform
operations that one has previously written (and tested!) software
for.
In the case of the language there are a range of things that
programmers very frequently wish to do, which are so common that
standard software for the purpose is actually distributed along
with every compiler. This software is called the
Standard Library . You are more or less guaranteed that every
compiler will come with an implementation of the Standard
Library. It is important to be aware at least of the existence of
the Standard Library, and to have some outline idea of the
software contained in the Library. In this way you can avoid
unecessarily writing and testing software which is already
effectively available, in a well tested form, for free.
Chapter 10 of Illustrating C
provides a detailed discussion
of the Standard Library. You should consult this, or some
equivalent documentation (e.g. the on-line help in the Turbo-C++
IDE), whenever you need detailed information on the full range of
functionality of the Standard Library. This essay is intended
only to provide an introduction to a very small subset of the
Standard Library, and is not a substitute for those more detailed
sources of information.
Mechanics
In order to use the facilities of the Standard Library, one must
have some understanding of the mechanics of how the compiler will
access it.
Some very simple programs can be
completely
"self-contained" : all the code for all the functions was
contained in a single program file. In general, however, this
need not be the case; in particular, it will not be the case if
you wish to make use of the Standard Library.
In principle, of course, the Standard Library could be
distributed simply as a set of one or more source code files.
You would then use a library function simply by copying the
source code out of the relevant library file and pasting it into
your own program file. However, this is not the mechanism
which is used in practise for a variety of reasons:
Since the code for the Standard Library is rarely if ever
modified, it is very inefficient to have to recompile or
retranslate it every time a programmer makes a change to his own
code.
The library functions are, in some cases, not actually
written in the language at all.
The compiler suppliers normally prefer not to distribute
source code for the Standard Library as this would facilitate
unscrupulous pirating of their work.
What is actually done is that the Standard Library software is
distributed in a sort of "canned" or precompiled form,
as a set of "object files" (or, more commonly, a single
"object library" file). Your program file(s) are then compiled
completely separately from the source code for the Standard
Library, yielding one or more further object files. Finally, all
the required "object" files, both your own, and those making up
the Standard Library, are combined or linked together to
yield the "executable" file, which can actually be run.
To a large extent this process can be made automatic and
invisible. The compiler compiles your source file(s) to the
"object" code form, and then automatically links these together
with the any required library object file(s).
However, it cannot be made completely automatic. In
practise there is certain information, associated with the
Standard Library, which the compiler must be made aware
of at the time it is compiling your source files, in
order to make sure that the object files produced will correctly
link up with the object files for the Standard Library.
Since, for the most part, the Standard Library simply consists of
a set of functions which you can call in your program,
the most important such information is the way data is to
be exchanged with each such function; i.e. how many
parameters does the function take, of what types, in what
order, and what type of return value does the function
yield (if any)?
The compiler could, of course, try to infer this information from
the way the function is actually called; but this is not always
technically possible, and, in any case, it is much better if
the compiler has some independent way of knowing how the
function should be called, because then it can automatically
check whether you have, in each case, called it correctly. It
turns out that this is extremely useful, and is an effective way
of automatically detecting a range of very common programming
mistakes.
The compiler is given this information about how a function
should be called by the use of a so-called function
prototype . This is simply the "header" of the function
definition, which states the function name, the return
type, and the formal parameter list. But whereas, in a function
definition this is then followed by a compound statement which
actually defines the function, in a function prototype it is
simply followed by a terminating semicolon. Thus, a prototype
might look like this for example:
int multiply(unsigned *multiplicand,
unsigned multiplier, unsigned *product);
This tells the compiler that the function called
multiply should take three parameters, repectively of
types (unsigned *) , unsigned and (unsigned *) , and
will yield a return value of type Function
prototypes should appear at the outermost level of a source
file -~i.e. not within the definition of any function.
So far, so good. It seems that if you wish to call or invoke any
of the Standard Library functions in your program,, you must
simply insert, somewhere before the function(s) which make such
call(s), a suitable function prototype, so that the compiler will
then be able to decide whether the calls are correct or not.
But how are you going to know what is the correct prototype for
each function in the first place?
Well, one possibility is to look up the function in the detailed
technical documentation for the Standard Libarary: this will
normally include the function prototype. You can then copy that
into your own file.
However, this is clearly unsatisfactory. Apart from being
laborious, it is error prone -~and the possibility of an error in
the function prototype undermines a primary point of using
prototypes in the first place, namely that it allows the compiler
to crosscheck for valid function calls! A better idea is if the
prototypes are provided to you in a machine readable form -~i.e.
in one or more files on the computer. Then you can simply copy
the required ones, and paste them into your own source files.
Well, yes, this is a major improvement, but still leaves
something to be desired. For one thing, duplicating the
prototypes in every source program wastes disk space. More
seriously, there is always a danger that you might (accidentally)
modify a prototype, again confounding the idea of allowing the
compiler to automatically crosscheck the prototype against the
invocation(s) of the function.
A final possibility is to leave the prototypes in one or more separate
files; but have the compiler automatically access or scan the
relevant files immediately before, or as part of the process of,
compiling your files. In this way, there is no
laborious, manual, copying of the prototypes, but no duplication
and no risk of accidental modification either.
Files which are used for this kind of purpose -~which contain no
actual executable code, but which contain only function prototypes
(and possibly other things such as symbolic constants etc.) which
allow the compiler to correctly mesh the file it is compiling
with some other software which has been "pre-compiled" , are
called header files. Header files are normally given the
extension .h to distinguish them from files which actually
contain executable code -~function definitions etc. -~which will
normally have the extension .c .
It would be possible, in principle, to put all the prototypes for
all the functions in the Standard Library, plus any other
required information (sybolic constants etc.), in a single
.h file, and have the compiler automatically include it in
compiling any file. However, in practise this is not done for
various reasons. For example, most .c files only involve
calling a small subset of the functions in the Standard Library;
it is then wasteful and time consuming for the compiler to
process prototypes for all functions in the Standard
Library.
The mechanism that is actually used then is as follows. A series
of separate .h files are supplied with the compiler. Each
one provides prototypes (plus other required information) relating
to only some coherent or related subset of the functions
in the Standard Library. The programmer must then explicitly
instruct the compiler to process just those .h files which
are required in order to properly compile any particular .c
file. And this is done by inserting, into the .c file one
or more so-called include directives. A include
directive is something much the same as a define in the
sense that it is not handled by the compiler "proper" but by
the "pre-processor" which runs (automatically) immediately
before the compiler. In this case, the pre-processor processes a
include by concatenating together the .h file and
the original .c file, to produce a big temporary file which
is what is actually then processed in the compilation phase proper.
Thus, if you want to use a function from the Standard Library,
you must first look up, in the relevant technical documentation,
which header file contains the prototype etc. for that function;
and then insert a line something like the following in your
.c file:
include
The angle brackets around the name of the header file tell the
pre-processor to search for the file in the "standard"
directories for such things; normally these will be set up when
the compiler is installed, and you, as a programmer, need not
worry about what these standard directories actually are.
However, in case you wish to actually examine any of the header
files, the relevant directory for Turbo-C++, as configured on the
machines in the CAE Laboratory is:
F:
You may, of course, need to include several header files,
depending on the particular selection of Standard Library
functions you wish to use.
All required include directives are normally placed close
to the top of your .c file -~typically either at the very
top, or immediately after an initial introductory comment which
documents the overall contents of the source file. Each
include must , in any case, preceed any calls or
invocations of the relevant functions.
The rest of this essay is concerned with introducing just a very
small selection of the several hundred functions normally
available in the Standard Library. It is organised into sections
according to the distinct .h files required.
Implementation-defined Limits: limits.h
The header file limits.h essentially just provides
define directives defining symbolic constants for the
maximum and minimum values allowed with the standard integral
data types -~i.e. it does not provide any function prototypoes
as such. It is important to be able to refer to these values in
your programs in order to prevent, or at least detect, overflow
situations. The values potentially differ from one compiler
to another (hence "implementation-defined" ), but the symbolic
constants defined in limits.h always have the same names.
Thus by using the symbolic constants to refer to these limits it
should be possible to write your programs in such a way that they
will automatically adjust to whatever the limits actually are
with any particular compiler.
Some constants which are commonly used are:
INT : maximum value of
INT : minimum value of
LONG : maximum value of
LONG : minimum value of
UINT : maximum value of unsigned int
ULONG : maximum value of unsigned long
This is not an exhaustive list: examine a copy of limits.h
for yourself to see others. However, note that there is, of
course, no need for constants called, say, UINT or
ULONG since these minima are always guaranteed to be
simply zero.
Error Conditions: errno.h
Many functions in the standard library will detect "exception"
or "error" conditions in certain circumstances -~essentially if
the function has been requested to do something which, for some
reason, it can't. The exact action of the function in such
situations depends on the details of the particular function and
the particular exception condition. However, the most usual
strategy is for the return value from the function to
signal, with some special value, that something has gone wrong.
It is then up to the calling site to react to this in some
"appropriate" way. Minimally this will mean giving some kind
of overt external signal of the problem.
In any case, while the standard library functions typically provide
an initial or gross indication that something has gone wrong via
the return value, it is often also useful for the calling
site to have access to a more detailed signal which clarifies
exactly the nature of the problem. This is usually achieved by
having the standard library function record a detailed "error
number" in a global variable called errno . This
variable is defined within the standard library itself: but your
programs can gain access to it by a so-called extern
declaration. This declaration is already provided in the header
file errno.h ; so if you include this, you will then
be able to access errno just as any other variable is
accessed. By examining its value immediately after a call to a
standard function, your program can generally establish what (if
anything) went wrong.
As well as the declaration of errno the file errno.h
also provides a series of define directives which define
symbolic names for the standard error numbers or codes which may
be recorded in errno . This would allow your program to test
for specific error codes by comparing errno to these
symbolic values. We shall also see later how errno can be
automatically translated into a corresponding textual error
"message" , and, say, displayed, on the computer screen (see the
discussion of the function perror() , prototyped in
stdio.h ).
Utility Functions: stdlib.h
The header file stdlib provides prototypes for a selection
of miscellaneous "utility" functions, as well as a few more
symbolic constants. A small selection of the more commonly used
functions is as follows:
int atoi(char *s)
This takes a string representation of an integer (i.e. a string
something like "145" or "-9999" or "000000"
etc.) and converts it into the corresponding internal
representation of type which is the return value
from the function.
With the Turbo-C++ implementation of the Standard Library, the
behaviour where the answer would overflow (e.g.
atoi(100000) ) is indeterminate -~so it is your
responsibility to make sure this never arises; if the conversion
simply cannot be done (e.g. atoi("xyz") the return
value will be 0 .
long atol(char *s)
This takes a string representation of an integer (i.e.
a string something like "145" or "-999999" or
"25763178" etc.) and converts it into the corresponding
internal representation of type which is the return
value from the function. Leading white space in the string is
ignored.
Again, with the Turbo-C++ implementation of the Standard Library,
the behaviour where the answer would overflow (e.g.
atol(-500000000) ) is indeterminate; and if the conversion
simply cannot be done (e.g. atol("") the return
value will be 0L .
void exit(int status)
This function may be called to forcibly terminate the program at
any point. The parameter status is simply a number which
is, in some sense, made available to the "external" environment
of the program. In the case of programs run under
DOS , the exit() status can be accessed
via the errorlevel parameter in the DOS if
command -~though this is normally only used in batch
files.
In any case, if you are using exit() to terminate your
program, you should normally just give it one of two pre-defined
exit values which have been given symbolic names in
stdlib.h : use exit(EXIT ) if the program is
terminating "normally" and exit(EXIT ) if it is
terminating because of some unexpected or intolerable exception
or error being encountered.
Input and Output: stdio.h
stdio.h ( "standard input/output" ) provides basic
functions for accessing data "external" to a program. This
naturally includes data in files on disk, but also covers data
coming from the keyboard, or displayed on the screen, or data
routed via any of the other input/output "ports" of the
computer. All of these sources or destinations for data may be
generically referred to as streams , or, more simply,
files .
Files are classified into two kinds: text and
binary . A text file is divided into "lines" , where each line
has zero or more characters, and is terminated by a newline
character A binary
file is simply a sequence of
unprocessed or uninterpreted bytes, with no line organisation
superimposed upon it. In general, the terms
character and byte can be regarded as almost
synonymous here.
Files are accessed through data structures called file
pointers . Technically, a file pointer is the address of an
object of a special type, denoted FILE . This type is
defined by the stdio.h header file, and will not be
recognised by the compiler unless the header file has been
include 'd.
A file pointer must be created and associated with each
particular external disk file, or input/output port, before any
data in that file can be accessed ( "read" or "written" ). This
process of creating a file pointer and associating it with a disk
file or input/output port is called opening a file, and
is performed by the fopen() function described in more
detail below. The return value from fopen() is the
value of the file pointer, and must be stored in a suitable
variable to allow the file to be accessed subsequently.
Files are opened for access in a particular mode :
reading, writing, or (occasionally) both. The file is treated as
a sequence of characters (or, more generally, bytes).
When the file is first opened it is positioned at the very start;
the contents can then be read or written in sequence until the end of
the file. With certain kinds of file (namely those stored on
disk) it may be possible to "reposition" the file in an arbitrary
way -~go back to the start, or directly to the end, etc. If a
file if opened for writing then its previous contents, if any,
will normally be lost.
A program may have many files open at any given time, each one
associated with its own distinct file pointer.
File contents can be read, one character at a time, in sequence,
with the fgetc() function (provided the file is in read
mode); or written with the fputc() function (in write
mode). There are also a variety of more sophisticated reading and
writing functions such as fprintf() and fscanf() ,
which typically read or write many characters in one go, and
automatically translate between external "text"
representations, and internal "binary" representations, for the
various native data types ( etc.).
When reading a file it is necessary to be able to recognise when
the end of the file is encountered. This is signalled by
fgetc() yielding a special, reserved, return value,
with the symbolic name EOF (this value is also -~somewhat
misleadingly -~the return value if fgetc() encounters
any kind of error or exception condition).
Now this raises a problem: if a special value is reserved to
denote the end of file condition, does this mean that this
special value can never actually be stored within a file
(since, once it is read, it would be mistakenly taken as
signalling that the file is ended)? If so, this would be a
serious restriction.
In practice, this problem is solved in a rather ingenious, but
also subtle and confusing manner. A file is treated as a sequence
of values of type but the return value from
fgetc() is actually made of type Normally this is
just an "encoded" or "numerical equivalent" of the
value read from the file. But since supports more distinct
values than does, it is possible to encode EOF as a
value than has no "equivalent" in the
data type. In this way, a file can contain any arbitrary
values, without restriction. But, in turn, this means
that it is very important that before the return value from
fgetc() is transformed back into a value (e.g. by
assigning it to a variable of type ) it must be tested to
see is it EOF .
For consistency with this behaviour of fgetc() many
other functions in the Standard Library (such as fputc()
for example) also use the data type to effectively deal
with values, but allowing also for the special EOF
value.
As I said, this mechanism is quite subtle; in my opinion it may
be just a little bit too subtle! It involves the
programmer in relying, willy nilly, on automatic conversions
between types -~a practice of which I am generally severely
critical. Unfortunately, this is now a de facto
standardised way of doing things with the Standard Library, and
cannot be helped at this stage. Nonetheless, it certainly helps
if you at least understand what is going on.
Once the program is finished processing a particular file, the
file should be closed with the fclose() function.
This essentially involves discarding the data structure of type
FILE which was associated with the file: it is therefore
very important that, once a file is closed, the file pointer which
was associated with it is not used again (since it no longer
points at anything meaningful!). Files are automatically closed
when a program terminates in a "controlled" fashion (either by
reaching the end of the main() function, or by an explicit
invocation of the exit() function).
There are three files which are opened by default, and
automatically, for every program. File pointers associated with
them are defined in stdio.h , as follows:
stdin : The standard input file . By default
this is a read mode file, associated with the computer keyboard.
However, if the program is invoked from the DOS command
line, then this file may be "redirected" to be associated with,
say, a disk file, using the DOS input redirection
operator < .
stdout : The standard output file . By default
this is a write mode file, associated with the computer display
screen. When running a program under the Turbo-C++ IDE,
this corresponds to the so-called User screen , which can
be accessed via the Window pull-down menu.
Again, however, if the program is invoked from the
DOS command line, then this file may be "redirected" to
be associated with, say, a disk file, using the DOS output
redirection operator > .
stderr : The standard error file . This is a
write mode file, associated with the computer display screen.
It differs from stdout in that it cannot be redirected in
any simple way under DOS .
A program can do input and output on these three files
immediately, without having to call fopen() first. The
three files have certain conventional usages, as the names imply.
Thus many programs take one input file,
or stream of data, and transform it in some way into one
output stream of data. If this is the case, the input data would
normally be read from stdin , and the output data would be
written on stdout . stderr is reserved for signalling
"errors" or exception conditions, separate from the "normal"
output. Programs written in this way can then be conveniently
connected together in "pipelines" , with the output from one
being automatically routed as the input to another; in DOS
pipelines are set up with the pipe operator | .
Thus, for example, suppose we have two programs part1 and
part2 which perform two separate transformations on a
data stream. The simple ( DOS ) command:
D: part1
would cause part1 to be started up, reading its input from
the keyboard, and writing its output to the
screen. Incidentally, when input is being taken from the
keyboard, under DOS , the special key combination
CTRL-Z , followed by Enter , is normally used to signal
end of file.
By contrast, the command:
D: part1 out.dat
would cause part1 to read its input from the disk file
in.dat and write its output to the disk file out.dat .
Note that, despite the redirection of stdout , any error or
exception messages, written to stderr , would still appear
on the screen.
Finally, the command:
D: part1 out.dat
would cause the output of part1 to be automatically routed
as the input to part2 , whose output would then finally be
routed to out.dat .
Thus, writing programs to take input from stdin and write
output to stdout (and errors etc. to stderr ) means
that they can be subsequently mixed and matched in a very
flexible way to achieve a variety of different effects, and is a
very powerful idea. But even if you have no intention of
linking programs in this way, it will still often be convenient
to use stdin as a source of keyboard input, and
stdout (and/or stderr ) as a route for output to the
screen. There are specialised versions of several stdio.h
functions which access one or the other of these files by default
(e.g. printf() is a variant of fprintf() which
automatically writes to stdout ).
A small subset of the functions declared in stdio.h will
now be described in more detail.
FILE *fopen(char *filename, char *mode)
fopen() is used to create a file pointer or stream, for
subsequent input or output to a file. Thus, the return
value from fopen() must normally be stored in a variable, so
that it can then be used as an argument to other functions such
as fgetc() etc.
filename is a string giving the name of the desired file.
This should conform to the conventions of the "environment" or
"operating system" under which the program will be running.
Under DOS , file names consist, in general, of a device
specifier (e.g., D: ), followed by a "path" identifying
the desired subdirectory, followed by an individual file name
proper. Directory and file names proper consist of a name part of
up to 8 characters, followed by a period, followed by an
"extension" part of up to three characters. DOS does not
distinguish between upper and lower case anywhere in a file name.
The directory separator character in DOS is the backslash
; this is a rather unfortunate historical
accident, as this character also has a special meaning in
strings, as an "escape" character. The nett effect is that, in
representing a DOS file name in a string constant, you
must write the directory separator as a double backslash:
; note that this actually only results in the
internal storage of a single character within the string,
when the program is compiled.
Examples of well formed filename arguments under DOS
might be:
"myfile"
"yourfile.h"
"d:something.xyz"
"f:
"COM1:"
"lpt1:"
Of course the filename argument, as with any of the other
string arguments to be discussed, might equally be a string
"variable" -~the name of a array which had previously
been loaded with the desired file name (including the usual nul
character as the string terminator: ).
mode is a string specifying the desired file "access
mode" -~i.e. what kind(s) of operations are going to be carried
out on it. Two examples of legal values for this would be:
"r" : Open the file for reading.
"w" : Open the file for writing.
In general, fopen() will open a file for access
either as a binary or a text file -~but the default
is implementation specific. Under Turbo-C++, this default can be
configured by the user. In any case, regardless of the default,
the type of access can be explicitly specified in the mode
string by adding either a t or b character for text
or binary respectively, e.g.:
"rt" : Open for reading as a text file.
"wb" : Open for writing as a binary file.
If the call to fopen() is successful, then the return
value is simply the value of the created file pointer. However,
if the call fails for any reason (e.g. trying to open a file on
device "X:" when no such device is present on the machine)
then the return value will be the special zero or
null pointer value defined in stdio.h (and, indeed,
several of the other header files) define the symbolic name
NULL for this value. Thus, after a call to fopen()
your program should always check the return value to see
whether it is NULL -~and take some appropriate action
if so (e.g. issue a suitable message and call exit() ). In
any case, if the return value from fopen() is
set to NULL then errno will also be set to some more
specific error code.
int fgetc(FILE *stream)
fgetc() reads the next sequential character (byte) from the
open file identified by the file pointer stream . This
character is converted into an value, and is the
return value from the function. However, if the end of file
is encountered (the last character has already been read), or if
any other error is encountered in reading, then the return
value will be EOF , and errno will be set
appropriately. In the case of a text file, the newline character
should be interpreted as terminating a line.
int getchar(void)
getchar() is equivalent to fgetc(stdin) .
int fputc(int c, FILE *stream)
fputc() converts the value c into the
corresponding value, and writes that to the next
sequential position in the open file identified by the file
pointer stream . The return value is normally set to
be equal to the input argument c ; however, if any error is
encountered then the return value will be EOF and
errno will be set appropriately.
int putchar(int c)
putchar(c) is equivalent to fputc(c, stdout) .
int fprintf(FILE *stream, char *format, ...)
fprintf() is a "print-and-format" function. It
provides for transformation of values of any of
the native data types into a corresponding textual or string
form; this whole string is written to the open file
identified by the file pointer stream .
fprintf() is actually just one of a whole family of
functions which provide minor variations on the basic idea of
formatted output.
fprintf() takes a variable number of arguments. The
arguments stream and format are
required -~they must always be present and must be of
the types specified in the prototype; but these may then be
followed by an arbitrary number (including zero!) of further
arguments of arbitrary types.
format is a string which indirectly specifies how many
further arguments are being passed in (if any) and their types.
More precisely, format contains "ordinary" characters
interspersed with "conversion specifications" . The ordinary
characters are simply written to stream . But each time
fprintf() encounters a conversion specification, another
input argument is converted into a textual or string form, and
output on stream instead.
Thus, to simply print a string on the screen (and assuming that
stdout has not been redirected) one might use the function
call:
fprintf(stdout, "Helllooo VietNAM!!!!!!")
Conversion specifications are introduced by the character
. Examples of some simple conversion
specifications are as follows:
"
representation.
"
"
"
"
be technically of type (char *) and point at a normal
nul-terminated array of characters).
In the last two cases, the argument is already essentially
textual, so there is strictly no "conversion" to be done as
such.
There are many other more complex conversion specifications, but
they will not be detailed here.
Examples of the use of fprintf() might be as follows:
fprintf(stdout, "The answer is:
It is important to understand that although the value
42 appears in texual form in the statement above, when the
program is compiled this is converted into an internal,
non-textual, representation. This is why, in order to display
this value on the screen again, fprintf() must be used to
convert back to a textual representation. In this case the nett
effect of this call to fprintf() will be that the string:
The answer is: 42.
will appear on the screen (assuming stdout has not been
redirected). Precisely the same display would, of course, result
from the statement:
fprintf(stdout, "The answer is:
Again, the same display would result from the sequence of statments:
x = 7;
y = x;
x ;
y++;
fprintf(stdout, "The answer is:
where x and y are variables. Similarly, if
a , b and c are variables of type
and respectively, we might encounter the sequence
of statements:
a = 32000;
b = 32000L * 32000L;
c = '!';
fprintf(stdout, "
resulting in the following message on the screen:
32000 times 32000 is 1024000000!
Note carefully, how the extra arguments in the call to
fprintf() must pair off exactly with the conversion
specifications in the format argument: i.e. for each
conversion specification, in sequence, which appears in the
format string, there must be one extra argument, of just
the right type. For technical reasons, which will not be explored
here, it is not possible for the computer to automatically
check that conversion specifications do, in fact, match up with
arguments correctly . A mismatch between conversion
specifications and arguments in a call to fprintf() , or
some related function, is one of the most common kinds of errors
encountered in programs -~precisely because the computer
cannot automatically detect it. The effects of such errors are
completely unpredictable, and may not be immediately
apparent at all, so that they can be extremely difficult to track
down. You have been warned!
Some trivial examples of such mismatching would be as follows:
fprintf(stream, "The answer is:
fprintf(stream, "Or perhaps it is:
fprintf(stream, "Or there again, maybe its
fprintf(stream, "My last offer is:
Note that if you want to move onto a new line on the display
screen, you must explicitly write the newline character
to the relevant stream. Try to predict the
precise effect of this sequence of statements for example:
fprintf(stdout, "Hello. ");
fprintf(stdout, "Hello?");
fprintf(stdout, " that you?");
fprintf(stdout, "Yes? );
fprintf(stdout, "My number is:
Note that because the character in the
format string is interpreted in a special way by
fprintf() (namely as introducing a conversion
specification), there is a problem if one actually wants to just
output this character. To get around this you just put in two
successive characters, thus:
. The second one will cause fprintf()
to recognise that this is not a conversion specification after
all, and it will simply output one character
without further ado (and without attempting to convert any
argument).
The return value from fprintf() is the number of
characters which have been written out, or EOF if any error
has been detected. programmers commonly neglect to check this
return value from fprintf() , presumably because error
conditions which it can detect and signal are rather rare.
Nonetheless, as a general practice I would recommend that you
include code in your programs to check this return value (at
least to see if it is EOF ), unless the your usage is
extremely trivial (e.g. just printing the format string,
without having any extra arguments to be converted).
int printf(char *format, ...)
printf(format, ...) is equivalent to fprintf(stdout,
format, ...) .
int fscanf(FILE *stream, char *format, ...)
fscanf() is more or less the converse to fprintf() :
it reads characters from stream and attempts to convert
them into "internal" representations of appropriate types, as
determined by conversion specifications in the string
format . The extra arguments must all be pointer types in
this case . Typicllly, these arguments will simply be the
addresses of variables (generated with the &
operator) into which the converted values are to be stored.
fprintf() thus simply stores each converted value at the
location pointed at by each extra argument in turn. Again, it is
up to you, the programmer, to ensure that the arguments match the
conversion specifications correctly; and again, if they do not,
then this will not be automatically detected, and will have
entirely unpredictable effects.
The conversion specifications handled by fscanf() are quite
similar in format to those handled by fprintf() . They will
not be described further here.
The return value from fscanf() is the number of input
fields or values successfully scanned, converted, and stored, or
EOF if end of file or any error is encountered. In general,
it is a very good idea for your programs to check this
return
value to see that it has the expected value.
int scanf(char *format, ...)
scanf(format, ...) is equivalent to fscanf(stdin,
format, ...) .
int fclose(FILE *stream)
fclose() simply closes the open file associated with
stream . The return value is zero if this is completed
successfully; otherwise it is EOF and errno is set.
While open files should be closed automatically if or
when your program exits anyway, it is still a good practice to
explicitly close files with fclose() when your program is
finished with them. This makes the behaviour of the program more
intelligible to someone reading it, and also provides a little
extra "robustness" against the possibility that your program
might terminate abnormally (i.e. CRASH!).
void perror(char *s)
perror(s) outputs the string s on stderr ,
followed by an error message which describes or elaborates the
error code currently stored in errno . It is
equivalent to:
fprintf(stderr, "
message ")
where error message is a string describing whatever error
condition is represented by the current value of errno .
perror() might usefully be called on any occasion when your
program detects that a standard library function has failed to
operate as expected.
Diagnostics: assert.h
assert.h provides the declaration of one "function-like"
object with the following prototype:
void assert(int status)
For technical reasons, this object is not implemented as
a function in the normal sense, but is what is called a
macro instead. However, for most practical purposes, it can be
regarded just as any "normal" function.
If status is non-zero then assert() will have no
effect; but if status is zero then a message of the
following form will be output to stderr :
Assertion failed: status , file
filename , line nnn
where status is the value of status (i.e. zero),
filename is the name of the source file containing the
call to assert() , and nnn is the line number in
that file at which the call appears. assert() then causes
the program to terminate.
assert() is potentially a very useful way of dealing with
error or exception conditions detected within your programs, where
the condition is such that you are unable (or unwilling) to try
to make the program deal with it in any more "intelligent" way.
The beauty of assert() is that, if it is activiated, the
resulting message immediately pinpoints exactly the position in your
source code where the problem is detected. However, note that
this benefit will be almost totally lost if you place the
invocation of assert() within a function of your own, which
you then call from wherever the error is detected: for then the
file name and line number output by assert() will always be
the same (namely within your error handler) and will not give any
indication of the "real" position at which the problem was
detected.
Character Class Tests: ctype.h
ctype.h provides prototypes for a selection of functions
which test a value for membership of a given class. In
each case the function takes a single argument, which is technically
of type int , but which represents a value or,
possibly, EOF ; this is for compatibility with fgetc()
as discussed earlier. The return value is always of type being
zero (false) if the value is not a member of the relevant calss,
or non-zero (true) if it is.
Some examples are as follows:
int isalpha(int c) : Letter (i.e. "alphabetic" )?
int isdigit(int c) : Digit?
int isalnum(int c) : Letter or digit
( "alphanumeric)?
int islower(int c) : Lower case letter?
int isupper(int c) : Upper case letter?
int isspace(int c) : "Whitespace" (i.e. space,
newline, tab, etc.)?
int isprint(int c) : Printable or displayable
(including space)?
ctype.h also provides two functions which convert the case
of letters:
int tolower(int c)
int toupper(int c)
String Functions: string.h
string.h provides prototypes for a wide range of functions
which operate upon strings in various ways. A brief description
of a small sample of these follows. Note that where one of these
functions potentially modifies a string, it might , in
general, make it longer ; therefore in passing in a
pointer to the string which will be modified, you must make sure
that the array holding this string is big enough to hold
the longest possible string (including the nul terminator) which
might conceivably result from the string operation(s). If you
fail to watch out for this the results will generally be
unpredictable -~and definitely not very pleasant.
char *strcpy(char *s, char *t)
Copy the string pointed to by t to that pointed to by
s . The return value is simply s (i.e. a pointer to
the destination string); this is sometimes
convenient in forming more complex expressions, but, more
usually, it can be ignored (discarded).
char *strcat(char *s, char *t)
Concatenate the strings pointed at by s and t ; the
result is pointed to by s . That is, in effect, the
characters from string t are added on at the end of string
s . The return value is again simply s .
int strcmp(char *s, char *t)
Compare the strings pointed to by s and t . The
return value is zero if the two strings are identical; it
is negative if s would come before t in an
alphabetical ordering; and positive if t would come before
s .
Conclusion
This essay serves only to skim the surface of the facilities
offered by the Standard Library. It is well worth your while
to become reasonably familiar with the full range of services
which are available in the library: it can speed up programming
of many applications very considerably.