Using QuickC PDF
Using QuickC PDF
Using QuickC PDF
Werner
Feibel
L
i
Using QuickC
®
Using QuickC
Werner Feibel
Osborne McGraw-Hill
Berkeley, California
Osborne McGraw-Hill
2600 Tenth Street
Berkeley, California 94710
U.S.A.
For information on translations and book distributors outside of the U.S.A., write to
Osborne McGraw-Hill at the above address.
A complete list of trademarks appears on page 595.
Using QuickC®
Copyright © 1988 by McGraw-Hill, Inc. All rights reserved. Printed in the United States of
America. Except as permitted under the Copyright Act of 1976, no part of this publication
information has been obtained by Osborne McGr«w-Hill from sources believed to be reliable. However,
because of the possibility of human or mechanical error by our sources. Osborne McGraw-Hill, or others, Osborne
IVIcCraw-Hill does not guarantee the accuracy, adequacy, or completeness of any information and is not responsible
for any errors or omissions or the results obtained from use of such information.
For my parents, with love and thanks
for all they've done for me
J
Contents
Introduction xiii
Introducing C 1
C Features 2
An Example of C Programming 3
The Nature of C 5
Using C 7
Why QuickC U
6 Controlling C 167
Relational and Equality Operators 168
Logical Operators 170
The if-else Construct 172
The switch Statement 178
More Operators 182
Loops in C 186
Components of the while Loop 188
The do-while Loop 191
The for Loop 193
break and continue 201
Summary 204
7 Functioning in C 205
C Functions 206
Flow of Control 207
Function Returns 213
Formal and Actual Parameters 217
Summary 225
10 Pointers 263
Objects, Lvalues, Values 264
Pointers 267
Pointers as Function Parameters 274
Pointer Arithmetic 280
Miscellaneous Points 284
Pointer Pitfalls 285
Summary 287
Index 597
Acknowledgments
Many people deserve thanks for making this book possible. The following
people were particularly helpful and encouraging throughout the project.
Kris Jamsa deserves thanks for his technical review and very helpful
suggestions. Many of the book's good features are there because of his
suggestions and corrections; the book's defects are there because I didn't listen
to Kris's objections.
I also wish to thank the people at Osborne/ McGraw-Hill who are always
helpful, friendly, and accommodating; it has been a pleasure to work with
them. Jeff Pepper deserves special thanks for getting this project started and
for keeping it on the smoothest course and Lyn Cordell
possible. Liz Fisher
helped keep me on course, and dealt gracefully and efficiently with ambiguous
materials and last minute changes, for which I thank them. Thanks also to the
unsung heroes in the art and production departments at Osborne/ McGraw-
Hill. They had to work extra hard (on a tight schedule made even tighter by
changes required late in the production process) to turn the manuscript into a
real book.
owe my friends many thanks for putting up with me and keeping
Finally, I
me going during the past few months. In particular, I want to thank Luanne
Panarotti, Scott Barnes, Alexa Beiser, Dan Kiely, and little Dylan Reiser
Kiely, whose favorite part of the project was the printer cable.
This book may have gotten done without these people, but it would have
been a lot less fun.
Introduction
xlll
xiv Using QuickC
ming environment. In later chapters, youll find sections that illustrate how to
use various QuickC features as part of the discussion of particular C con-
structs. I've also included advice on some pitfalls you may encounter when
language definition currently being drafted, so you can also use this book to
learn C with other compilers.
The text contains lots of examples, since these usually illustrate a point
better than several paragraphs of description. Youll also find suggested modi-
ficationsand variations to try. The programs are designed to let you experi-
ment easily, and many of them will report on their own actions.
The best way to learn a programming language is to use it. Whether your
main interest is in C or QuickC, you'll get a lot more out of this book and —
enjoy it much more — if you enter the programs and examples and try them
out yourself Once you've done that, experiment with the examples; change
some of the functions or the input. One of the best ways to deepen your
understanding of a language is to experiment with it. Then, once you feel
comfortable with the material covered here, work on projects that interest you.
You'll find it is helpful, though not essential, to have some familiarity with
programming — perhaps with a language such as Pascal or BASIC. If you
have used either or both of these languages, you'll find that C differs in
important, and sometimes in very subtle, ways. If you have no programming
experience, don't worry. You'll still be able to use this book, but you may need
to put in some extra effort to be sure you understand some of the terms and
concepts.
The complete source code for all the programs is included here, so you can
simply type them in from the book. However, if you want to save yourself the
demands of that task, the source code for all the programs in this book (as well
as some additional programs and functions) can be purchased on two 360K
diskettes, for $22 ($20 plus $2 for postage and handling). The diskettes contain
about 450K of source files. In addition to the programs from the book, these
files include a quick and dirty program for testing functions during develop-
ment, a simple daily reminder program that you can tailor to your needs, and
several others.
To order the diskette, use the form that follows this introduction.
To order the source code diskettes for Using QuickC, please provide the
following information and a check or money order for the appropriate
amount:
$25 per copy ($20 + $5 shipping and handling) for foreign orders
$23 per copy ($20 + $2 shipping and handling + $ sales tax) for Massachu-
1
setts residents
$22 per copy ($20 + $2 shipping and handling) for other orders
Name
Street Address
Please send this information, along with your check or money order to:
Werner Feibel
P.O. Box 2499
Cambridge, MA 02238
Osborne/ McGraw-Hill assumes no responsibility for this offer. This is solely
the offer of Werner Feibel, and not of Osborne/ McGraw-Hill.
I
A
1
Introducing C
of C; learning how to use the language to its fullest is your real challenge. Your
rewards, which include programming power and a better understanding of
your computer, are definitely worth the effort.
Microsoft Corporation's QuickC makes C
and accessible by
available
providing a complete, easy-to-use language implementation. QuickC is an
enhanced compiler (it converts programs into object code), a linker (it pro-
duces executable files), a shell (it serves as a command interpreter), and a rich
programming environment. With the help of this book, you'll be able to take
advantage of QuickC's special features, and make C work for you.
C Features
Performance Features
C is smaller than Pascal, Modula-2, or even BASIC. C has only about 30
keywords or reserved words. Because many of C's actions are close to
assembly-level activity, using C can increase the speed of your program and
make it easier to create concise, "optimized" expressions.
Introducing C
C gives you a complete set of tools. It's your job to maximize its potential.
Because C lets you get very close to the actual memory cells and registers in
your computer, you can do just about anything in a C program. This capability
makes C very powerful — ideal for complex projects, or programs that need to
take full advantage of the system on which they are running.
Portability
C can be very portable. One reason for C's portability is the preprocessor,
which makes it possible to substitute information specific to a particular
machine when compiling or running a program. As you'll see in later chapters,
you can use the preprocessor to help make your programs run faster.
IVIodularity
One feature that helps make C portable is its capacity to build programs out of
small functions and library modules that have already been compiled. The
library modules can contain C functions specific to a particular machine.
Because you can build and compile independent function libraries. C allows
you to write programs in a modular fashion. You can break large programs
into smaller units, which contain functions that can be used together in other
An Example of C Programming
The following listing shows one way of incorporating information from other
files into a program, and illustrates one way of increasing program portability.
Using QuickC
feed, which puts the cursor at the leftmost column of the next line.
The % begins a placeholder for a particular type of information, whose
value is substituted when printf( ) writes the material; the s indicates that a
string is to be written. In our example, %s is the placeholder for PROG
MESSAGE, which contains a string. In C, a string is a sequence of characters
terminated by a special character, which we'll discuss later.
enclosed in braces.
Within the body of the function, individual statements end with a semi-
colon. Comments may appear anywhere in a program, and are enclosed
between /* and */.
Note that PROG MESSAGE is not defined anywhere in the program
file. Rather, this value is specified in the header file, proginfo.h, which the
compiler reads when processing the file containing the main program. Such
header files are common
Each system or program may have its own
in C.
header files. These files contain program or operating system specific values
and definitions. For example, when running under QuickC, the program's
Introducing C
message would read "QuickC Version 1.0"; running under Microsoft C Ver-
sion 5.0, the message might be "Microsoft C Version 5.0. "These messages are
stored in a file such as proginfo.h.
Most C compilers provide a file called stdio.h. which contains system-and
implementation-specific information, and which associates specific values,
such as "QuickC Version 1.0" with names, such as PROG MESSAGE.
Search your QuickC directories for files with the extension .h. These are the
header files provided with your compiler.
The Nature of C
C can be described as a middle-level language, having characteristics and
capabilities of both high- and low-level languages. This is one reason for its
and Brian Kernighan, who was involved in much of the early work with C,
intended C to be useful for writing systems (such as operating systems) as well
as application programs (such as editors). Therefore, the developers gave C the
speed and flexibility of assembly language, but the data structures and control
constructs (such as if-then, for, while) of high-level languages.
Table 1-1 lists some properties of high- and low-level languages, and
indicates where C stands with respect to these properties. Note that C has
almost every property listed.
A Small Language
C has a very small vocabulary — only about 30 keywords. Although the words
are few, you can say a great deal with them. A small vocabulary does not mean
Using QuickC
a weak language: some linguists claim that fewer than 900 words are needed
to communicate effectively in English.
On the other hand, C has lots of operators or symbols that cause specific
actions. In fact, C has more operators than keywords! This combination of a
small vocabulary and many operators lets you use C as a "construction set."
You can easily create individual statements or functions to do just the right
amount of work. As you'll see, C programs are usually built of small functions
that perform specific tasks. These functions are collected in modules and used
as needed.
The Power of C
Despite its smallC is being used for a huge range of programs. Large parts
size,
of the powerful UNIX and DOS operating systems are written in C. Similarly,
C has been used to write compilers (including C compilers), editors, typeset-
tingprograms (such as troff and TeX), and such programs as dBaselll and
Microsoft Windows.
At the other end of the spectrum, you will find many C programs that
perform specific tasks, such as display a calendar for any month or year, count
thenumber of words in a file, list the contents of a directory, find a particular
word or other expression in specified files, compute the interest payments on a
loan under various conditions, and so on. Source code for many of these
Introducing C
Standard Versions
There is as yet no official standard for C. Although C is used everywhere, it is
only now being officially defined. There has been a de facto standard version
of the language, summarized in The C Programming Language by Brian
Kernighan and Dennis Ritchie (Englewood Cliffs, New Jersey:
Prentice-Hall, 1978). This version of C is known as "K&R C," with compilers
often claiming that they implement "full K&R C," or "K&R C, with exten-
sions."
After several years, however, a standards committee is about to complete
its work on an official definition of the language. The working name for this
version is "Draft Proposed ANSI Standard C." Eventually, C's official name
will be "ANSI Standard C." (ANSI stands for American National Standards
Institute.)
Using C
Getting a C program from idea to completion takes several steps, with each
step modifying or extending your program. In the next chapter you'll learn
how to write C
programs and get them to run using QuickC. This section
summarizes the main components of a C program and the major tasks
involved in getting from your text file to a running program. We'll see that
QuickC puts the tools for accomplishing various tasks under one program
Using QuickC
"shell." This makes it easy to move from one phase of the program creation
process to another.
When you create a C program, various types of files are created and used.
These include: source and header files created with a text editor; library and
other object files, linked with the compiled version of your main program file;
and an executable file that runs the program.
The first step is to create the program in text form — this is the source file.
The file contains the main program, niain( ), which calls printf( ), as did
our first example. There is also an instruction (#include "proginfo.h") to read
the contents of a header file, proginfo.h.
There are several new features. Notice that this program also writes
numerical information, the value of variable il. The %d (for decimal) place-
holder indicates this fact. In the second call to printf( ), the value to be
substituted is the value of il, which is 16 after the addition is carried out.
The second argument, information passed to printf(), is substituted for
the placeholder in the function's first argument. This simple replacement
strategy extends to handle additional arguments, as you'll soon see.
The program also illustrates how to define a variable in C, that is, how to
associate a data type with a variable name. (If you're not used to working with
data types, and are not sure what they are, don't worry. Data types are
explained in Chapter 3.) The variable il is defined to be of type int, for integer.
Introducing C
assigns the result of adding 7 and 9 to the variable il. You'll learn about
variable definition and assignments in Chapter 3.
Header files are ordinary text files either written by you or provided with
the compiler. These files generally contain information needed for specific
tasks on a particular machine or with a particular compiler. Associated with
this information are mnemonic names, which are easier to remember than
obscure numerical values. When you use these names, the compiler substitutes
the values it finds for each name in the header files. For example, the header
file, proginfo.h, may contain the following:
The second statement provides a name for the largest integer represented
on the system. This name is much more meaningful than the actual number,
which may vary from machine to machine. When the compiler sees MAX
INT in the source file, it substitutes the value 32767. In the third instance, the
compiler substitutes the numerical value when you use false in your program.
(Later, you will see how C represents true and false.)
Such substitutions work out well for you and your program. You can use
.
10 Using QuickC
with function library files and other precompiled (object) files. The outcome of
this linking process is the program itself often known as an executable file.
Library Files
1 Source files created with an editor and processed by the compiler. One
of these files contains the main program function, main( ). Every C
program must have at least one source file.
2. Optional header files, which may contain various values and defini-
tions for the program or the environment.
3. Library files and other object files, which are linked with your com-
Introducing C 11
4. An executable file.
^REMEMBER
• There must be at least one source file — otherwise you have nothing to
compile or run.
All files other than source files — headers, libraries, and so forth — are
optional.Whether you need such files, and how you add them, depends on the
C compiler you are using and the tasks you are trying to accomplish in your
program.
If all goes well, and the compilation and linking processes take place
without error, your program will be ready to run. Of course, that's often when
the real fun begins, when you discover the surprising things you've uninten-
tionally instructed your program to do.
3. Linking the resulting object file with whatever libraries and additional
modules are needed
Why QuickC?
QuickC makes it easy to carry out the steps required for C programming in a
flexible manner, within a tightly integrated and powerful environment. The
QuickC system provides a shell within which you can easily move from one
12 Using QuickC
QuickC to optimize the program for speed (at program size); or you
the cost of
can ask it to warn you about any questionable programming practices, such as
constructs that violate the official language definition.
If you are used to writing programs, you know the tedium involved in
fixing small errors as the compiler encounters them. You must stop the
compilation, call up your editor, change your file, save it, call up the compiler,
keystrokes. If there are any problems with the program, QuickC puts you back
You can edit the file, then tell QuickC
in the source file at the appropriate place.
to continue. The system then recompiles, relinks, and reruns the revised
program.
QuickC's integrated environment is also convenient for debugging. You
can trace the program's execution using the debugging facility. If a change is
will get the system to reprocess your program and run it again, but this time in
debugging mode.
QuickC comes "preloaded" with many functions defined in separate
libraries by other C implementations. This gives you access to a more powerful
C without forcing you to link in separate libraries to get this power.
QuickC does its work quickly. In the next chapter, we will look
Best of all,
at the QuickC environment and explore it with a few sample programs.
Throughout the book, you will learn ways of taking particular advantage of
QuickC's environment.
A Quick Look
at QuickC
13
14 Using QuickC
Hands-on QuickC
Although QuickC includes dozens of options and commands in its collection,
the chances are very good that a large proportion of the things you'll need to do
in most applications will involve only a dozen or so commands. In this part of
So, let's get started. We'll assume you have QuickC installed on your hard
disk or on floppy disks and that you are currently in a directory or on a disk
from which you can access the qc.exe program. We'll also assume that you are
familiar with your keyboard, and that you know where keys such as the
RETURN, ALT, CTRL, DEL, INS, HOME, SHIFT, TAB, and BACKSPACE are
located.
A Quick Look at QuickC 15
A Quick Introduction
As you try things, you'll get a better feel for the command structure of QuickC.
You'll notice that commands generally tend to be either single keystrokes or a
combination of the ALT, CTRL, or SHIFT key with a letter key.
Also, as you spend more time in the QuickC environment, you'll notice
that it includes several menus, which provide you with access to QuickC's
components. To menu, you will need to press the ALT key together with
select a
a letter key (the first letter of the menu's name). Once in a menu, you can select
an option by pressing a letter key or highlighting the option and then pressing
RETURN. The default option is always highlighted when you get into a menu.
You can also call many commands directly —
that is, without going through a
menu. In the first half of this chapter we will give commands directly and with
a menu, depending on the command. In the second half of the chapter, you'll
learn the alternative ways of giving commands.
Selection of an option inside a menu generally brings you to a dialog box,
in which QuickC shows you the values it has for the options, and gives you a
chance to change some of these values. For these options, the default values
will generally be highlighted, and will be in effect if you just press RETURN.
Finally, if you ever find yourself in a position where you don't know the
command to accomplish something, just press the Fl function key, to call up
the on-line Help facility.
To get you started with QuickC, let's create a small C program. This will
give you an opportunity to use the editor and the compiler. The sequence of
steps described below will get you started. As you follow these steps, keep in
mind that you sometimes need to press RETURN (also called ENTER ) after
completing a command or a line. We'll mention this explicitly at first, but will
eventually stop reminding you. At other times, however, you should not press
RETURN.
To get QuickC to a point where you can start entering a program, type qc,
then press RETU rn. Once QuickC has started up, your screen should appear as
shown in Figure 2-1 You are now ready to start writing a program. Type in the
.
following program:
malnO
16 Using QuickC
printi ( "The sun is '/.li miles from the earth. \n". DIST) ;
To do this, just start typing. When you get to the end of the first line, press
RETU RN twice in succession to end the first line and to add a blank second line.
Type things exactly the way you find them in the listing; make sure to observe
the case of the characters. If you make a mistake, use the BACKSPACE key to
delete the error. Pressing backspace deletes the last character you typed.
To get the indentation in the fifth line (the one beginning with printf),
press the TAB key before starting to type the hne. When you press RETURN at
the end of this hne, you'll find the cursor directly under thep in printf -that is,
indented. Type the right curly brace. Then, to move it back to the leftmost
column, press the SHIFT and TAB keys simultaneously to get a "reverse tab."
When you've finished typing in the program, it's time to compile and run
it. To do so:
2. Press RETURN.
QuickC will quickly compile and run your program. You should get the
following output from the program:
This will be followed by a QuickC message telling you to press any key.
Congratulations, you've just finished running a QuickC program! To save
the program to disk,
3. Then press X (for Exit) or S (for Save), but do not press RETURN.
After verifying that you want to save the changes, QuickC will ask you for
a file name. Press the BACKSPACE key until you've erased the default name,
untitled.c. Type in the name under which you want to save the program. For
this discussion we'll use first.c. (Actually, all you need to type here is first.
18 Using QuickC
qc first
Notice that we did not include the QuickC file extension .c when telling
about the Unless you tell it otherwise, QuickC assumes you want to work
file.
with a file ending in .c. You can, of course, include the .c extension when you
type your file name on the preceding command line.
You're going to add several new lines to the program, and also delete one
line. Once you're in the QuickC environment, and have the first.c file open,
move the cursor to the line immediately below the #define line.
Start entering the additional #define statements in the following version
of the program. Make sure all the uppercase names are entered exactly as in
the listing, and make sure the #define is always in lowercase.
mainO
printf ( "y.s y,s and Xs: '/.ll '/.B\n"
DIST MESS. PTA, PTB, DIST, DIST.UNIT)
printf ( "%s '/.If '/.sVn", SPEED.MESS, SPEED, SPEED.UNIT)
printf ( "'/.s '/.If y.8 = '/.If '/.s\n",
TIME_MESS. DIST. DIST.UNIT, FORMULA. TIME.UNIT):
Once you've added all the new #define lines, move the cursor down to the
line beginning with printf. When you're on that line, press CTRL-Y, to delete
the entire line. Enter the replacement lines from the preceding program.
—
To see what happens when you get a compiler error, let's go back into the
file and change something. Press the PGDN key to move near the end of the file.
Then use the arrow keys to move to the line, beginning with printf, that
contains references to SPEED_MESS, SPEED, and SPEED_UNIT in
each of these three names, delete one of the £'s, to give SPED — MESS,
SPED, and SPED_UN1T. To do this, use the CTRL-RIGHT arrow keys to
move the cursor to SPEED MESS. Then move to one of the £"s in this
word, using the RIGHT arrow key. Press DEL to delete this E. Then move to
SPEED and do the same thing. Finally, delete an £from SPEED_UNIT To
recompile and try to run, press ALT-R RETURN again. This time the compiler
gives you an error message, as shown in Figure 2-2.
The compiler tells you that SPED MESS is undefined, and that the
error is the first of three. Use the arrow keys to move to the E in SPED
MESS. Once there, simply type £ again.
Then press SH1FT-F3, that and the function key F3
is, the SHIFT key
simultaneously. QuickC will leave you on the same line, and will give you the
same error message, but will tell you that this is error two of three. Make the
correction to SPED, and press SHIFT-F3 again. You will see the third error
message. Correct this, then press ALT-R RETU RN to recompile. This time things
should work as they did earlier.
What the compiler did was process your entire file, cataloging the errors.
Since none was fatal, do so successfully. When the compilation
it was able to
process was finished, the compiler put you back into the editing mode, at the
approximate location of the first error. The SHIFT-F3 command lets you move
from one error to the next in your file, making it easy to correct compiler
errors.
This program provides some moderately interesting trivia. Let's change
20 Using QuickC
d
error CZB65: (1 of 3)
'SPEDJIESS' undefined
:
Figure 2-2. Work file after compiler errors have been detected
the program once again to clean up the output somewhat. You'll format the
output, so that the numbers are displayed only to two decimal places, using
QuickC's Search facility.
Once you've got the first.c file open, press ALT-S, immediately followed by
C. Do not press RETURN.
You will get the dialog box shown in Figure 2-3:
In the top box, labeled Find What, type %lf. Then type ALT-T to move the
cursor to the Cliange To box. In this box, type %.21f. Then press RETURN.
Because the default setting is to ask you to verify each change, QuickC
will highlight each occurrence of %lf in your file. Press RETURN to accept the
default; that is, to verify that each occurrence is to be changed.
A Quick Look at QuickC 21
nainO
{
22 Using QuickC
statements (see Chapter 4), you are providing information for the compiler to
substitute when it creates your program.
Thus, when the compiler sees DIST in your program, it substitutes
93000000.0; when it sees DIST_UN1T, the compiler substitutes "miles" and
so forth. The second line beginning with printf actually displays the following:
This becomes "Average human walking speed: 3.50 mph" when the substitu-
tions are made. So, all you've done is provided canned phrases for the program
to display.
Two definitions are worth looking at more closely. The MAGIC NR in
this case represents the number of hours in a yean The material between /* and
*/ iscomment that indicates this fact to a reader of the source code.
a
The FORMULA is the expression used to compute the amount of time it
would take a human to walk to the sun. Dividing DIST by SPEED
(93000000.0 / 3.5) gives you the number of hours this walk would take.
Dividing this result by MAGIC NR (8766) tells you how many years these
hours represent. The compiler substitutes exactly what you have specified
when it encounters FORMULA in your program, then substitutes the values
and messages for DIST, SPEED, and MAGIC_NR. What is finally displayed
is the result of 93000000.0 / 3.5 / 8766.
• 93000000.0 to 35000000.0
• "sun" to "mars"
If you make the changes directly in your program file, you'll lose your
original example about walking to the sun. Instead of writing it directly into
first. c, write this information to a separate file. In this way, you'll be able to
change the program to enable you to create as many distance files as you wish.
To accomplish this, use the following strategy:
1 Copy the definition lines from first.c to another file, mars.h, which will
contain the new set of information. This is the file you will modify.
2. Copy these same lines to a file, sun.h, which will contain the informa-
tion from the original example.
To do this, start QuickC with first.c as your work file. If you aren't already
working on this file, type
qc first
To move something from one place to another, you first need to mark the
text you want to move. To do so, keep pressing SHlFT-DOWN ARROW until all
the definition lines have been highlighted. When all these lines have been
marked, press ALT-E (no RETURN ) to get to the Edit menu. During the
following session, whenever you are asked whether to save a file before moving
4. Once inside mars.h, press ALT-E P to paste the marked lines into
this file.
24 Using QuickC
7. Once inside sun.h, press ALT-E P to paste the marked Hnes into this file
as well.
10. Use the SHIFT-DOWN ARROW keys to mark the definition lines again.
1 1 When these are all marked, press ALT-E T to cut these lines from first.c.
After doing all this, you'll have three files instead of your original one:
first.c, mars.h, and sun.h.
Before leaving first.c again, add the following line at the top of the file.
tlnclude "mars.h"
Now go back to the mars.h file to make the required changes in that file. To do
this, press ALT-F O and enter mars.h in the resulting text box.
To change 93000000.0 to 35000000.0, press CTRL-RIGHT ARROW twice.
The cursor should then be on the 9 in 93000000.0. Press the DEL key twice, to
delete the 93. Then type 35 to insert these two digits.
To change "sun" to "mars" use the Search menu again. Type CTRL-S C to
change text. Type sun (no quotes) as the Find What text. Then press ALT-T to
move to the Change To box. Type mars (again, no quotes) here, then press
RETURN. You can use the same method to change 3.5 to 124 and "human" to .
Then press ALT-F F to reopen the last file, which was first.c. Once back in
this file, press ALT-R R to Restart the compiler. This time the program will
To have the program display information about walking to the sun again,
just change the file name on the first line of first.c to sun.h from mars.h. (The .h
extension stands for header file. Chapter 4 includes more information about
header files.)
; ; ;;; ;
Here's one more sample file you can one provides the informa-
use. This
tion needed to determine how long it would take sound to travel from New
York City to Peking, China. Use the commands you've learned so far to create
this file.
If you find program amusing, you can use the following listing to
this
build more files. The next program (called buiider.c) will create a file with the
name you specify, and will write the values you enter to this file. Once builder
has written your file, you can make first use this file by substituting the new
file's name for sun.h at the beginning of first. ( Leave the quotes in this case.) To
compile and run first after making these changes, just press ALT-R RETURN.
include <8tdio.h>
include <string.h>
include <stdlib.h>
define MAX.STR 80
gets ( str)
fprintf ( fptr, "define Xs \"y.s\"\n" , message, str);
>
^
26 Using QuickC
A Quick Look at QuickC 27
You should now have a file called builder.exe on your disk. To run this
program, just type builder at the DOS prompt, and answer the questions the
program asks you. Be forewarned that the program does not check whether
you already have a file with the name you specify. If you give the name of an
existing file, the program will create a new file with that name and your
original file will be lost. So be careful.
Review
Let's summarize the things you've done so far, and the commands to accom-
plish these things.
• Editing an existing source file: Once you've created a file, you can work
with it in QuickC. To specify the file, you can either type the file name
on the command line when you call QuickC (for example, qc first), or
you can start QuickC, then select Open on the File menu (ALT-F O), and
type the name of the file you want to open in the resulting dialog box.
select X for an Exe output file, then press RETURN to accept the default
action of building the program (that is, compiling and linking it, in
this case).
28 Using QuickC
• Copying text into a clipboard: Use the Copy option in the Edit menu
(ALT-E C).
• Cutting text from a file: Use the Cut option in the Edit menu (ALT-ET).
• Pasting the contents of the clipboard in another file or at another
location in the same file: Use the Paste option in the Edit menu
(ALT-E P).
Often, you'll want to know what's happening while your program is executing.
This information can be very useful if your program isn't doing what it's
supposed to be doing. The information can also help you better understand the
behavior of an algorithm (which is a well-defined solution process) in your
program.
Debuggers are programs that let you execute a program step by step, and
at various points in the program. By deter-
check the values of key variables
mining where your program "went wrong," you should be able to find a way of
fixing it. in this section, you'll learn how to use QuickC's built-in debugger to
look inside your program while it's executing.
We won't try to fix program bugs, however. Rather, we'll use the debugger
to see how a particular algorithm works as it tries to reach a certain value. The
program applies the algorithm over and over, until a satisfactory solution is
found. You can watch how values change as the algorithm converges on the
"correct" answer.
The algorithm we'll use is a simple one, involving only whole numbers.
Ironically, it turns out to be an unanswered question whether the algorithm
will eventually stop for any starting value. The algorithm is often known as the
; ; ; ; ; ;
2. If A= 1, stop.
The question is whether the algorithm will always end at I for any starting
value. (It's known that the algorithm stops for all values less than 536870912,
or 2".)
The following program implements this algorithm, and also computes
and displays some information about the solution process. In particular, the
program keeps track of the number of times val, the current number, is odd
and even.
#include <stdio.h>
#include <stdlib.h>
#dcline MAX.STR 80
main ()
{
long val, count = 0, even = 0,
char str [ MAX.STR]
count++;
if ( val " 1)
odd++
else il ( !(val X 2)) /* il val is even */
<
even++
val /- 2;
>
else /* if val is odd. but not 1 */
<
odd++;
val -3 * val + 1
printf ( "val Xld; odd - » Xld; even - '/,ld\n" .val odd, even)
, ;
if ( ! (count X 23))
<
printf ( "Press a key to go on.");
getch ():
printf ( "\n");
;
30 Using QuickC
}
}
while ( val !» 1)
printf ( "Alter Xld iterations, result - l.\n". count);
printf ( "# even values - Xld; # odd values = XldAn", even, odd);
The program, which we'll call algor.c, does not show you the intermediate
values created while applying the algorithm. You could get this information by
adding the appropriate instructions to the program. Or you can use the
debugger to peek at the values of specific variables as the program is running.
We'll use the debugger to watch the program execute.
algor.c is your current work file and you are ready to compile.
2. In the dialog box you get, make sure the Debug option is marked with
anX. If it is, just press RETURN. If the Debug option does not have an
X, type D to set this option. Then press RETURN.
The compiler will compile your program to memory, with the "hooks" needed
for the debugger to tell you what you want to know. After this is done, you'll be
back in your program file.
To set things for the debugger, you'll want to do two things: specify the
variables you want to watch, and set the breakpoints at which you want to see
the values of these variables.
In this case, let's set three watch variables, val, even, and odd. To look at
these variables, we'll set a breakpoint at the following line in the algor
program:
printf ( "val - Xld; odd - Xld; even - %ld\n", val, odd, even);
A Quick Look at QuickC
3. Type ALT-D RETURN to get the Add Watch variable dialog box.
4. Type val; even; odd; in the box to specify that you want three watch
variables. The semicolons are necessary. After you type this, but
before you press RETURN, the dialog box should look like Figure 2-4.
5. Move your cursor to the printf() statement at which you want to set
your breakpoint.
of trials before val reaches you may want to try one or more of the following
1 ,
starting values: 5,8, 10, 16, 20, or 21. (If you get caught in a run that is taking
too long for you, you can use the file menu to get out. Type ALT-F X.)
nain
Ion
cha
; ; ;
euen++;
oal /= 2;
>
odd++;
ual = 3 * ual i;
}
printf ( "ual - /-W odd = xld: euen = xldVi uali oddi euen)
if ( '(count /.
23))
<
double data [ MAX ENTRY] - { 7.99e-26. 6e-9. 3.15, 67.3. 1.38e5, 3.51e5,
7.6e22, 1.9e27, 2.2e41. leBl, 1900. 0>;
>
it ( argc -- 2)
seed • atoi ( argv [ 1] )
else
{
; ; ; ;
; ; ; . ;; ; ; ;
34 Using QuickC
gets ( str)
seed = atoi ( str)
>
seed •/,= CYCLE;
do
ch » getche ;
}
while ( ch != '
!
')
else
printf ( "It would take '/.
.21g '/.ss to make 1 '/,s.\n", num, strl, str2)
Enter the program, and give it a name. We'll call it game.c here. When you
compile the program within QuickC — that is, when you use the ALT-R
RETURN program prompts you for a seed. After you provide this,
option, the
the program generates random facts, based on the information available to it.
The program keeps generating facts as long as you press any key other than the
exclamation point ( !), which ends the program.
The following listing was produced using a seed of 235:
A wasp is 6e-00S kg
A water molecule is 8e-026 kg
Press ! to stop.
A wasp Is 6e-000 kg
A person is 67 kg
Press ! to stop.
Press ! to stop.
Press I to stop. I
This program is actually different from the previous ones you've built. To
see the difference most clearly, compile the program to an executable file. To
do this,
2. Use the arrow keys to set Warning Levels to 1 , if it's not already at that
value.
3. Type X to compile to an Exe output file, if this isn't already the default
output file.
Once you have an executable version of the program, you can simply type
game at the DOS command line, and the program will start executing. Try
this. You'll program asks you for a seed, just
find that the as it did earlier.
game 271
On this run, the program does not ask you for a seed, because the program
takes the 271 as the seed. The effect is the same as if you had typed 271 in
QuickC with the name of the file you want to edit. The ability to specify certain
information when calling a program can be very useful, as you'll see later.
You can accomplish the same thing even if you just compile the program
to memory. A Run-Time option in the Run menu lets you specify the informa-
::
; ); ; ; ;; ;; ;; ;; ; : ;
36 Using QuickC
#include <8tdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX.STR 80
#define MAX.ENTRY 100
if ( argc == 2)
fp = f open ( argv [ 1] , "w")
else
<
printf ( "Complete file name? ")
gets ( str)
fp « fopen ( str, "w");
}
if ( fp != NULL)
•t
fclose ( fp)
}
else
printf { "File not opened. \n");
else
fprintf ( fptr. " \"%s\". \n ". str);
}
useful as possible. Recall that to do this, you just need to select the Exe Output
option in the Compile options under the Run menu. Let's use makefact.c and
makefact.exe for this program's source and executable files, respectively.
With this program, you can specify a file name when you invoke the
program. For example, if you typed
makefact myfacts.h
and entered object names and values, the program would write this informa-
tion to a file named myfacts.h. To use this collection of facts, you would need
to cut the top part of the game.c file as you did earlier with first.c. That is, you
would need to cut everything beginning with the line
through the end of the values entered for data[ MAX_ ENTRY]. You would
also need to add the following line near the beginning of game, probably right
after the definition line for CYCLE:
This program wrote the following listing into a file called area.h.
#deline MAX.ENTRY 13
#deline UNIT "square km"
These data can be used by the game program if you include the following
line between the definition line for CYCLE and the first line of the my_rand( )
function:
^include "area.h"
As you become more familiar with C, you can come back and modify
these programs if you wish. If you have become more comfortable moving
around in the QuickC environment as a result of working with these pro-
grams, then they have served their purpose. If you also found them interesting
to play with or if you picked up some information you didn't know before, all
the better.
QuickC Commands
In the remainder of this chapter, we'll lookmore closely at the QuickC features
and commands you're most likely to use in ordinary applications. The QuickC
Programmer's Guide provides additional information about many of these
commands and also describes features and commands not discussed here. This
discussion will be largely a catalog of commands and features. Our main intent
is to gather in one place brief summaries of the most common QuickC features
and commands, although we will suggest things for you to try and you are
strongly encouraged to experiment. This section lays.the foundation for your
use of QuickC throughout this book. To get the most from this chapter and
from QuickC, try the things suggested and, by all means, experiment.
A Quick Look at QuickC 39
On-Line Help
The first command to learn is for the on-line help available in the QuickC
environment, if you're ever uncertain of the command to do something, press
function key Fi. This gives you a dialog box, the first of several help screens,
that contains summaries of the major commands. The box also contains
several choices for action. The default is to display the next screen of help
information, which is a summary of C operator precedence. Press P to see the
previous help screen, and also to make backwards paging the default action.
If you want a summary of C keywords or of QuickC library functions,
press K while in the Help dialog box. Use the arrow keys to select the label that
best describes the general topic about which you want information. Press
RETURN to get a complete list of keywords for which help is available in that
topic.
When you start QuickC, the program immediately loads a work file. This will
be either a file you have specified on the command line, as you saw earlier in
this chapter, or a named untitled.c.
file
As soon as QuickC has loaded your file into memory, you can start
inserting text into the file. Thus, to create a file, just start typing. To do other
things, such as move around in the text or switch to an overwrite mode from
the default insert mode, you need to use the QuickC commands described in
this section.
mode (the default) and overtype mode. In insert mode, text is inserted at the
current cursor position. Any text at or to the right of the cursor is moved to the
right. Thus, if the cursor is sitting on top of a character (let's say, a in the
40 Using QuickC
following listing), when you insert a different character (let's say z), the original
character (a) will not be lost. Rather, it and all subsequent characters on the
line will be moved to the right.
The following listing shows the result of inserting z into the word maybe
on the first line when the cursor is on the a in that word. The second line in the
listing shows the result.
maybe not
mzaybe not
In overtype mode, on the other hand, any character at the current cursor
position is overwritten when you type a new character. Surrounding charac-
ters are not affected. Essentially, typing in overtype mode amounts to crossing
out text and substituting new text. The cursor changes from a small blinking
line to a large block when you go into overtype mode.
The following two lines show the result of typing a z when the cursor is on
the a in the string.
maybe not
mzybe not
When you first start QuickC, the editor is in insert mode. To switch to
overtype mode, press the INS key or press c TRL-V. The editor toggles between
these two modes, so to return to insert mode, just press ins again. Remember,
the cursor is larger when you are in overtype mode, so you can always tell what
mode you Ye in.
Moving in Your File To move around in your file, you need to decide how
far to move, and also the direction in which to move. The QuickC editor has a
rich set of movement commands, so it may take a while to become familiar
with all of them. Many of the commands are the same as those in WordStar, so
if you've used that program you'll find the task somewhat easier.
When moving in your file, you can either move the cursor through the file
or you can scroll the file past the cursor. We'll look at the cursor movement
commands first.
The smallest vertical movement you can make is up or down one line, and
the smallest horizontal movement is left or right one character. The QuickC
editor has two ways of accomplishing each of these four moves. The most
intuitive way is with the arrow keys. The UP ARROW key moves you up one line.
A Quick Look at QuickC 41
the DOWN ARROW key moves you down one line, and the LEFT ARROW and
RIGHT ARROW keys move you left and right one character, respectively.
You can also use commands involving the CTRL key to accomplish these
moves. The four keys, S, E, D, and X, form a cross on the keyboard. Each of
these keys, in conjunction with the CTRL key, moves you in one of the four
directions. Thus, CTRL-E moves you up one line, and its counterpart CTRL-X
moves you down one hne. Similarly, the other pair of keys, CTRL-S and CTRL-D
move you left and right one character, respectively. Table 2-1 summarizes these
movement commands.
All of these moves are nondestructive. That is, the commands simply
move the cursor; they do not delete or insert any text, regardless of the mode
you're in.
You can also make larger moves. To move to the right or left one word,
use the RIGHT and LEFT ARROW keys together with the CTRL key. That is, to
move one word to the right, press CTRL-RIGHT ARROW; to move one word to
the left, press CTRL-LEFT ARROW. You can also use CTRL-F and CTRL-A to
move right and left by one word, respectively. (It may help to remember the
latter two commands if you think of them as more extreme versions of the
single character moves, CTRL-D and CTRL-S, based on the relative locations of
the F and A keys on the keyboard.) Table 2-2 summarizes the commands for
moving by words.
Key(s) Effect
UP ARROW
42 Using QuickC
Key(s) Effect
• "Special keys," such as the arrow keys, backspace, HOME, TAB, and
the function keys
To move on the entire screen, you need to modify the E and the X keys for
moving vertically. Thus, to move to the top of the screen, press CTRL-Q E. That
is, press CTRL-Q then the E key. The CTRL-Q sequence tells the editor to
Key(s) Effect
Key(s) Effect
Key(s) Effect
The QuickC compiler will keep trying to compile your file as long as
possible. Sometimes, the compiler will have to stop because of a fatal compiler
error, such as being unable to open a specified file. In other cases, the compiler
may be able to process your file completely — finding and cataloging errors,
but not producing a compiled file. In that case, the compiler will return you to
the editor at the approximate spot where the first error was found. The
compiler will give you a message telling you the nature of the error and also the
number of errors it encountered, as shown in Figure 2-2, earlier in this chapter.
You can move to errors that the QuickC compiler has found in your file, as
shown in Table 2-6. To move to the next error in your file, press SHIFT-F3, that
is, the SHIFT key and function key F3 simultaneously. To move to the previous
All the movement commands we've just summarized move the cursor
through the file. You can also move the file in relation to the cursor, however.
This is known as scrolling. You can scroll up and down one line at a time or one
screen at a time. To scroll up one line, press CTRL-W; to scroll down one hne,
press CTRL-Z. These two keys are right next to the E and X keys, respectively,
so you may find it helpful to use those keys for orientation in remembering the
scrolling commands.
Similarly, to scrollup one screen, or window, at a time, press CTRL-R; to
scroll down one screen at a time, press CTRL-C. These two keys are on the other
side of E and X, respectively. You can also use the special keys, PGUP and PGDN,
to scroll up and down one screen. Scrolling commands are summarized in
Table 2-7.
A Quick Look at QuickC 45
Key(s) Effect
Key(s) Effect
Marking and Moving Text You can easily move text from one part of your
file to another, or even to another file, simply by marking the section of text
you want to move and then specifying where you want to move it. You've
already used some of these commands in the first part of this chapter.
The first step in moving or deletmg a chunk of text is to mark it. Once you
mark the text, you can use the Edit menu to place the text on the Clipboard, a
buffer where you can temporarily place text in order to move it to a new
location, without necessarily deleting your original text. All commands for
marking text involve the SHIFT key.
As with cursor movement, you can specify text units of different sizes to
mark. The smallest unit is a single character, and the largest is an entire file. To
mark characters to the left of the cursor, press SHIFT-LEFT ARROW once for
46 Using QuickC
each character you want to mark. This command does not mark the character
(if any) at the current cursor position. To mark characters to the right of the
current cursor position, press SHIFT-RIGHT ARROW once for each character
you want to mark. This command does mark the character at the current
cursor position.
To mark individual words, you need to use both SHIFT and CTRL as
modifiers. That is, you will need to press three keys simultaneously: SHIFT,
CTRL and an arrow key. To mark words to the left of the current cursor
position, press SHIFT-CTRL-LEFT ARROW once for each word you want to
mark. Notice that this command will also mark portions of words. For
example, you have the cursor on the r in careful, and you press SHlFT-CTRL-
if
LEFT ARROW, the first "word" to be marked will be the ca to the left of the
current cursor position. Thus, this command will mark words or partial words
to the left of the current cursor position; the command does not mark the
character (if any) at the current cursor position. To mark words to the right of
the current cursor position, type SHlFT-CTRL-RIGHT ARROW. This command
also marks partial words, and marks the character (if any) at the current
it also
cursor position. Thus, if your cursor is on the r in careful again, pressing
SHlFT-CTRL-RlGHT ARROW would mark the reful from the cursor to the end of
the word.
To mark an entire line of text, use the SHIFT key in conjunction with the
UP ARROW and down arrow keys. To mark lines above the current one, press
SHIFT-UP ARROW once for each line you want to mark. Notice that this
command does not mark the current line. To mark lines below the current one,
press SHIFT-DOWN ARROW once for each line you want to mark. This com-
mand does mark the current line. Figure 2-6 shows an example of marked text
in a file.
To mark from your current cursor position to the beginning of the file,
press SHIFT-CTRL-PGUP to mark from your current position to the end of the
;
when you have the Edit menu selected, or by using the down arrow key to
highlight the Copy option and then pressing RETURN.
A Quick Look at QuickC 47
double data c nAXJNTRYJ = < 7,99e-26, 5e-9) 3,15) 67,3) l,38e5, 3,51e5)
7,5e22, l,9e27) 2,2e41, leSl, 1908, B>;
Once text has been copied to the Clipboard, you can paste the text into
other files or at other locations in the current file. To paste the text at a
different location in the same file.
3. Select the Paste option by pressing P. You can also use the arrow keys
to highlight the Paste option, and then press RETURN to select the
option.
48 Using QuickC
Key(s) Effect
You can also copy from the Clipboard to a different file, as you did earlier.
(You did try that, didn't you?) Recall that you first need to open the target file:
1. Press ALT-F O to select the File menu and the Open option in that
menu.
3. Once in the new file, move the cursor to the desired location.
Actually, if you have just copied the text to the Clipboard, you will be able
to carry out steps 4 and 5 (that is, call Edit menu and select Paste option) much
more easily. You can simply move the cursor to the desired location and press
SHIFT-INS while editing. This will paste the text from the Clipboard to the file
To see what's going on here, look at the Edit menu. The menu includes six
options. Some of these options have single characters underlined. If you type
1. Move the cursor to the location in the current file where you want to
add the material.
3. In the resulting dialog box, specify the file whose contents you want to
merge with your current file.
4. Press RETURN.
Once you've successfully pasted your text, you'll probably want to delete
the text from its original location. To do this, mark the text you want to delete,
then press DEL. This deletes the text without copying it to the Clipboard.
Pressing DEL while editing your file is equivalent to pressing ALT-E E, to call
the Edit menu and select the Clear option. (Notice that in this case the e in
menu. This essentially combines the copying before pasting and the subse-
quent deletion. you get into the habit of using this lazy way to move text you
If
will almost certainly regret it at some point when you accidentally lose
something vital.
50 Using QuickC
Table 2-9 summarizes the operations that are possible when you have
selected text using the commands listed in Table 2-8. You can also use the
commands shown in Tables 2-10 through 2-12 to insert and delete text while
editing — that is, without going to the Edit menu. You don't need to select the
text before invoking the commands listed To delete the charac-
in those tables.
ter at the current cursor position, use the DEL key or press CTRL-G. The DEL
command is also used to delete marked text, as you've just seen. The editor will
know what to do from the context. To delete the character to the left of the
current cursor position, use the BACKSPACE key or press CTRL-H. When
editing, press RETURN any time you want to end a line or insert a blank line.
Key(s) Effect
Key(s) Effect
Key(s) Effect
Key(s) Effect
To delete a word (actually, the part of the word to the right of the current
cursor position, including the current character), press CTRL-T. If you decide
that this deletionwas not a good idea, immediately press ALT-BACKSPACE or
select the Edit menu and press RETURN. These commands select the Undo
option on the Edit menu, which restores a line to its status before your last
action. Undo works only for the current line, and only if you have not yet
moved the cursor from the current line. Table 2-11 summarizes these
commands.
You can delete an entire line or the portion of the line to the right of the
cursor. To delete the entire line, press CTRL-Y. To delete just to the end of the
line, press CTRL-Q Y. Line here refers to a line on the screen, not to a sentence.
You can undo either of these actions by pressing SHlFT-lNS. This command
will restore only the last line deleted.
If you want to look at your file but don't want to make any changes,
especially inadvertent ones, you can make your work file read-only. This
prevents accidental changes. Press ALT-E R to set this option. Pressing the
same keys again turns the option off When your file is read-only, an R appears
.
52 Using QuickC
selected lines will jump to the next tabspace in the appropriate direction.
such locations,
Key(s) Effect
Key(s) Effect
You can get into real difficulties if you mismatch braces or parentheses. To help
minimize the chance of this happening, QuickC lets you find matching braces
or parentheses. To find the left brace that matches a right brace you recently
typed,
1 Move the cursor to the right brace whose counterpart you want to find.
2. Press CTRL-} to find the matching left brace. (To find a right brace
matching a left brace, you would type CTRL-} after moving the cursor
to the appropriate location.)
File Manipulation with the File Menu The File menu provides the com-
mands for opening and saving files. You've already used several of these
commands, so the discussion here will be brief
Press ALT-F N to create a new file. You will be asked to provide a name for
this file. QuickC checks for duplicate file names, and will allow you to choose
another name if you are accidentally about to overwrite an existing file with
the same name as the one you want to create.
The Open and Open Last File options let you open existing files for
editing. The Open option (ALT-F O) lets you specify the file to edit. The Open
Last File option (F2 or ALT-F F) opens the file you had open just before
switching to your current work file. For example, suppose you edited game.c
54 Using QuickC
Key(s) Effect
then used the Open option to start editing another file, area.h. If you press F2 at
that point, QuickC will automatically return you to game.c, since that was the
last file open before area.h.
Finally, you can write a file to disk using the Save option. If the file has no
official name (that named untitled.c), QuickC asks you to provide a
is, if it is
name. If the file exists, QuickC will prompt you to verify that you really intend
to overwrite the existing version of the file.
The Save As option you specify the name under which to store the
lets
current working file. This can be handy if you need to revise a program
temporarily. For example, suppose you want to modify game.c in some way.
Knowing that the current version of the program works fairly well, you want to
keep that source code from accidental destruction. After making many
changes to game.c, you are reluctant to overwrite the file since your new
version may have unanticipated bugs, in which case you will need to go back to
the original.
To handle this situation, you can create a new file "on the way out." You
can tell QuickC to save the current file under a new name, let's say game2.c.
The original version of the work file will be unchanged, and gamel.c will
contain the altered version of game.c. To accomplish this, press ALT-F A, then
specify a file name and press RETURN.
Table 2-16 summarizes the first set of File menu commands. We'll discuss
the File menu options relating to program lists later in this chapter.
The Search Menu The Search Menu lets you move through and modify
your file based on textual rather than physical criteria. The options you've seen
so far have involved physical units, such as current character, word, or line.
A Quick Look at QuickC 55
Key(s)
ALT-F
56 Using QuickC
Key(s) Effect
Find Options When finding and replacing text, you can put constraints on
the actions or make your commands even more summarized in general, as
Table 2-18. The dialog boxes for the Find and Change options allow you to
specify whether QuickC is to consider something found only if it is surrounded
by space or punctuation — that is, if the text is a word, rather than simply part
of a word. To make this stipulation, press ALT-w. This command toggles, so
you can cancel the restriction by pressing ALT-w again.
Similarly, you can specify that the case of the characters in the text found
should match the case in the text you're looking for. For example, if you ask
QuickC to find MAX— ENTRY, and press CTRL-M to select the Match
Upper/ Lowercase option, QuickC will not find Max Entry.
You can also generalize the text QuickC is to find by creating a regular
expression. Regular expressions consist of text that includes certain characters
that are given particular interpretations.To get QuickC to accept your Find
What text as a regular expression (as opposed to a literal expression), you must
select the Regular Expression option, by pressing ALT-R in the dialog box. The
.*$- \[][-][^]
You can put a period ( ) in the Find What text you specify. This tells
.
QuickC to accept any single character in that position. For example, suppose
A Quick Look at QuickC 57
Key(s) Effect
QuickC would match any of the following words if they appeared in your file:
doer
door
dour
The search would not match dollar, however, since the period can represent
only a single character. Characters that are used to stand for other characters
in this way are called wild cards.
You can also tell QuickC to accept one or more consecutive occurrences
of a particular character. To do this, put an asterisk (*) immediately after the
character you want to handle in this way. For example, specifying
f\ir*y
file. For example, suppose you specify the following as your Find What text:
are you?$
QuickC would match the first of the following lines, but not the second.
;
58 Using QuickC
Similarly, you can begin your text with a caret (" ) to tell QuickC to search
only for text at the start of a line. For example.
finds the first, but not the second of the following lines.
void beep_user ()
Sometimes you may want to search for the occurrence of any of a number
of characters. For example, suppose you wanted to search for the first occur-
rence of any digit. Both of the following Find What strings would specify this:
If you put square brackets around text in the Find What string, QuickC
will search for the first occurrence of any of the characters specified between
the brackets. The first string explicitly lists each of the digits you are seeking.
The second string uses one of the two special characters allowed within these
brackets.
If you put a hyphen (-) between two characters in this context, QuickC
will search for any occurrence of an ASCII character with a value between the
values of the two characters surrounding the hyphen. It turns out that the
digits are represented as consecutive characters in the ASCII character set (see
Appendix A). Therefore, the second Find What string simply says to match
any ASCII character between and 9 inclusive — that is, between ASCII
values 48 and 57, inclusive.
The other special character is a caret ( ) again. In this context, the caret
'^
must be the first character after the left bracket. The caret tells QuickC to
match any characters except those within the brackets. Thus, the following
A Quick Look at QuickC 59
[0123456788]
[-0-9]
literally, rather than as a special character. The following three strings would
tell QuickC to search for a dollar sign, a period, and a backslash, respectively:
Key(s) Effect
Change Options There are also several options available if your are
replacing text. By default, QuickC verifies each replacement before making it.
This means that QuickC stops at each occurrence of the string to be replaced,
and waits for you to press RETURN to verify that the change should be made.
When QuickC finds the text, it pauses and you are able to specify whether
to change that occurrence or to skip it. To change it, simply press RETURN; to
skip it, press S first. This changes the default setting to Skip, rather than
Change. Now if you want to skip something you must press RETURN. If you
want to change something, press C. This restores Change as the default action.
Notice that these actions merely change the default; they do not affect the
current text occurrence. To do that, you need to press RETURN to verify the
appropriate action. In such a context, pressing RETURN accepts the current
setting, whatever it is at the time.
you do not want QuickC to verify each change, you can press ALT-C in
If
the Change dialog box. This tells QuickC to change all occurrences of the
specified text, without verifying. You had better be sure of this one, because
QuickC makes the changes very quickly.
Table 2-20 summarizes the options you have when changing text.
Two other options on the Search menu are commands to find the next
and the previous error found by the compiler. You learned how to do this
earlier.Such jumps can be very handy if you want to quickly correct the errors
the compiler has found. You can compile your entire file, let the compiler find
all the things to correct, work through the file making the required changes,
Key(s) Effect
put you back in the You can make the correction and tell
program source file.
Compiling a Program When you compile your program, you may want to
specify various things to the compiler. The Compile option on the Run menu
gives you access to a dialog box containing several possible instructions for the
compiler. Figure 2-7 shows the dialog box for the Compile option.
Key(s) Effect
Include:
Define:
II
Progran List: <none> Context: <Progran not riinning>
One thing you can tell the compiler is how broad a range of warnings it is
to give you during the compilation process. In the C world, compilers are able
to warn you about things in your program that are not syntax errors, but that
may affect program results, execution, or portability. Experiment with some
programs compiling them at different Warning Levels.
Level is completely silent. At this level, the compiler tells you only about
errors, not about questionable practices. Level 1 is moderate, and tells you
about such things as incorrect use of pointers. Level 2 is considerably noisier;
in particular, this level complains about possibly discrepant function declara-
tions and calls. Finally, Level 3 complains about the largest number of things.
A Quick Look at QuickC 63
QuickC environment, the Memory option is the appropriate one. This is also
the defauU option. With this option, no permanent compiled version of your
program is created. To select this option if a different one is checked, press M
while in the Compile option dialog box.
If you are building libraries, as described in Appendix B, you will want to
create an object (.obj) file. This is a compiled version of the file that can be
linked with other files when building programs or libraries. To select this type
of output file, press O in the dialog box. To build an executable version of the
program (an .exe file), as you did in the first part of the chapter, press X.
Finally, you can have QuickC check your program only for syntactic
errors, without running the program and without producing either an .exe or
an .obj file. Press Y for the Syntax Check option in the dialog box for this type
of compilation.
The remaining compiler options instruct the compiler to provide various
types of information for use when debugging or running your program. These
option settings toggle. That you select the option
is, if to turn the setting on,
you can turn it off by selecting the same option again.
The Debug option tells the compiler to prepare the information needed
for the debugger to let you step through your program, watching what lines are
executed and what happens to variable values as the program executes. Press
D to set this option.
The Pointer Check option tells the compiler to run portions of your
program during compilation, to make sure you are not using pointers in a way
that could destroy things you want to keep. To select this option, press P in the
dialog box.
Pointer checks are a very good idea when you are first learning C, because
64 Using QuickC
they help protect you against common and careless use of pointers.
errors
When you select this option, the resuhing program may run much more slowly
then otherwise, however To make the program faster, you can tell the compiler
do such checking only for certain pointers. See the Programmer's Guide for
information on how to do this.
The Stack Check option (S in the dialog box) provides another check on
your program's performance. In this case, the compiler tries to make sure that
there will be enough of the required (stack) memory available to load functions
and their variables when running the program. This check is also recom-
mended when you first start developing your program. As with the Pointer
Check option, you can tell do Stack Checks on portions of
the compiler to
your program. See the Programmer's Guide for details.
Microsoft's implementation of C provides the constructs and syntax
specified by the Draft Proposed ANSI Standards committee. However,
Microsoft has provided some language extensions in their C version 5.0 and
QuickC compilers. When writing QuickC or Microsoft C 5.0 programs, you
need to decide whether you will want to compile them under non-Microsoft
compilers. If so, you should not choose the Language Extensions option —
that is, the box for this option should be blank. If you decide to choose the
option, press L.
Finally, you can ask the compiler to generate the fastest code it can, at the
price of making the resulting program file somewhat larger. To select this
option, press Z.
As you'll find out in Chapter 4, C programs often use header files that
contain special definitions or other information. Ordinarily, QuickC has
certain default places where it searches for such files. Two places are the
current directory and the \include directory (if you installed QuickC with the
default directory structure).
You may want QuickC to search other directories as well when compiling
a particular program. In that case, write the paths to all these directories in the
Include text box. These paths should be separated by commas, and the names
must conform to DOS conventions. To get into the Include text box, press I if
you are in the dialog box for the Compile options. Press ALT-l if you are in the
Define text box.
The Define text box letsyou specify definitions that are to be used with
the current program file. Press F in the dialog box or ALT-F if you're in the
Include text box. This facility is useful if you want to compile your program
A Quick Look at QuickC 65
differently under different circumstances. You are unlikely to need this facility
very much when you first start programming in C. See the Programmer's
Guide for more details about the Define text box.
Once you've set everything the way you want it, you can tell QuickC what
to compile by selecting one of the boxes at the bottom of the Compile option
dialog box. The Build Program option compiles only those source files used in
the program that have been changed since the last compile, and links these
with the unchanged object files. Press RETURN to select this option.
To compile just the source file you're editing, press C, to select the
Compile File option. Finally, to compile all source files used in the program,
regardless of whether they have changed, press R to select the Rebuild All
option.
Table 2-22 summarizes the compiler options.
Key(s) Effect
QuickC debugger, you first need to compile your program with the Debug
Compile option on (with an X in the Debug option box), as you did earlier.
Key(s) Effect
Once this is done, you can specify the variables and expressions you want
to watch while running the program. To do so, select the Add Watch option in
the Debug menu (ALT-D RETURN), and write the variables or expressions you
want to watch into the text box that appears when you select this option. If you
want to watch more than one variable or expression, they must be separated by
semicolons.
You can ask to see the values of the watch variables in a variety of output
formats, including octal (base 8) and hexadecimal (base 16). These are de-
scribed in the Programmer's Guide, and Appendix C. They are similar to the
formats you can use to display information in C programs, as described in
Chapter 5.
You can delete all currently set watch variables by selecting the Delete All
Watch option in the Debug menu (press ALT-D L to do this). Or you can delete
the most recently specified watch variable by selecting the Delete Last Watch
option (press SHIFT-F2 or ALT-D E).
If you select the Trace option in the debugger, the program will execute
Key(s) Effect
For example, nice print is the name of a function in the game.c pro-
gram. This name occurs at several points in the file. Ifyou search for this name
using the Find option in the Search menu, the first occurrence will be near the
beginning of the function main( ), since this is the first time the phrase
nice print is used in the file.
Ifyou search for this same name using the Function option — that is, by
pressing ALT-S U — the cursor will be moved to the left brace that starts the
main body of the function, near the end of the game.c file. This is convenient
when you have large files, or when you are trying to find a function that is
Program Lists
Many C environments have utilities that will let you keep track of changes
made to different files that make up a larger program. This is important when
you have large programs that take very long to compile.
Instead of compiling the entire program each time, it is often possible to
recompile only those component files that have changed since the last time the
program was compiled. These new .obj (object) files can be linked with object
versions of the unchanged files that did not need to be recompiled.
\ program list specifies all the source files that are used in a program. You
can create a program list for a program by using the Set Program List option
on the File menu. Press alt-F S to get a dialog box that will let you specify the
files to be included in the program list. For each file, or program module, you
want to include in the program list, type the name and press RETU RN. If the file
is not yet on the list, QuickC adds it; if the name is already on the list, QuickC
removes it. That is, the Add/ Remove option in the dialog box toggles. When
you have finished entering all the file names, press ALT-S to save the file.
QuickC will create a file with the same name as your program, but with
the extension .mak. This file will contain the information the compiler needs to
determine what files to recompile.
To discard a program list associated with a program, use the Clear option
in the File menu after you've called up the program source file. To edit an
Key(s) Effect
You probably won't need program lists when you first start programming
in C, but you'll find this facility extremely useful when you start writing long
programs. See the QuickC Programmer's Guide for more details about pro-
gram lists.
Table 2-26 summarizes the File menu options for working with program
lists.
Other Features
QuickC includes two additional features that can be very helpful on occasion.
Sometimes it can be useful to have a printed version of your You can
file.
print your file, or portions of it, while in QuickC. To do this, press ALT-F P to
call up a dialog box for printing. Specify whether you want to print the entire
file or just the text you have marked. The Print option sends your file to the
printer attached to LPTl.
QuickC also lets you go back, temporarily, to the DOS command line.
This command suspends (but saves) your current session with QuickC, and
temporarily transfers control back to DOS. QuickC remains in memory,
however, because you will return to the QuickC environment. To specify this
command, select the DOS Shell option on the File menu (press ALT-F D).
Key(s) Effect
Once on the DOS command line, you can run other programs or do
whatever you need, within Hmits. Be very careful when deleting files during
such a side do not delete any .mak files, source files, or files
trip. In particular,
included in your current program list. See the Programmer's Guide for more
information. When you're finished with DOS, type
Key(s) Effect
Summary
In this chapter, you've had an informal introduction to QuickC and how to use
it; you've also read a brief discussion of the most common commands and
features. There's much more to learn about QuickC, but there's little point in
doing so more about C, which is what you're about to do.
until you've learned
The following chapters will tell you about C and will give you an oppor-
tunity to consolidate your understanding of the features and capabilities of
QuickC so that you can take advantage of this powerful environment from the
start of your C programming career. Happy learning!
Simple Types
and Operators
In this chapter, we'll look at some of the data types available in C, and how to
define variables in a program. We'll look at some of the operators C provides
for working with data. In the process of exploring these topics, you'll learn the
rules and conventions for naming things in C. You'll see that there's more than
one way to write a number or a letter.
Before jumping into these topics, let's look quickly at some of the general
characteristics and components of a C program —
the program itself, func-
tions, and statements. You'll find enough information about these elements to
provide a foundation for the other constructs in this chapter.
73
;
74 Using QuickC
C Program Structure
examples. This time, however, calls are to functions already defined in the
program.
Note that parentheses are used when main( ) calls the functions, even
though there are no arguments (values or variables) to be passed to the
functions. In C, these parentheses are necessary when calling any function.
The two "message" functions, first message( ) and second niessage( ),
each call printf( ). This time, calls to printf( ) have only one argument. Every-
Simple Types and Operators 75
Comments
Program comments make it possible to include remarks and reminders within
program listings. This, in turn, makes it easier for other people to read your
code, and help you to understand your own code six months after you wrote it.
Comment your code thoroughly. The next time you need the code you will
thank yourself
Program comments in C begin with /* and end with */. The compiler
ignores all material between the comment start characters *) and the com- (
ment finish characters (*/). Comments may occupy multiple lines, but you
may not nest comments, as shown in this (incorrect) example;
because the compiler takes the first * / encountered as the end of the comment,
leaving "are not allowed.*/" as uncompilable text after the comment.
you may lose lots of hair, sleep, or both, trying to find your programming
error.
76 Using QuickC
the matching right brace. Figure 3-1 shows a function's major components.
The main( ) function is special in C. A C program starts executing in
niain( and the function's closing brace marks the end of the program. All
),
other function calls are made (directly or indirectly) from main( ). Put simply,
main( ) is the program.
Although a program begins and ends with niain( ), you can place this
function anywhere in your program file. You may not have more than one
main( ) in a program. If you do, you'd have two programs.
^REMEMBER C programs must have the main( ) function since the pro-
gram starts and finishes executing in this function.
function name, putting any arguments to the function within the parentheses.
(An argument is information passed to a function; the function is generally
expected to use or modify the information. Functions may have more than one
argument.)
The following is a call to printf( ) with one argument:
second.nessage {)
} -^ Closing brace for function
This function call writes the sentence within the double quotes to the screen.
Writing to the screen can be a simple task or it can involve various
substitutions by printf( ). The string argument to printf( ) may contain special
character sequences, beginning with %, that serve as placeholders for other
information. When printf( ) does its work, it replaces each placeholder by a
value of a specific type. The following program illustrates placeholders and
some other features of printf().
main ()
<
int il, i2; /* define two variables */
il - 37;
12 = 73;
printf ( "one string, \n but written on two lines\n\n");
printf ( "one placeholder, for a string: y,B\n\n" PF.EXAMPLE) ,
printf ( "two placeholders, both for integers: y,d and '/,d\n\n" il, ,
PF.EXAMPLE2, il);
printf ( "four integer placeholders, note spacing: %d '/,d'/,d '/.dXnNn"
il, 12. 11. 12);
Again, this program has many aspects in common with the examples
you've seen. There is which contains the values substituted for
a header file,
that it can be called with a varying number of arguments. The first call
lines. This is because the \n (for newline) escape code was placed in the middle
of the string. (The two newhne commands at the end of the string terminate the
second line and force a blank line in the output.) Calling printf() with just a
string is probably the simplest call you will ever make to the function.
Each of the next two calls has two arguments. In both cases, the first
argument has a placeholder in it (%s and %d, respectively). The printf()
function replaces each placeholder with the information contained in the
second argument. In one case, the second argument has a string value; in the
other, the argument is an integer variable. In each case, the placeholders (%s
and %d) will be replaced by the current values of the second argument.
The next two calls have thiee arguments each: a string argument and two
additional arguments. In each case, there are two placeholders in the string
argument (which is always the first one). The number of placeholders in the
first argument must always equal the number of additional arguments to
printf( ).
Look at the output for these calls, particularly the spacing. In the call that
writes a string and an integer, notice that the comma comes immediately after
the substituted string, without space. Also, there is no space between %s and
the comma in the string argument to printf( ) for that example.
Finally, the last call to printf( ) has five arguments. This statement is
intended to clarify some points about spacing in the output. Notice in the
output that the middle two numbers are "fused. "There is no space between the
73 and the 37. Now look at the function call that generated this output. There is
no space between the middle two placeholders; hence, there is no space
between the numbers substituted for them in the output.
• Portions of the string argument that begin with % are placeholders that
specify where particular values are to be placed, and the type (integer,
Escape Codes
You have already seen the use of the newline character, \n, to move to the next
line in the output. Newline is an example of an escape code or escape sequence.
You may put escape codes anywhere in your string argument to printf( ).
Escape codes consist of two characters: a backslash ( \) followed immediately
by a single character. For example:
\n (newline)
\t (tab)
\b (backspace)
\\ (backslash character)
are all escape sequences. Escape codes are two characters long (with one group
of exceptions, which we'll see later). Any characters after the escape code are
considered part of the regular string, rather than of the escape sequence:
Because the escape code for a new line ended with \n, the character a was
treated as the next character in the string.
Table 3-1 summarizes some of the more common escape sequences.
Given the information in this table, how would you write a single quote ( ')? Try
it. What do you suppose will happen if you write \r, followed immediately by
\b? Also, can you predict the output from the following printf( ) call?
80 Using QuickC
Statements
Simple Statements
The following are all simple statements in C:
curr_int = 7
return ( 8.5)
continue;
Compound Statements
You can "collect your thoughts," by grouping several statements together and
treating them as a single, extended statement, known as a compound state-
ment. A compound statement consists of zero or more statements (simple or
compound), with the statement collection being bounded by left and right
curly braces ({ and |), respectively. Compound statements are often called
blocks. Thus, the and symbols correspond to BEGIN and END, respec-
{ 1
In Pascal, some programmers omit the semicolon between the last state-
ment in a block and the END that terminates the block. However, they must
usually put a semicolon after the END, since another statement is likely to
follow. In C, the semicolon rules are just the reverse: you must end the last
statement before the right brace with a semicolon, but you need not put a
semicolon after the right brace.
semicolons. (Incidentally, what do you suppose will happen if you change the
compound statement {;) to {
} ?)
Identifiers
So far, we've discussed two functions, namely main( ) and printf( ); we have
82 Using QuickC
used some variables (il and i2); and we've used names for values specified in
header files: PROG_MESSAGE, PF_EXAMPLE, PF_EXAMPLE2,
and FALSE. In what constitutes a valid name, or
this section, you'll learn
identifier, in C. and also learn about some naming and spelling conventions
among C programmers.
Naming Rules in C
The naming rules for C variables and functions are similar to the rules in other
languages. An identifier, or name, consists of some combination of valid
characters. The only real trick is remembering what characters are valid and
when each is valid. Valid characters for C identifiers are:
Identifiers nwsi begin with a letter or an underscore. Digits are not allowed as
the first character of an identifier.
Table 3-2 shows examples of valid and invalid identifiers. The table also
contains a column of identifiers on which to test your understanding of the
naming rules for C. If you're unsure whether an identifier in the third column is
valid, try it in a short program. Even you are sure, it's sometimes interesting
if
being reserved. Compilers will not let you use any of the keywords as identifi-
Variable Definition:
IVIalcing Room
C programs generally manipulate information stored in specific memory
locations in your computer. When writing a program, you'll use variables to
store the information. A variable is essentially a slot capable of holding
information.
A memory address is associated with a variable, and the variable's value is
stored at that address. To use variables in C, you must give the compiler a list of
variables names that you'll be using in your program. For each variable
identifier, you must also tell the compiler what kind of information you want to
store in that variable; that is, you must associate a type with each variable
name.
A variable's type tells the compiler something about how the information
is to be stored, what operations can be performed on the information, and also
how much storage space must be allocated to store the variable's value.
When you provide both a variable name and a type specifier, you are
Simple Types and Operators 85
Table 3-3.
.
86 Using QuickC
Assignment Statements
Values are stored in variables by means oi assignment statements. This process
puts the value found on one side of the assignment statement into the variable
specified on the other side. Assignments are made using the assignment
operator (=). For example, the followmg listmg shows how to assign a number
to a variable.
main ()
{
Int il, 12;
11-37;
12 - 27 + 10;
printf ( "after asslgnnent, 11 has the value ild\n" , 11);
prlntf ( "after assignment, 12 has the value Xd\n" , 12);
The variable definition has set aside space for two variables of type int.
The assignment operator, = stores the value on the right side of the
first ,
assignment operator (37) in the variable specified on the left side (il). The
result is that the memory location associated with il now contains the value 37.
Simple Types and Operators 87
In the second instance, the system first evaluates the expression to the
right of the assignment operator (27 + 10), then assigns the result of the
evaluation to the variable, i2.
In the first assignment, the right hand side of the assignment statement
contains a single number. The right hand side can also be a more complex
expression, which evaluates to a specific value, as in the second assignment
statement. In that case, the system first evaluates the expression, then assigns
the result to the variable on the left.
C gives you great flexibility in the expressions you can put on the right
The expressions can actually contain assign-
side of the assignment operator.
ments under certain conditions. Such assignments "within" assignments are
unnecessary and potentially troublesome.
If you want to explore complex assignment statements on your own,
begin with a statement such as this:
Make sure you have defined all three variables to be of the same type.
What are the values of i2 and i3 when all the assignments are completed?
What, if anything, does this tell you about the order in which QuickC evaluates
assignments? Use printf( ) to answer these questions.
Initialization During
Variable Definition
There is a special form of assignment that can be used under certain circum-
stances. Essentially, a variable can be initialized when it is first defined. Look
carefully at the following program:
main
88 Using QuickC
This program writes the values of the two variables, il and 12. While basically
thesame as the previous program, this version does not have the two assign-
ment statements of the earlier listing.
This time, the assignments are made when the variables are defined. In
this program, the system immediately stores the value 45 in the memory
location reserved for il when that variable is defined; in the same way, the
system stores the value 23 in i2.
C will let you assign initial values to variables when you first define them.
Because variables are separated by commas in a definition statement, all you
need to do is add the assignment operator and the initial value before the
comma.
You cannot nest initialization assignments in QuickC. The following is
invalid:
int il = i2 = i3 = 45;
This is only one way in which the initialization rules differ from those of
regular assignment in C; you'll learn about other restrictions later.
After the brief discussion above of the structure of C programs and functions,
let's find out about some of the data types C provides for programmers.
Variables may contain simple pieces of information (such as a character
or a number) or multiple pieces of information (such as a string of characters
or an array of numbers).
In the following discussion, we'll concentrate on variables that contain a
single piece of information. Such variables belong to a simple data type.
Simple Types and Operators 89
C has the "usual" simple data types, characters, integers, and numbers
with fractional components. In addition, C allows variants on some of these
types, and lets you convert some simple types into others. Simple types include
char, int, float, and double. These types differ in the sort of information they
bits allocated for int variables to represent values ranging from — 32768 to
32767(-2'^{151 to2^{15i-l).
On larger systems, 32 bits may be allocated for an integer, with possible
values rangingfrom-2,147,483,648 to 2,147,483,647 (-2'^{31! to 2'^{31}-1).)
Suppose you try to represent a number larger than the maximum possi-
ble? If you try to store such a number, you will get odd results. For example, if
you try to add to 32767 on a computer with 16-bit integers, you will not get
1
32768. Try Because of the way integer values are represented, the system
it.
ends up reading this new value as a very large negative number. This result is
; ;
90 Using QuickC
What do you suppose will happen if you try to use a negative number
beyond the allowable range? For example, what does the following program
return?
main
Hint, think of the integer values as being on a giant wheel, with at the top,
and the maximum and negative values next to each other at the
positive
bottom, as shown in Figure 3-2. Then think of overflow conditions as arising
when you continue counting in the same direction, but past the maximum
value.
number part. For example, you can represent the number 500.250 in each of
the following ways. 500.250eO, 50.0250el, or 50025.0e-2. These representa-
tions tell you to move the decimal point places, 1 place to the right, and 2
places to the left, respectively.
Simple Types and Operators 91
2765 32765
32766 32^56
32767 32767
-32768
latenaUy, float \'alaes also are stored in three components, a sign bit,
some number of bits to store a fractional component of the number (called the
numtissa), and the remaining bits to store an exponent (see Figure 3-3). These
components correqmnd to the three parts ofai floating point representation.
The only difference between the actual representation of floats and our
discussion is that the internal values are all in terms of binary values. (That is,
the exponent in an internal representation tdls ho«' many "binary" places to
move the point.)
92 Using QuickC
As with floats, arithmetic makes the range of possible values larger than it
might have been. A double allows representation of values ranging from 1.7 *
10~ to 1.7 * 10"^
, with the same range of values possible for negative
numbers.
The main difference between floats and doubles is the amount of space
allocated for these numbers. Be careful about assuming that two variables have
exactly the same values. Because of the way floats and doubles are represented,
not all possible values within the permissible range can be represented. Also,
intermediate computations in an expression may introduce rounding errors
that could change the value of a particular variable slightly. This means that
such values may only be approximately correct, or nearly equal. (For most
you won't need to worry about the
calculations, effects of such errors. Just
remember they can occur.)
The following listing and output illustrate some of the ways in which
values can differ because of limited representation accuracy and because of
factors such as rounding when computing results.
main ()
<
/• define float and double variables:
initialize to arbitrary starting values.
*/
float fl - 230.6.
f2 - 230.6. f3 • 1.0.
f dv - 11.0; /* float DiVisor */
double dl - 230.6,
d2 - 230.6, d3 - 1.0,
d_dv • 11.0; /* double DiVisor */
94 Using QuickC
/* report outcone •/
prinM ( "X20.17f (fl) does NOT equal\nX20.17f (12)\n\n\n", fl. f 2)
prlntf ( "X10.1711 (dl) equals\n%lB.171f (d2)\n". dl d2) .
STARTING VALUES
fl - 230.60000000000000000
dl - 230.60000000000000000
RESULTS
f2 - 0.00001183342556033
d2 - 0.00001183342620640
DIVISORS
13 - 19487172.00000000000000000
d3 - 19487171.00000000000000000
This program computes the "same value" in two different ways, and displays
the results of the two computational methods. The program does this with
variables of type float as well as double. The results for the two types are
different.
rent value by the same divisor, until the division has been carried out far
enough. Each division may introduce approximation errors.
The second method first multiplies the divisor by itself as often as needed
to get the "appropriate" quotient. Then the program carries out the division.
Each multiplication may introduce approximation errors, but these will be
different from those introduced by repeated division.
Mathematically, these two methods produce the same results. Therefore,
any differences are due to representation or computational "approximations."
There are three differences in the output; there is also one important result
where there is no difference.
The difference between fl and f2 is the result of computation error The
difference is very small, less than one part in 10 billion. You won't have to
worry about such errors in most of your work, since they will probably be too
small to make any practical difference for your answer.
If the two answers are not identical, which is more accurate, which is
that sometimes the manner in which you compute a result might make a
difference in the outcome.
Notice that the two double results are identical. That's because the
precision used to represent double values is enough to be accurate within
great
17 decimal places for this problem. If you did enough computations, you would
get differences even in the double representation.
The differences between f3 and d3 and between
and d2 are also due in
f2
The following program shows how to define and write simple types. The
program is fairly long, and contains lots of new features. We'll go through it in
detail in the following pages.
main ()
<
char cl, c2. c3; /* define three variables of type char */
int il. i2. i3: /* ... type int */
float fl, f2; /* ... type float */
double dl, d2: /* ... type double */
; ; ;;
96 Using QuickC
The first two char assignment statements show that you can assign a value
to a char variable in more than one way. by actually specifying the character
to be stored there or by assigning the ASCII value associated with the
Simple Types and Operators 97
character to the variable. In the first assignment, the system translates your
character (written within the single quotes) into its ASCII code, and stores this
code in binary form. You can assign 97 to c2 because characters are stored in a
byte capable of holding values between - 128 and
127, and 97 falls within this
range. (Note what happens if you add a larger number, as the program
suggests.)
The third assignment actually adds an integer to a character. While this
may seem odd, it is allowed in C because, internally, C actually represents char
variables as integers anyway. Essentially, you are adding 3 to a numerical
(ASCII) value. Since 100 is still a valid character, the system handles the result
in the same way as if you had simply assigned the value 100 to c3. This
assignment also illustrates the use of the addition operator (+). You'll learn
more about other operators later is this chapter.
The first call to printf( ) has four arguments, the string argument (con- ,
taining three placeholders) and three char variables, whose values will be
|
substituted for the placeholders. This call to printf( ) also illustrates how to get <
Notice that the values written for c2 and c3 are actual characters, rather
than numerical codes, despite the fact that we had assigned numerical values
to the variables. Using %c tells the function to translate the numerical code
into the actual character before writing it.
The second call to printf( ) seems to violate a rule we saw earlier that the —
placeholder type should match the types of the variables passed as the addi-
tional arguments to printf( ). That is, the %d does not match the char variables
whose values will be written. This is allowed for the same reason that adding
characters and integers was valid, because char variables are actually repre-
sented as integers internally.
The third integer assignment statement (i3 = i2 + cl) also illustrates that
int and char variables can be used together in an expression. The result can be
To write a float, use the %f placeholder. The printf( ) that writes the floats
uses two different forms of the placeholder To write fl, the function uses the
system defaults for the number of decimal digits shown; for f2, we told the
system how to write the result. The first number (40) tells the the system the
98 Using QuickC
field width — how many places to allocate for the entire value. The second
format parameter specifies the number of decimal places to write. The period
between these two formatting parameters is not a decimal point; it's just the
character used to separate the two values.
The assignments and output for the double variables had the same goal as
for the float values. This time, however, the 1 does show up in the output —
because a double can represent more numbers within its range than the single
precision float can. Notice the syntax for placeholders for double varia-
bles. %lf (for long float, which is essentially what a double The %lf is not
is).
^fiEMEMBER Make sure that the variable type on the left side of an
assignment statement is able to store information of the type which the right
hand side evaluates.
The last call to printf( ) illustrates an error that will get you into all sorts of
trouble. Trying to substitute a floating point value for an integral placeholder
leads to unpredictable results, just about guaranteed to be wrong. Notice that
the system writes seemingly arbitrary values; it does not simply leave off the
fractional part of the number. The reason for this concerns the way the bits are
organized for integral and for floating point types.
The second integral placeholder also contains a format command. You
can specify the field width for integers as well as floating point types. Since
there are no decimal places, you need only one number to specify the format
for integers. Check the QuickC documentation for even more ways to specify
formats.
The last thing to notice is the use of the newline escape code ( \n) at the
beginning of the string argument. We've already seen the trick of using two
consecutive newlines to produce a blank line between entries. The introduc-
tory sentence in the next-to-last printf( ) shows that you can put the newlines
codes anywhere in your string argument.
;
nain
{
signed char scl
signed Int sil;
unsigned char ucl;
unsigned int uil:
Short and long are requests for versions of the int type for which different
amounts of storage may be allocated, depending on implementation. You can
also define variables of types unsigned short, unsigned long, signed short, and
signed long. The syntax for defining such variable types is the same as for the
other simple types. For example.
short sil:
long 111;
These two definitions allocate space for variables of type short (integer) and
long (integer). The amount of space allocated for these variants depends on the
implementation. For example, QuickC allocates the same amount of space to
.
int and short (two bytes), but more to long (four bytes). Regarding storage
allocation, the only guarantees are.
1 The same amount of storage will be allocated to int and unsigned int
variables.
2. The storage for a short int <= the storage allocated for an int.
will be
3. The storage for a long will be >= the storage allocated for an int.
main ()
<
printf (
Simple Types and Operators 101
^CAUTION The types whose storage sizes are most likely to differ from
system to system include short int, int, and long int. Such differences may
affect the portability of your program.
Table 3-4 summarizes information about simple data types. Notice that
some of the variants on a type have the same range of values. For the floating
point types, note that both extremes have very large positive exponents. This
amounts to saying that float and double values range from very large negative
to very large positive numbers. Remember, a positive number with an expo-
nent such as —38 (e.g., 5e— 38) is a very tiny number, but still positive, and
therefore larger than a number such as — le25.
Constants
You may find the need for a specific value at some point in a program. This
may be the only place you need the value; or you may need it in lots of places,
but the value never changes. Such unchanging values are called constants.
Specific values —as opposed to variable names —
that are used in assignment
statements or other expressions are also called constants.
Depending on its value, a constant will be interpreted as being of a
particular type. For example, the letter q is a character constant, hello is a
string constant, the number 8 is an integer constant, and the number 2.78 is a
floating point constant. As we'll see, C is quite flexible in how it lets you write
constants for the various types.
Integer Constants
The following program writes the same number (32254) in three different
bases; 10, 8, and 16. We'll discuss these alternate number bases after we
analyze some of the program's new constructs.
main ()
>
This program produces the following output. Each value represents the same
number (the decimal 32254).
This program simply writes numbers. It uses the initialization during defini-
tion that we have already seen. It also introduces two new placeholders and
uses an escape code we haven't tried yet.
Simple Types and Operators 103
The %o placeholder tells the system to write the value substituted in octal
form. This means that the value stored in variable il is transformed before it is
written to the screen. Similarly, the %x specifies that the value is to be written
in base 16.
The \t escape code is a tab, and tells the system to move to the next
tabstop before continuing with the writing. Notice that there is no space
between \t and the following word. This is because everything after the t in \t
In base 16, the same principle applies, but this time the powers are based
on 16, so that each place value is 16 times the place value to its right. The
hexadecimal value for our number is 7dfe. This translates into, e ones, f
sixteens, d two hundred fifty-sixes (16 * 16), and 7 four thousand ninety-sixes
(256 * 16). If you don't know what these strange numbers d, e, f are, check — —
the next paragraph.
In base 10, you use 10 digits. through The next number is 10
9. — 1 ten
and ones. In base eight, there are eight digits. through 7. The next octal
number is 10. 1 eight and ones.Not surprisingly, you need 16 digits to count
in hexadecimal. There aren't enough digits to count in base 16. Therefore, the
first six letters of the alphabet —a through f — are used to represent the
"digits" 10 through 15 in hexadecimal. So, the hexadecimal digits are
through 9, a through f The next hexadecimal number is 10. 1 sixteen and
ones. Thus, the hexadecimal representation translates into. (7 * 4096) + (13 *
256) + (15* 16) + (14* 1).
104 Using QuickC
Constants of Character
Character constants are written within single quotes. For example, 'Q' is a char
constant. You can also specify a character constant in other ways, however.
For example, the letter 'Q' has ASCII code 8 ( 2 in octal). C lets you specify
1 1 1
a character by writing its ASCII code. Because of this, 'Q' and 81 are
equivalent ways of writing the value of the character constant. When discuss-
ing integers, we learned that integers could be written in bases other than base
ten. Thus, you could just as well write 0121 to represent this char constant.
( \121) corresponding to the character's ASCII value. Note the following about
this representation, a backslash starts the escape code; the use of octal, rather
than decimal, and the absence of the before the octal representation when
writing the character as an escape code.
;
main
{
char cl = 'A', c2 > 65. c3 = AlOl". c4 = 0101;
The program writes the same character value, 'A', based on four different
initial representations. In all instances, the same numerical value is being
stored, but different representations are used.
Notice that the output is on two lines, split between the second and third
characters. The reason is that \012 immediately follows the second char
placeholder. This is another way of writing a newline command, using the
String Constants
is a string constant. On the other hand, the following is not a string constant
because it is not within double quotes.
When writing constants, the only difference between a float and a double is in
the possible range of values. You may get an error if you try to assign a value to
a float that is too large for the allocated storage. Except for size, float and
double values are written in exactly the same way.
number can be written in most of the ways you would
Basically, a real
think. The number must have a number portion; this may be preceded by a
sign and may be followed by an exponent portion.
The number portion has one of the following forms.
with no decimal point or portion following. In this case, the number must have
an exponent portion. For example, le5 is a valid float, but 10 is not. Ten is an
int, no matter what you intended. This is how the system distinguishes floats
from ints. Be careful when writing floats without fractional components. Write
1.0 rather than 1 because the latter is an int.
^REMEMBER Float and double constants can be written using three com-
ponents, an optional minus (— ) sign, a numerical part, and an exponent part.
The sign and exponent portion are optional (with one exception, as described
in the text). Parts must be included in the order listed.
Simple Types and Operators 107
Arithmetic Operators
+ - * /
Each of these works with integral as well as floating point types. Some of
the operators produce different resuhs, depending on the type on which the
operations are being carried out. In this section, we'll look at some of the
properties of the arithmetic operators, and will discuss them in terms of
operator precedence, or evaluation priority.
When applied to floating point types, these operators do the obvious, add,
subtract, multiply, divide. The only quirk here is the danger of exceeding the
range for the variable type. If the resuh of an operation is a value outside the
floating point type's range, you will get an error message.
The result of operations with floating point variables and values should
be stored in a floating point variable. Thus, if you are adding two float values
and want to assign the results to another float, the system will assign a float
result to the variable. If you are assigning the results to a double, the system
will return a double.
il = il * 3;
printl ( "value after overflow: "/.dXn" , il) ;
108 Using QuickC
This program returns 30464, rather than the 96000 you would expect, because
96000 exceeds the largest integer value possible. There is no error message.
When applied to integers, the operators will return integers. This means
that certain operators must work differently with integers than they do with
floating point types. For example, if you divide 7.0 by 2.0, the result is 3.5. On
the other hand, if you divide the integer 7 by the integer 2, the result cannot be
3.5 because 3.5 is not an integer The result will actually be 3, which is what you
get if you divide 7 by 2 and throw away the remainder. The C compiler decides
what kind of division (integer or floating point) it is doing, based on the types
of the variables or numbers involved.
In addition to the four common operators already mentioned, there is
Be careful when doing division or when using the modulo operator with
negative numbers. When doing division, the sign of the result depends on the
sign of the numbers involved in the division. Specifically, if both the first and
second values involved are positive or both are negative (that is, if the values
before and after the division operator ( / ) have the same sign), the result is a
positive number. If the numbers involved have different signs, the result is a
negative number. For example, 36.0 / 12.0 = 3.0, as does —36.0 — 12.0. On the
/
For the modulus operator, the first of the numbers determines the sign. If
the first number (i.e., number from which the remainder will be computed)
the
is positive, the result is positive; if the first number is negative, the result is
negative. These are the rules for the modulus operator in QuickC; other
implementations may handle this operator differently.
they take two values, and return a single value as their result. For example, 7 %
4 takes the two values 7 and 4, and returns the single value, 3.
There is an important unary operator that you already know about.
also
Earlier, —31000 was mentioned as a valid integer value. This is actually the
number 31000, modified by the unary operator, — known
, as arithmetic
negation. This is not the same as subtraction.
why you may find that a number such as +32254 is not valid, if you have an
older compiler. Because there is a unary minus operator, —32254 is valid in any
implementation. QuickC does include a unary plus operator.
Operator Precedence
The relative precedence of two operators tells you which of the operators to
apply first in an expression. For example, 3 + 5*4 could equal 32 or 23,
depending on the order in which the computations are carried out. In algebra,
we learned that the answer should be 23, because multiplication takes prece-
dence over addition. 5 * 4 is computed first. On the other hand, (3 + 5) * 4
does equal 32, because the parentheses tell you to add 3 and 5 before multiply-
ing the result by 4. The parentheses override the existing operator precedence
hierarchy.
The operators we have seen so far also fall into a precedence hierarchy,
summarized in Table 3-5. The topmost operators have the highest precedence
in the table.
Operators on the same level have the same precedence. In this case,
operators are evaluated in the order in which they are encountered, usually left
Operator
; ;
promoted to float; then the expression is evaluated and the result is of type
first
int il = -12, i2 = 3, i3 = 4;
unsigned ul = 10, u2: /* define some unsigned integers. */
float fl, f2 = 3.0. f3 = 4.0;
fl = 13 / 12;
printf ( "\ni3 / 12 (4 / 3) --> float, written as float: '/.f \n" , f 1) ;
fl = 13 / f2;
printf ( "13 / f2 (4 / 3) --> float, written as float: '/.fXn", f 1) ;
actually an expression, rather than a variable name. This means that the
expression is first evaluated, and its result is substituted for the placeholder.
The examples in the listing show what happens under good and bad condi-
tions.
Note that when the resuh of (—12 +10), an expression that we would
evaluate to —2, is stored in an unsigned int, the result is a very large positive
number. This is because the sign bit (the high order, or leftmost, bit) is 1 for
negative numbers, but is used to help represent magnitude for an unsigned int.
When writing out the results of expressions containing mixed types, make sure
the resulting type and the type specified in your string argument to printf( )
match.
Notice the %ld placeholder command. This says that the information
coming is a long and should be written as such. Similarly, %lf is intended for a
double in the Draft Proposed ANSI Standard (and, hence, also in QuickC),
Table 3
114 Using QuickC
between float and double in placeholder specifiers. They simply write the
variable passed as required by the variable's data type.
Summary
In this chapter, you've learned something about the makeup of a C pro-
gram, the structure of a program, of functions, and of statements (simple and
compound). Two functions that are particularly important in C — main( ) and
printfO — were also discussed. You've also seen how to put comments into
your program, and how to define variables of different types.
The chapter and their
also introduced you to C's simple data types
variants. In the process, you learned how to represent these types and how to
carry out certain operations using variables and constants.
In the next chapter, you'll find out more about the structure of C pro-
grams and about the files that make up such programs. You'll also learn ways
to make your files cleaner and easier to read.
4
Preprocessors and
Programs
I
;|i
In this chapter youll find out more about C programs. In the last chapter, we
saw a very simple program — made up of a few functions defined in a single ]
",
source file. In this chapter, we'll elaborate a bit on that barebones program i,
il
structure. You learn about the C preprocessor and the directives you can give
it. You'll also learn how to write macros to do certain things, and how to
declare library functions you want to use.
In Chapter 1 you learned about program source and header files, and in
Chapter 3 you learned that programs consist of functions. Now, we'll look
more closely at program files, and at how to use definitions in programs.
Program Structure
In addition to functions, a program can contain instructions on the contents of
a file, or it can contain variable definitions. You can hide many detailed
TI5
; ; ; ;
/* write a greeting to screen. Add to line counter for each line written. */
wrlte.message
<
line.number • line.number +1; /* increment line counter. •/
printf ( "•/.2d\ty,s\n5'.2d\t\t'/.s\n'/.2d\t\t y.s\n".
line .number. GREETING,
line.number + 1, CLOSING,
line.number 2, SENDER)
line .number line.number +2; /* increment line counter. */
>
mainO
write.program.info ();
wrlte.message ();
; ;; ; ; !
j
empty.line ()
lake.empty ()
empty.line C)
wrlte_program_info ();
wrlte.nessage ()
printf ( "\n\ny,2d lines writtenXn" . line.number)
>
Ml
2 Hello. |i
3 Sincerely. ) |
4 World e
5 t'
16 lines written
118 Using QuickC
The next listing shows the header file read by the program.
/• one-line version */
•define PRQG.MESSAGE "QuickC version 1.0"
The program writes the same messages to the screen. In addition, it counts the
number of lines being written and gives you a total at the end.
The program above uses substitutions defined in the proginfo.h file.
When QuickC encounters a name defined in proginfo.h in the source file, it
replaces the identifier with the information associated with the name in
proginfo.h.
If you could read function write program_info () after it has been
compiled, it would look like the following listing:
write.program.lnfo
<
line.number = line.number + 1;
printf ( "'/,2d\t'/.s Version '/.B.lfXn", line.number, "QuickC", 1.0);
The difference between this and the original, pre-compiled version is that the
references to identifiers found in file proginfo.h have been replaced by their
string or numerical equivalents. For example, instead of PROGRAM, the
function now has "QuickC." Note, however, that you never actually see the
substituted text in your source code. Instead, QuickC places the substituted
value into the executable code. The substitutions are literal — exact replicas of
what you wrote in the header file are reproduced in the function. Later, you'll
learn how to make more complex substitutions that will actually perform a
task.
The version number (1.0) is being written in five columns, with one
decimal place. Although "1.0" is an exact substitution from proginfo.h, you
must specify the format if you want control over the output. This is because the
substituted value is treated like any other value of the type specified by the
Preprocessors and Programs 119
The C Preprocessor
The C preprocessor provides a means of modifying a C source file in various
ways before the actual compilation. This capability lets you extend the C
language to make it easier or more useful for particular purposes. The prepro-
cessor is conceptually distinct from the compiler, but it isn't necessarily a
separate program. The important distinction is that the compiler treats your
source as if you had actually typed the material the preprocessor substitutes for
the appropriate identifiers.
For example, the #define directive enables you to use mnemonic names
(such as FALSE or VERSION_NR) instead of the arbitrary numerical values
needed in your program. In such cases, the preprocessor replaces the identifi-
ers with the actual values you've associated with those names. After the
substitution, the compiler never knows that the source file ever contained
anything but the actual values.
In this section, you'll learn how to use some of the preprocessor com-
mands, or directives, available in QuickC. You'll see that the preprocessor
commands provide a sort of "mini-language," which lets you do some fairly
main ()
{
printf ( "5il\n", SqUARE(3.5))
}
The preprocessor substitutes (3.5) * (3.5) when the line calling printf( ) is
information included as the argument must also be put into the text (as in the
Then the substitution text replaces the macro. This process of
listing above).
exchanging the macro call for the information it represents is known as macro
expansion.
column of a line. The Draft Proposed ANSI Standard lets you put a space
between the # and the command name. Both QuickC and C 5.0 will let you do
this. However, some older compilers will not allow this, so check your compiler
for the exact form a preprocessor command must take.
will also let you put space between the # and the command name.
After the directive name, the command includes the constant or macro
name and the replacement text for the macro or constant, each separated by
one or more blanks, as shown here:
! t t
Directive IVlanifest Replacement
constant text
If your entire command won't fit on one line, or if you want to make the
command easier to read, you can tell the preprocessor that the command con-
tinues on the next line. To do this, end the current line with a \ (backslash). When
the preprocessor sees the backslash, it will keep reading the next line. For example:
#define TOO.LONG "I made this too long to fit on one preprocessor \
line .
main ()
<
printi ( "'/.f\n\n". SQUARE (2.5));
print! ('"/.aNn". TOO LONG);
}
6.250000
This program writes the results of processing two definitions. One value is the
result of a simple substitution for a manifest constant (TOO LONG); the other is
the result of expanding a macro (SQUARE(x)).
The two #define commands each extend over two lines. Notice that the output
for the TOO LONG constant has a large blank space in it — because the original
command has blank space before the text on the second line. The preprocessor
takes the replacement text literally, blank space and all.
This also explains why preprocessor commands don't have semicolons at the
end. Any semicolon at the end of the string would show up inside the parentheses
for the printf( ) call, which is not acceptable to the compiler.
The use of space in the string replacement text for TOO LONG has had an
effect. By putting the space at the beginning of the second line, a gap was
introduced in the output text. On the other hand, the SQUARE(x) macro was
called with a space between the name and the argument list (in parentheses),
despite the fact that the original macro name had no such space. This is allowed by
most compilers — including QuickC, and in the Draft Proposed ANSI Standard —
because the system ignores any spaces between names and argument lists for
functions or macros.
In other words, the SQUARE (2.5) is processed just as if it had read
SQUARE(2.5). This means you can use spaces to help make your source file easier
to read. You may not leave spaces in the middle of a name, however.
Preprocessor Commands
There are many preprocessor commands, which serve various purposes. In
this section, you will find information about these commands, constants, and
macros.
Preprocessors and Programs 123
named at the spot in the source file where the #include command is found.
For example,
#include "proginfo.h"
causes the contents of the file proginfo.h to be written in the source file
location where the command was found. You could have achieved the same
thing by actually typing the contents of #proginfo.h directly into your source
file.
There are two forms of the #include command. In one case (used in the
examples so far), the file name is specified as a string constant, between double
quotes (e.g., "proginfo.h"). The system searches for the file in the current
directory. Some implementations may also search in other places, if the file is
not found in the current directory. This form of the command is most useful for
including header files you've created.
In the other form of the command, the file name is written, without
quotes, between < and >. For example,
#includs <proginlo.h>
tells the preprocessor to search for the file in a directory specified for the
implementation. Usually, this directory will be where the particular implemen-
tation keeps its header files. For example, QuickC looks for such header files in
a directorynamed \include. Again, if the file is not found in that directory, the
system may look in other places specified by the implementation. This form of
the #include command is primarily for including files created by the developers
of QuickC or whatever C compiler you're using.
1. Within quote marks (" ") for files in the current directory.
2. Within angle brackets (< >) for files in a default "include file"
directory.
In QuickC, you can have#include commands inside include files. That is.
; .
you can nest include files. Most implementations of C let you do this; however,
they differ in the number of nesting levels you can use. QuickC and Microsoft
C 5.0 let you nest up to 10 levels deep. Check your compiler to see how many
levels of nesting it allows.
Manifest Constants
/* Program to print out number oi bytes on a double sided DOS floppy disk.
Disk capacity Is computed using several preprocessor definitions.
mainO
{
printf ( "declmal\t\t HldXn". FL.BYTES)
printf ( "octal\t\t 511o\n" FL.BYTES);
,
decimal
;
The preprocessor then sees four new substitutions to make, and makes
them, so that the first printf() call looks as follows when all the macro
expansion has been carried out:
decimal
Preprocessors and Programs 127
•define FL.SIDES - 2L
REMEMBER Unless you have a very specific reason for including assign-
ment operators in preprocessor definitions, don't include them.
Similarly, you may inadvertently put a semicolon at the end of the
replacement text. Again, there may be times when you actually want to do this.
In most cases, however, this will resuh in a compiler error.
LI'
Macros with Parameters
The preprocessor command #define can be used with function-like macros to
make more complex substitutions. These macros have "slots" into which you
can pass specific values when you call the macro in your program. The slots are
known di^ formal parameters, and are essentially placeholders. When you call
the macro, you pass one argument for each formal parameter in the macro
definition. These arguments are also known as actual parameters.
For example, the following preprocessor commands define macros with
parameters:
Each of the "square" macros has one formal parameter, specified by the x
within the parentheses immediately following the macro name. Any formal
; ;
mainO
int good_int_sqr, bad.int_sqr. int.sum;
double good.dbl.sqr, bad_dbl_sqr. dbl.sun;
Good INT: 25
Bad INT: 11
INT Sum: 5
Good DOUBLE: 25.000000
Bad DOUBLE: 11.000000
DOUBLE Sum: 5.000000
This program computes a number of things and displays the results. Three
;
function-like macros are defined for the C preprocessor, and are used several
times in the main program. Two of the macros, SQUARE(x) and BAD
SQUARE(x), are supposed to do the same thing; however, they behave quite
differently, as shown by the output.
First, notice that the macros are being used with both int and double.
Preprocessor macros can be used with any type of information. The prepro-
cessor will make nonsensical substitutions, if you tell it to, although the
compiler would then complain. In the listing, the substitutions result in valid
expressions, so all is well syntactically. Although you can use the macros with
integral or floating point types, you still need to make certain that the right
types are returned and used in other statements, such as the printf( ) call.
ment.
The preprocessor then expands the first macro it encounters, ADD(a,b),
again substituting arguments for formal parameters. The macro is found
macro expansion is carried out twice. After
twice, so the the all calls to
ADD(a,b) macro have been expanded, the statement involves only numerical
expressions and an assignment operator.
Notice all the parentheses appearing as the statement is processed. These
are introduced in the macro bodies, for both SQUARE(x) and ADD(a,b).
Let's look at the statement involving bad_int_sqr, to see why it pro-
duces a different result.
The BAD SQUARE (x) macro does not put parentheses around the formal
parameter in the macro body. When argument is substituted in the
the
expansion, no parentheses are included in the expanded version of the state-
ment. The absence of parentheses becomes a factor after the ADD(a,b) macro
has also been expanded. Because multiplication has precedence over addition,
the two middle elements — (3)
and (2) —
are multiplied together first. The
parentheses in the SQUARE(x) macro override the precedence hierarchy,
forcing the two sides of the multiplication operator to be added first.
After seeing how nested macros are expanded, you should have no trouble
working through the transformations for the statements involving variables of
type double.
Many commands that are already available to you in QuickC are actually
macros which are expanded when the preprocessor modifies your program.
Some of the "functions" available will actually be macros, which are expanded
by the preprocessor.
For our purposes here, there's no difference in how to use the command,
except in what happens when a macro or a function is called. When a function
is called in a program, the function variable definitions must be loaded into
memory (placed on the stack). This takes time. When a macro is expanded, the
instructions in the macro body are inserted directly into the source code. This
is done either before or during compilation. When compiled, the instructions
will be in the compiled program at all the required places; the program will not
need to load anything extra into memory.
So, you can sometimes make your program run faster by using macros
instead of functions. The trade-off for the speed increase is increased code size.
However, macros can be tricky, so it probably won't pay to place too great an
emphasis on macros over functions. Still, when used correctly, macros greatly
and portability of your code.
increase the readability
One danger in using macros with parameters is incorrect results coming
about because of operator precedence. The solution is to put parentheses
around each parameter in the macro body. It turns out that even this may not
be safe enough in certain cases. Often, the safest thing is to put parentheses
.
around the entire macro body. For example, ADD(a,b) would then be defined
as follows:
Decision-making with
Preprocessor IVIacros
You'll probably encounter situations where you may want to make different
types of substitutions for the same constant or macro, depending on context.
For example, suppose you wanted a program to report the number of bytes on
a floppy disk — where the disk could be single- or double-sided, or high-
density. The following listing shows how to specify this information using
preprocessor commands.
mainO
{
printf ( "decimal\t\t '/,ld\n" FL.BYTES)
, ;
>
132 Using QuickC
This program computes and displays the capacity, in bytes, of various types of
DOS floppy disks, single- or double-sided, or quad density. The preprocessor
commands determine what value to display. Currently, the macro
FL BYTES will evaluate to the capacity of a quad density disk used with
ATs. Let's see why.
*endif
#else
*elif
If FL SIDES has been defined, then two new definitions are made:
FL_TRACKS and FL_BYTES. If FL_SIDES has not been defined, then
neither of these definitions occurs. Instead, the preprocessor checks the com-
mand following the #else.
Preprocessors and Programs 133
In the example, the next command is another #ifdef, this time asking
whether QUAD TRACKS has been defined. If so, all lines until the next
#endif, #else, or#elif are read. If the macro has not been defined, the prepro-
cessor skips all lines until the next #endif, #else, or #elif.
In the example, QUAD TRACKS has been defined, so the preproces-
sor adds three new definitions. Had QUAD — TRACKS not been defined, the
next decision point would have been the #else following the definition of
FL BYTES. If the preprocessor makes it that far, it means that neither
FL_SIDES nor QUAD_TRACKS has been defined. This means that the
disk being asked about is single-sided, and is not a high-density disk.
The next two lines tell the preprocessor that an "if statement has been
finished. The first #endif signals the end of the #ifdef QUAD TRACKS
portion, and the next #endif terminates the #ifdef FL SIDES test. Notice
that the inner "if was terminated first. and
Corresponding #ifdef (or #if)
#endif pairs behave like parentheses, and you should make sure you match
them as carefully as you match parentheses.
Although the macro FL BYTES is defined in three different places,
only one of them is processed during any one run of the program. The result to
which FL BYTES evaluates depends on the pattern of definitions in the
program. In the current form of the example, the first and third definitions are
ignored, since the preprocessor reads only the commands between #ifdef
QUAD_TRACKS and the next #else.
«il 5 > 10
«deflne ARBITRARY OL
«endlf
assumed to represent true. Thus, the 5 > 10 evaluates to 0, which means that
the definition is not made.
134 Using QuickC
similar to #ifdef, except that #ifndef says to carry out the subsequent actions if
the name specified with the #ifndef command has not been defined.
The #error command you specify an error message that the prepro-
lets
cessor will display if it finds specific situations which it is testing. For example,
you may want to ask whether a particular macro has been defined, and display
an error message if it has not. You could do this in the following way:
#ilndef MY.MACRO
terror "ERROR: MY.MACRO is midef ined"
#endif
any point in your source file. The compiler treats the following lines as having
numbers continuing from the number you specified. For example:
This capability is most useful when you are debugging your program, that
is, when you are tracing the execution of your program in order to locate errors
or incorrect results.
A #pragma is a preprocessor command that lets you tell the implementa-
tion to behave in certain ways. For example, it lets you tell the implementation
; ; ;
that you want to keep a trace of the program's execution if your implemen- —
tation supports such a capability, an action or a declaration specified with a
if
Library Functions
We've used the predefined printf( ) function in many of the programs so far.
This is only one of the many functions that have been precompiled, and put
into a Run-Time Function Library for use with QuickC.
The Run-Time Library is a compiled module accessible to your C pro-
grams. Functions defined in this library are available for use in your programs.
Such functions are handy because they save you the trouble of writing instruc-
tions to carry out the tasks the library functions are designed to do. Microsoft
has put a great deal of work into creating a useful and sound function library;
it's to your advantage to make use of the capabilities it provides.
In this section, we'll look at some of the functions available to you
through this library, and you'll learn how to tell the compiler you intend to use
one of these functions. See the documentation for your Run-Time Library for
information about the available functions and macros.
The following program shows how to use predefined functions to com-
pute the absolute value of an int or a long. (There is also an absolute value
function for floating point types, but that requires an additional header file.)
main ()
<
int pos.Btart " 357. neg.start • -200;
Int abs ;
This program computes and displays the absolute value of several variables.
The abs( ) function returns the absolute value of an integer. If the number
is positive or zero, the function returns the number itself; if the number is
negative, the function returns the positive form of the number (that is, the
number without the minus operator). The labs( ) function operates similarly
for longs, and the fabs( ) function returns the absolute value of a floating point
variable or value.
There are two important concepts in the preceding program. First, the
program shows how to tell the compiler about the functions you want to use.
Second, the program shows how to use such functions.
Although the abs( ) and labs( ) functions have been predefined, the com-
piler has no way of knowing whether you are using the functions correctly in
your program. One thing the compiler needs to know is what kind of informa-
tion a particular function is going to return. To tell the compiler this, you
declare the function before it is used. The declaration for the labs( ) function is
long labsO :
This associates a particular identifier (in this case, labs) with a data type (in this
case, long). The double parentheses (after the name) indicate that labs() is a
function. Essentially, the function declaration tells the compiler that labs( ) is a
function that evaluates to, or returns, a value of type long. The declaration for
abs( ) is similar, except that the declaration specifies that the function returns
an int.
The program also shows two ways of using function calls in program
statements. With some restrictions, you can call a function anyplace you use a
value of the sort returned by the function. So, you can call abs() in an
argument to printf( ), because abs( ) returns an int, which is substituted for the
appropriate placeholder in the string argument to printf().
. —
You can also call a function on the right side of an assignment statement;
for example, when the result of computing labs() is assigned to pes long
result or to neg_long_resuit. Note here that you cannot put a function call
on the left side of an assignment statement.
A function declaration in C does not allocate any memory for the vari-
able, unlike a definition, which does allocate storage for a function or variable
function for which no declaration has appeared, the compiler assumes the
function returns an int. Function declarations for library functions that return
an int are therefore optional.
because the compiler would have assumed that the function returned an
integer. You may, however, get warnings about this from the compiler
depending on the level of error reporting you have selected when you compile.
Let's look at one more example, to introduce some more library func-
tions. The following listing uses the library functions, getch( ), getche( ), and
rand( ), each of which return integers. The program also introduces a new
operator, the equality operator: ==, as well as providing a first look at a way
of making decisions in C.
•/
main ()
<
tdefine MY.ID 'w' /• user ID; just to show use of getchO */
#define RESTRICT 'r' /• restrict random range */
»define TDP.RAND.VALUE 100
, , ; . ;
int char_read.
rand_resultl raiid_re8ult2;
int randO ,
/* function returns a random integer */
getchO /* read a character from keyboard; NO echo */
getcheO; /* read a character from keyboard; echo */
printf ( "Type in your one character ID. please. \nYour character: ");
char.read = getchO /* get user's id */
;
if ( char.read -- MY.ID)
{ /* beginning of a compound statement. */
printf ( "\nThanks.\n") ;
TOP.RAND.VALUE) ;
}
else /+ if any random integer will do */
{
printf "\nOK. Number will be between
( and %d\n"
MAX.INT);
rand.resultl = randO ;
rand.result2 = randO ;
rand.resultl. rand.result2)
numbers each time it is run. Thus, you will get the same random numbers each
time you run the program in a particular mode. If you asked for the restricted
range of random values, the program would return 67 for the second random
value. This is just the remainder after applying the modulus operator to the
18467 returned by rand() mode.
in non-restricted
The program has some other features worth mentioning. First, notice
that the preprocessor definitions occur inside the main( ) function. Preproces-
sor commands can appear anywhere in a file, although most programmers
place preprocessor commands at the start of a program.
The program introduces a new operator, ==, which evaluates to true (a
nonzero value in C) if the elements on either side of the operator have the same
value, and to false (0 in C) otherwise. The equality operator has lower
140 Using QuickC
precedence than the arithmetic operators we've seen so far, but higher prece-
dence than the assignment operator. (C returns the value if a result or test is
^CAUTION Do not confuse the equality operator (==) with the assign-
ment operator (—).
This program also includes the first compound statements we've seen in
actual programs. There are four of them: after each of the two if statements
and after each else.
}"
else /* if the condition does not hold */
{
is the first C control construct we've seen. You'll find out more about it and
other constructs in Chapter 6.
The last point to mention about this program concerns the call to
printf( ), asking about the /^f^T^/Crselection. The string argument for this
function call actually is split over two lines. To indicate this to the compiler,
we've ended the first hne with a backslash ( we did when a preproces-
\), just as
sor definition did not fit on a single line. You need to do this if you split a single
stringargument over multiple lines; you need not use the backslash if you write
additional arguments on a second line.
The important thing to remember about using library functions is that
you need to declare the function, that is, associate the function name with a
particular type. The type should correspond to the type of value to which the
function evaluates. If you don't declare the function, the compiler will assume
the function returns an int. If you declare it as returning the wrong type, you
will get bizarre results. A library function should be declared at the start of
whatever function calls it the library function.
: ; ; , :
0, 1,2,3,4,5,6,7,8,9
a, b, c, d, e, f
A, B, C, D, E, F
The function returns true if the character is such a digit. Here is the
program:
Note the inclusion of the file CTYPE.H, which defines the macros used.
*/
#include <ctype.h>
mainO'C
int how.long; /* will store length of TEST.STR */
char alnum_char = 'Q',
non_alnum_char = '*';
char hex.char = 'F',
non_hex_char = 'G';
int strlenO; /* actually, a true function */
Length of
Hello, world,
is 13 chars
This program writes the results of testing various characters for certain
properties. The macros used in this program are defined in the header file,
change the macro definition, you change the text that will be substituted
during macro expansion.
So far, we've spoken just about preprocessor commands in separate files.
However, you can also collect C functions in separate files, which can also
make it and revise programs. For example, suppose you have a
easier to read
program requiring numerous string manipulations. You might write some
functions to do this work for you. If you design them well, these functions may
also be useful for other programs.
To make it easy to use your string handling functions in other programs,
you might put them all in one file, then include that file in any program that
needs the string handling capabilities your functions provide. One approach is
to compile the source code for these functions into each new program. This is a
144 Using QuickC
Straightforward but slow process, since the compiler must process the func-
tions for each new program.
Another way of using these functions in other programs is to compile the
file containing the functions, and save the compiled version as a new library
file. Then declare the program needs, just as you've been
string functions your
doing with the library functions. When your program has compiled, you can
link in the library file(s) your program needs. Appendix B provides details
about how to do this.
The ability to create and compile library files independently of each other
is one of C's major advantages, especially because C makes it easy to do. One
Summary
In this chapter, you've learned more about the way C programs are put
together.You were also introduced to the preprocessor and the kinds of things
you can do with it. Finally, we've examined some of the advantages of making
your programs modifiable and using separate files to keep distinct portions of
your program.
Along the way, you also learned about another operator, equality (==),
and about the if control construct. There were also two new printf( ) output
formats: %lo (long in octal format) and %lx (long in hexadecimal format).
Finally, you saw how to indicate that a constant value is to be treated as a long.
In the next chapter, we'll discuss how to enter and display information in
C programs. This will be helpful for building a useful collection of functions.
Reading arn
Writing in C
In this chapter, we'll look at some of the functions QuickC provides for Ij O
(input I output), that is, for getting information into and out of a program.
While not part of the C language, these input and output capabilities are
provided in libraries or predefined an implementation. For example, the
in
documentation for your compiler's Run-Time Library has more about the
functions described in this chapter.
Input and output can get tricky in C, so we'll look only at some of the
ways to do I/O. Some of the 1/0 functions will have to wait until we've
covered certain data types and control constraints more thoroughly.
145
.
Reading Characters
You've already seen two functions for reading characters, getch( ) and
getche(). Each of these returns an integer that represents the ASCII code of
the character read. The difference is that getche( ) echoes the character on your
screen, but getch( ) does not.
The getch( ) and getche( ) functions actually read their information from a
buffer (memory storage area), rather than directly from the keyboard. This
turns out to be handy for functions that might read "too much" information.
For example, you may have a function that is supposed to read characters
until it encounters a particular character (such as a '
— ') which is actually
intended for another function. Situations such as this can be tricky to handle in
most programming languages, since the function doesn't know it wasn't
supposed to read the information until it has read it.
C's solution takes advantage of the fact that information can be put into a
buffer as well as taken out. The ungetch( ) function lets you put exactly one
character back into this buffer. The buffer being modified is used to store
information for the console. This "device" is associated with the keyboard for
input and with the screen for output.
The following listing illustrates the use of the ungetch( ) function to put
information back into the buffer, thereby making the information usable by
another function.
main ()
{
printf ( "Press exactly one key. Do not press z.\n");
lirst.try ();
second. try ();
>
ch getchO ;
z •
This program reads some information, then puts the information back, then
reads it again. The variable (ch), into which information was read in this
program, is assigned a new value between the calls to getch( ). The program
displays the values stored at various points in the program.
Notice the global definition of the variable ch, and declarations of the
functions getch( and ungetch( ). These are used throughout the program, so
)
1 Reads a character from the console buffer and stores it in the variable
ch. This removes the character from the console buffer.
2. Puts the value stored in ch back into the console buffer. This merely
puts a copy of the value into the console buffer; // does not remove the
value from ch.
3. Assigns a new value to ch, overwriting the value stored in ch before the
148 Using QuickC
Function second try() can get a character from the console buffer
because first try( ) has put one back. Had ungetch( ) not been called, the
program would wait for you to press a key for the getch( ) in function
second try( ).
#include <stdio.h>
In your program notice that the instruction uses theO form of the#include
preprocessor command. This tells the compiler to look for the header file in
getchar( ) in your program, the preprocessor substitutes the macro body for
your call.
)
error or because the end-of-file has been reached. QuickC recognizes CTRL-Z
(which you get by typing the letter Z on the CTRL key at the same time) as the
end-of-file character. Other implementations may interpret other characters as
end-of-file.
Unlike getche( ), which returns control to the calling function as soon as it
can return a value, getchar( ) waits until you indicate you are done (usually by
pressing the RETURN key) before letting the program continue. Briefly, get or
ENTER char( ) reads its character, then ignores anything else until you press
Return or indicate end-of-file. (When characters don't echo until the RETURN
key is pressed the system is using buffered input.) For example, the following
program does not end until you press Return:
Int ch;
differ from functions, however, in the way things are actually read and in the
parameter checking that is possible. Sometimes you may need the flexibility of
a true function. The fgetchar( ) function is the function counterpart of get-
char( ). To use fgetchar( ) in the preceding program, you could declare the
function (although you don't have to because it returns an int); you do not need
to include stdio.h.
keyboard. The getc( ) macro does the same thing as getchar( ), but gets its input
) ;
from the stream (that is, file) you specify as an argument. Until we discuss files,
we'll use getcharO or fgetchar() — instead of getc() and fgetc() — when we
need to read individual characters. To give you a feel for how getc( ) is used, the
following example illustrates the format of a getc( ) call using stdin as the
stream. (Remember, stdin is just another stream. It just happens to be the one
for which the system does the work of opening and closing.)
int ch;
The only difference between this and the preceding example is in the routine
works, make the following substitutions in the first listing appearing in this
output. Both putchar( ) and fputchar( ) write the character argument to stdout,
usually defined as the screen. The routines return the character written or
EOF, if there is an error. The following listing illustrates the use of these two
routines:
#include <stdio.h>
main ()
<
Int chl » 07, ch2 - 100. teat.ch;
putchar ( chl);
fputchar ( chl)
teat.ch = putchar ( chl);
}
This program writes some characters to stdout. Each character is written using
either the putchar( ) macro or the fputchar( ) function.
The stdio.h header file is included because it contains the definition for
the putchar( ) macro. Although it's a macro, putchar( ) in this program is
To assign a value to test ch, the expression on the right side of the
expanded, and the replacement body is evaluated. The result is then assigned
to test During the evaluation process, however, the statement also writes
ch.
a character (the contents of ch2) to stdout. This means that three characters
are written, in succession: aad.
Although you can call these output routines with or without the returned
value, you'll find it useful to check the value returned, especially if you are
transferring information from one place to another (such as from the key-
board to a file or from a file to the screen). In such cases, you'll need to check
each value to be written to determine when you're done.
The putc( ) macro and the fputc( ) functions are available for character
;
output to a file that you specify, just as getc( ) and fgetc( ) were for input from a
file. The and fputc( ) output routines each take two arguments: the
putc( )
character you want to write and the file to which you want to write that
character. As you may have guessed, you can use putc( ch, stdout) to accom-
plish the same thing as with putchar( ch). You can also use fputc( ch, stdout)
instead of fputchar( ch).
As with stdin, the stdout file is opened automatically for you at the start of
the program, and is closed when the program finishes. You are responsible for
opening and closing most other files used in your programs.
main
<
newgetche ()
Reading and Writing in C 153
We've already discussed the printf( ) function, one of the workhorse output
routines for C. This function lets you write simple data types, as well as strings,
to the screen (actually, to stdin), and lets you specify the format this output is to
have. In this section, you'll find out more about how to control output with
printf( ).
Integral Types
The following codes are available in printf( ) to specify output of an integral
type:
c d i o u X X
If you specify %x for your placeholder, your result will use the lowercase
digits (abcdef); %X tells printf( ) to use uppercase digits (ABCDEF).
Each of these format specifiers can be preceded by (lowercase L) or h to
I
indicate that the integral type is a long or short version, respectively. For
example:
So far, we've used %f and %lf to specify float and double variables, respec-
There are two other codes you can use to specify floating point output.
tively.
e E g G
the exponential format, whichever is more compact, based on the value and
the precision you have specified. %G uses the uppercase E, if it writes your
result in exponential form.
You can use 1 with any of the floating point format specifiers to indicate a
double in precision value.
nain
<
int il - 32. i2 « 29999;
left of the value. The second integer is to be written in only two columns, but
the number is five digits long, so printf( ) ignores the field width specification.
The same thing happens when we ask the function to write string con-
stants: printf( ) puts five blanks before the first "hello," because we've asked it
ma
<
d2. d2);
;
defaults for field width. The decimal point (.) is required, otherwise the
function will assume you are trying to specify the field width.
We've seen that printf() right-justifies by default. That is, the function pads
any output with blanks to the left of the value, if appropriate. What if you want
pad on the right? printf( ) has flags you can use
to left-justify or to do that. The
following example illustrates the most common flags:
main ()
{
int il - 32, i2 - -29S99;
32 -20009
:
29990
29999
29090
hello :hello
hello :hello
hello hello :
This program writes the same information four times, using different
flags. The first call to printf( ) does not use any and produces the same
flags,
output as in our earlier listing. The next call uses the — flag, which tells the
function to left-justify the output (as on the second output line). In this case,
numbers only. The + flag changes this. Notice that the flag does not tell the
function to write only plus signs (as shown by the —29999).
The fourth call shows that you can use multiple flags in the same place-
holder. The two placeholders also show that the order of the flags does not
matter. These flags tell the function to left-justify and to include a sign.
The next three calls to printf( ) output string values. Only the flag to
left-justify is relevant, and printf( ) obeys it in the second line of string output.
The sign flag makes no sense with a string variable, so the function ignores it.
One other flag may come in handy at times. The # flag can be used with
octal (o), hexadecimal (x, X), or floating point values. When used with octal or
hexadecimal formats (that is, #o or #X), the flag tells printf( ) to include the
leading or OX when writing the value. When used with floating point
formats, the flag forces printf( ) to include a decimal point in the output.
If you are using all the formatting options in printf( ), you need to write
your placeholder specifications in the following order:
Flags, field width, and precision are all optional, but if you use them, they
must appear in the order given. The % and the format are required. If you are
specifying precision, you need to include the decimal point.
fprintfO
The printf( ) function writes its information to the standard output, stdout;
fprintf( ) function lets you specify the file to which it should write the informa-
tion. The only difference between these two functions is that fprintf( ) takes an
argument before the string argument. This argument is the file to which you
want to write. The following two statements have the same effect:
The fprintf( ) function will become more interesting after we've covered files in
more detail.
158 Using QuickC
values stored in the double variables -locations 4858, 4866, and 4874 — will
depend on the bit pattern, and will almost certainly differ from the six decimal
place precision written by default. Here is the listing:
main
{
Int il - 23. 12 - 12345. 13 - 234;
double dl - 23.0. d2 - 12345., d3 - .234;
Reading and Writing In C 159
This program writes information about the locations of six different variables
and about the values stored there. Three of the variables are integers, and three
are of type double. The calls to printf( ) illustrate the notation for the address
operator (& followed by the variable name).
Notice that the placeholders for addresses are integral values, even if the
variable located there is a floating point type. There is no such thing as a
fractional address. The distinction between the location and the value of a
variable is important, as we'll see in later chapters.
Let's look at the numbers displayed for the addresses. Notice that the
addresses of the int variables differ by 2 while the addresses of the double
Variable
Name
) ;
double. For now, this program is intended to illustrate the use of the address
operator. Later, you'll see how to do "arithmetic" on address locations.
Incidentally, notice how d2 and d3 are initialized (with a trailing and a
leading decimal point, respectively). Both of these are valid floating point type
representations in C, as we noted in Chapter 3.
Because it takes one element and returns one element, & is a unary
operator, with the same precedence as the other unary operators we've seen so
far: sizeof( ),
— (arithmetic negation operator), and + (arithmetic plus sign).
scant (
The scanf( ) function is the input counterpart to printf( ). It can be a tricky
function to use, with many variations, so we'll introduce it now and explain
further as we go along. Consult your QuickC Run-Time Library reference
manual for more information.
The function scanf( ) reads data from stdin. The data must have your
specified format, and scanf( ) stores the data in locations that you specify. The
following line illustrates the structure of a scanf( ) call:
test.double, respectively —
and assigns these data to the variables test.lnt, test.char, and
by storing the Information at the
locations of the variable parameters.
*/
scanf ( "ftd '/,c Xlf" ttest.int, ttest.char, ttest.double)
Notice first that $canf( ) has a structure just like that of printf( ): a string
argument, followed by an additional argument for each placeholder in the
string argument. Notice also that each of the variable arguments is actually an
address. This is necessary for scanf( ), since it needs to put the data directly into
the variables, so the rest of your program can use the values $canf( ) read.
^ REMEMBER Except for the string argument, you must pass addresses as
parameters to scanf( ).
non-whitespace character. Thus, the following would be valid input for the
scanf( ) statement above, since scanf( ) would just ignore intervening blanks:
If scanf( ) does not find the character it's looking for, the function terminates. If
scanf( ) does find the character, the function simply discards it and continues
processing your input.
to look for in your input, not to get those characters into your program. This is
handy if you have something hlce a database that has specific words at
particular places in a line. If all you really need are the data around these
words, you can get scanf( ) lo get just that information by including the words
at the appropriate places in your string argument — so scanf() will find and
This is because scanf( ) is expecting to see a number after reading the comma
that is the first char encountered after the int. Instead, the function sees
another character.
You can also specify field widths in your placeholders. These are inter-
preted as the maximum number of places to read for that variable. scanf( ) will
main ()
{
Int il. 12. 13;
char cl;
11 • 1; cl - 2; 12 - 3
3 arguments processed
Reading and Writing in C 163
This program reads three pieces of information and writes them out. It
The first placeholder in the string argument for scanf( ) says to read one
digit at most,and to store this in variable il. The function does this; then it
skips over any intervening whitespace (in this case, none) before reading the
next datum. This is a char, and scanf( ) treats the 2 as a character. After
assigning 2 to cl scanf( , ) continues processing the input. It starts reading right
after the last character it processed, and continues until it reaches a separator
(a blank). Because scanf( ) finds only 3, it assigns this value to the variable i3.
The other two inputs are ignored, since scanf() has read everything it's been
told to read.
The statement containing the call to scanf( ) also provides information
about the type of value returned by $canf( ). Whereas printf( ) returns the
number of characters printed, scanf( ) returns the number oi arguments pro-
cessed successfully. You can often use this value to check whether scanf( ) has
at least read the correctnumber of data.
As with printf( ), the number of additional arguments (after the string
argument) must equal the number of placeholders in the string argument.
There is one important exception to this rule, as we'll see below. You can use
the same formats with scanf( ) as with printf( ): %x, %u, %s, %lf, and so on.
naln ()
<
Int il;
char ci , c2, c3:
cl - a; c2 c3 - ;
3 arguments processed
main
Reading and Writing in C 165
This program reads three integer values. It processes the first and assigns it to
il. The program reads the second integer, but discards it as instructed by the
asterisk (*) preceding the format specifier in the second placeholder. The
program then processes the third integer and stores it in i2.
Notice that only two integer arguments were passed to scanf( ), even
though there were three placeholders in the string argument. The instruction
to suppress tells scanf( ) not to count the placeholder containing the *. You can
also see this in the information concerning the number of arguments processed
(the two arguments passed in, rather than the three placeholders specified).
There's more to using scanf( ) than we've covered here. We'll raise other
issues in later chapters; we'll also talk about reading strings after we discuss
what a string variable is.
The scanf( ) reads its information from stdin. As you might have guessed,
based on our discussion of the getchar( )and getc( )routines, there is a version
of scanf( ) that reads from a specified file. This is the fscanf( ) function. Its
structure and use are just like those of scanf( ), except that its first argument is
the name of the file. Thus, the following two function calls will do the same
thing:
Summary
In this chapter, we discussed some of the routines C provides for reading and
writing information. We've elaborated on the printf( ) function that's been so
useful throughout, and introduced scanf( ), the counterpart for input. In order
to discuss scanf( ), we also introduced another operator, the address operator
(&).
and macros for reading
In addition, you learned about several functions
and writing single characters. In most instances, both macros and functions
are available to do the same thing. As you program more in C, you'll develop a
feeling for when to use which routines.
We also found that most of the routines in this chapter had versions that
used the standard streams (stdin and stdout) as well as versions where you
could specify specific files — for example, fscanf( ) and printf( ).
We'll discuss routines for reading and writing strings later, because many
166 Using QuickC
In this chapter we will explore the constructs C provides for controlling the
flow of your program. These constructs let you specify what your functions
and program should do under various conditions. C includes control con-
structs for selection (taking conditional actions) and for iteration (repeating,
or looping actions). These constructs form the core of most C programs.
The behavior of both selection (if-else, switch) and iteration (while,
do-while, for) constructs depends on the resuhs of tests made somewhere in
the construct, usually at the start.
in a selection construct, a particular action may be taken //a condition
holds, or else a different action may be taken.
In an iterative construct, a particular action may be taken (possibly with
variations) while a condition holds. You will see examples of these constructs
in this chapter and throughout the rest of the book.
An important consideration when using control constructs concerns the
actions your function takes when a condition you've specified holds. Some-
167
168 Using QuickC
the former consisting of one action, the latter including any number of simple
or compound statements, placed between curly braces.
The C compiler treats a compound statement as if it were a big simple
statement. As you'll see, compound statements, or blocks, are important when
you use control constructs. Compound statements allow you to specify a
group of statements to be performed if a condition holds, or while a condition
holds.
Before discussing C's control constructs, let's look at some more opera-
tors that will be useful for formulating the conditions tested by the control
constructs.
Relational Operators
ThQ relational operators establish the relative magnitude of two elements. For
example, 18 is less than 35; you might also say that 35 is greater than 18. In C,
you would write these statements as:
Similarly, you might want to be less specific and say something like
lower than that of the arithmetic operators, but higher than that of the
assignment operator. Because the arithmetic operators have higher precedence
than relational operators, an expression such as the following evaluates to
true:
Because the addition operator has higher precedence, "6 + 1" is evaluated first.
The result, 7, is then compared to the 6.5 on the left side of the less than
operator. The expression reads 6.5 < 7.
Equality Operators
We've already seen the equality operator, ==, in a previous chapter. The
negative counterpart to this operator is the inequality operator, \—. The
inequality operator lets you test whether two elements are not equal to each
other. For example, the following expression evaluates to true:
Both the equality and inequality operators have the same precedence, and
their precedence immediately below that of the relational operators. This, of
is
course, also puts them below the arithmetic operators. For this reason, the
following expression evaluates to false:
3+7I-6+4 /* reduces to 10 != 10 •/
This is because the additions on either side of the inequality operator are done
1
first, making both sides equal. Since both sides are equal, it is incorrect, or
false, to say that 10 does not equal 10. Logically, this is just a very complex way
of saying that 10 == 10. When creating your condition tests, try to keep the
expression as simple and straightforward as possible.
Logical Operators
When writing a function to accomplish a particular task, you'll often find you
need to test several statements at once. For example, a number is divisible by 6
if the number is divisible by 2 and divisible by 3. In other cases, you may be
satisfied if any of several tests comes out true. For example, a character is a
vowel if it's "a" or "e" or "i" or "o" or "u."
You'll also find cases where you need exactly one of several conditions to
be true. For example, someone filing an income tax return can be either single
or married, but not both, at least not without arousing great suspicions at the
IRS.
C provides logical operators for combining the results of multiple tests.
The compound expressions that are created with such operators can be
evaluated further, to a single true or false result.
connects are true. If either or both of these elements are false, the && operator
evaluates to false. For example:
(7 < 12) ftft (12 > 2.7) /* evaluates to true: true S4 true */
(7 > 12) kt (12 > 2.7) /* evaluates to false: false kk true */
(7 > 12) kk (12 > 23.7) /* evaluates to false: false kk false */
connects evaluate to true or if both elements are true. Thus, the only time 1
could return false as soon as the first element -(7 > 12) — was evaluated.
Because this element is false, the entire expression must be false as the &&
operator requires all components to be true. Similarly, you can tell what the
following expression will return without evaluating the entire expression:
As soon as you see (7 < 12), which is true, you know the entire expression will
than the equality operators, but higher precedence than the assignment opera-
tor. The && operator has higher precedence than the II operator. The and
operator sometimes called logical multiplication, and the or operator logical
is
!( 7 > 3)
Controlling C 173
Operator
; ;
;
Let's look at another example to illustrate some other points about the if
construct.
Int val_read;
/• get value */
prlntl ( "Please enter an Integer: ");
scanf ( "Xd" tval.read)
,
This program reads a number, then tells you whether the number is
Controlling C 175
main
<
int val.read;
if Ival.read) /• il val.read -- */
(
printl "Number
( = zero\n")
else if ( val.read < 0)
{
printl ( "Hot zero ...\n");
prints ( "NegativeXn")
}
else /* if val read is positive
<
printf ( "Hot zero ...\n");
printf ( "PositiveXn")
In this construction, the conditions are tested in order, from top to bottom. As
soon as the system finds one that is true, the actions associated with that
condition are carried out, and the rest of the clauses are bypassed. The
program then exits the if-else construct.
Notice that there are two if and two else statements. The first else
corresponds to the first if. If the first condition is not met, the program moves
to this else portion. The first statement along this path is another if construct.
The second else corresponds to this if. This second (and final) else catches any
states that had not been caught at previous conditions. The only path to this
last else is to fail the conditions associated with the two if statements.
Look at the following listing, and try to determine what the output will be
for an input of 0. What is the output for an input of 5?
main C)
int val_read;
Controlling C 177
matches an else with the nearest if that does not have an else associated with it.
In practice, this is the innermost if without an else. In this case, the inner if has
no else portion. Therefore, the compiler associates the else with this if. The
result is that the program writes "Number == zero" when val read has the
value 5. Because the else is associated with the inner if, there is no alternative
action for the outer if (that is, if (val read)). This means the program does
nothing when val read has the value 0.
There are two ways around this difficulty. First, you can include an inner
else that explicitly tells the program to do nothing. The next listing illustrates
this.
main ()
<
int val_read:
if ( val_read)
if ( val.read < 0)
printf ( "NegativeXn")
else /* to close off the inner if */
/* do nothing */
;
The semicolon with nothing preceding it is a null statement which tells the
The second way of dealing with this problem is to use braces to group the
statements the way you want, as in the following listing.
/* Another fix for the program with unbalanced ifs and elses.
This program uses braces to enclose the inner if
•/
main
int val.read:
it ( val.read)
i /* put inner if into compound statement */
il val.read < 0)
(
printf ( "NegativeXn")
} 7* end out if portion. */
else /* it !val_read. i.e. val_read == */
printf ( "Number == zero\n")
In this version, the braces tell the compiler that anything within the braces
is to be grouped together, andfrom the following material. In
is to be separate
this case, the (implicit) effect is to have the compiler do nothing (as originally
intended) if val read was positive (condition for the inner if was false). This
technique is the preferred method.
'U.' In this section, you'll learn about the switch statement, a selection con-
struct useful when you want to test whether any of several constant values is
found.
For example, suppose you want to read a character and determine
whether it is a digit or whitespace. The following listing shows how to use the
switch construct to make such a decision and how to act accordingly:
main ()
{
char char_read;
: : ; ; ;
Controlling C 179
switch ( char_read)
{
case
case
case
case
case
case
case
case
case
case
print! ( "Digit\n");
break; /* force exit from switch */
case ' '
case '\t'
case '\n'
printf ( "WhitespaceXn")
break: /* force exit from switch */
default : /* if neither digit nor whitespace */
printf ( "NeitherNn")
break; /* force exit from switch */
}
printf { "Done\n")
This program reads a character, then tells you whether the character is a digit,
a whitespace character (blank, tab, or newline), or neither type of character.
The program uses the switch construct to decide what response to give.
The switch construct begins with the keyword switch, followed by an
expression in parentheses. This expression must evaluate to an integer in most
implementations. QuickC and Microsoft C 5.0 allow the expression to evalu-
ate to any integral type. The result, however, is converted to an actual int. You
will get a compiler warning if the expression has evaluated to a long. The main
Each label represents a possible value for the expression at the top of the
switch statement. Most labels begin with the case keyword. This is followed by
a value and a colon. In the preceding listing, the values are characters (treated
as integers internally). Notice that each value must get its own label. This
means you can't have a label such as case '0,' '1,' '2':. No two labels can
represent the same value. This means you can't have case '0': in two different
any statements it encounters until if finds a break elsewhere or until the end of
the switch construct. For example, if you removed the first break (after the
instruction to write "Digit"), and then entered '8' when running the program,
the following output would result:
Digit
Whitespace
Done
You can nest switch constructs. The following listing provides such an
example.
: ; ; : ; ; ;
Controlling C 181
main ()
{
char char.read;
switch ( char.read)
{
case '0'
case '1
case '2
case '3
case '4
case '5
case '6
case '7
case '8
case
printl ( "DigitVn")
/* this will distinguish even from odd digits •/
switch ( char.read)
{
case '0'
case '2'
case '4'
case '6'
case '8'
print! ( "Even\n");
break;
default
printf ( "Odd\n");
break;
}
break
default :
prlntf ( "HeitherVn");
break
'
case
case '\t':
case '\n'
printf ( "Whitespace\n")
break
because the second occurrence is inside the inner switch. In a sense, when the
program is in the inner switch it doesn't really know that the outer one exists.
182 Using QuickC
Finally, notice the use of the default label, instead of five labels for each of
the odd digits. This works here, because any character that leads to the inner
switch construct can only be an even or an odd digit.
More Operators
C provides several operators that make common tasks easy, but are absent
from most other languages. The operators are used very frequently in C's
This statement accomplishes the task of adding the value of another int to
To accomplish this, you have to do a lot of typing, and the program has to
do some unnecessary computing. The program actually evaluates my int
twice, once for the expression to the right of the assignment operator and the
second time when storing the result in my int.
C provides compound assignment operators to make such statements
easieron you and on the program. They are called compound operators
because they combine an assignment and an arithmetic operator. For example,
you can write the preceding statement as follows in C:
my_int += another_int;
; ; —
Controlling C 183
/=
%=
The %= operator can only be used with integral types, as was the case for
the simple modulus (%) operator. (You will soon learn about other compound
assignment operators that use non-arithmetic operators.) Let's look at some
more examples.
The first of these assignments stores the result of dividing the integer
variable my int by 5, using integer division. Remember, for integers, the
result of a division operation is the quotient with the remainder discarded: 5
goes into 37 seven times, and the remainder of 2 is discarded. After this
assignment, my int has the value 7. The modulus operator returns the
remainder upon dividing 7 by 7. This result is 0.
The statements in the previous listing produce exactly the same results as
operators are useful in loops, and make it possible to control looping condi-
tions compactly. Let's look at an example.
main
{
int result, test = 0;
printf ( "result =
y,d\n", result);
/• else do nothing */
printf ( "after ++test value of test == '/,d\n". test);
,
>
result 1"
after ++te8t, value of test -- 1
nothing to do, so test nust still "
after test++, value of test •" 1
Controlling C 185
Let's look carefully at the test condition for the if construct. Ultimately,
expression evaluates as true or The program will assign whatever value is
false.
stored in test to the variable result. After this assignment is made, the program
checks whether the value stored in result equals The parentheses around the 1 .
assignment statement ensure that this is the order in which things happen. If
In the case of test++, the value of test is first used in the assignment, then
the value of test is incremented to 1 . This means that a is stored in result. The
equality test evaluates to false in this case, since != 1
The main purpose of the conditions for the if tests is to return a value of
true or false. Assigning a new value to result and incrementing test occur
almost as side effects. As we'll see, however, such "incidental" assignments and
changes are used very commonly in C, and make it possible to write compact
code.
^REMEMBER The major difference between the prefix (++test) and the
postfix (test++) forms of the increment operator is form
that the prefix
increments first, then uses the new value in the expression. Tht postfix form
first uses the current value in the expression, then increments this value after
use.
each time.
Because they are unary operators, the increment and decrement opera-
tors have very high precedence. Don't confuse the precedence of the operators
with the their use in prefix and postfix form. The precedence level only serves
to help the compiler properly group the elements in an expression, as the
following example shows:
main ()
<
int il = 6, 12 » 10, result;
. ; .
result = il * 12--;
printf ( "result == '/.d; 12
The greater precedence of over * ensures that the compiler does not
turn the statement into result
because the expression in
=
parentheses
(il * i2)
is
— ;. This would be invalid anyway
not a variable.
Using the in its postfix form just means that the original value ( 10) of
i2 is used in the multiplication, rather than the lower value (9) after
decrementing.
Loops in C
Computers are good at doing something over and over again,
particularly
perhaps with slight variations. Such repetitions, or iterations, are called loops.
The loop construct is very common in programming.
The main type of loop in C is the while loop, which comes in two forms
and in a more compact variant, called the for loop. The following listing
illustrates a while loop:
main ()
il - 1;
while ( il <- 10)
<
rmming.sura += CUBE ( il);
printf ( "•/,2d: CUBE == '/.4d; running.sum == %4d\n"
il CUBE ( il) , running_sum) ,
++il;
Controlling C 187
CUBE
i ,
The program carries oui each of the actions in the body of the loop. Then
the program goes back to the top of the loop, and checks the continuation
condition. If this condition is still true, the program carries out the actions in
the loop once again.
The increment statement, ++il, is a common way of modifying the
counter variable in a loop because most loops simply change the counter by 1
il = 1; 4 /* Initialization */
while ( il <= 10) 4 /» Continuation Condition */
{ < /* Loop Body */
ruiuiing_sum += CUBE ( il);
printf ( "•/,2d: CITBE == '/,4d; ruiming.Bum == '/,4d\n"
il , CUBE ( il). ruiming.sum)
+*il: * /* Ctiange in looping variable */
}
the test to determine how long the loop continues. Unless you deliberately
want to create an infinite loop, make sure your continuation condition can
become false. The expression in ordinary continuation conditions will always
involve the looping variable. The loop body contains the actions to be done
when executing the loop.
These actions are the main purpose of the loop; the other components are
really administrative, to make sure the loop executes the proper number of
times, and with the correct values.
;
Controlling C 189
Finally, the change in the looping variable is designed to ensure that you
get closer to making the continuation condition false each time you go through
the loop. If you are changing the looping variable explicitly in the loop, make
sure you change the looping variable in the direction of the continuation
condition's boundary, or ending, value (in our case, 10).
^REMEMBER The four major components ofa while loop are: (l)initial-
ization of looping variable; (2) continuation condition; (3) loop body; (4)
change in value of looping variable.
In our illustration, the looping variable was changed at the bottom of the
main loop body. You can change it anywhere in the body. Be aware, however,
that changing it in different places may affect what happens in the loop. The
following example moves the change to the top of the loop from the previous
example:
main ()
<
int il, running_sum = 0;
il = 1;
while ( il <= 10)
{
++il;
running_sum += CUBE ( il);
printf ( "'/,2d: CUBE == '/,4d; running.sum
il. CUBE C il) running.sura)
,
CUBE
190 Using QuickC
this version of the loop displays cubes and sums of cubes for the integers 2
through 11, whereas the previous program displayed for through 10. 1
Be careful when specifying your loops. The initial value for the looping
variable, the form of the continuation condition, and the place where the
looping variable is changed can all affect the outcome of the loop. To convince
yourself of this, try running this program with each of the variations listed in
Table 6-2. The first two cases correspond to the examples we've already done.
Study the behavior of your while loop, as you try these variations. They
illustrate the issues and problems that might arise in relation to the boundary
cases— that is, the first and last times through the loop.
Initial Looping
f :
Controlling C 191
main ()
{
int ch;
If you type the first few letters of the alphabet in order, this program
produces the following output:
aabbccddeef
The while loop in this program has no initialization for the looping variable
(ch, in this case). The looping variable is changed, but not necessarily in a
manner that will eventually make the continuation condition false. Essentially,
this program relies on the fact that the user will eventually get tired and end the
input. Later, we'll read information from files, generally using a loop to do so.
In those cases, we'll rely on the fact that the loop will eventually encounter an
end-of-file character, at which point the loop will stop.
The while loop tests the continuation condition at the top of the loop, if
the condition is from the start, the loop does not execute. Thus, a while
false
loop executes zero or more times. There is a version of the while loop that tests
its condition at the end of the loop body-after the loop has executed. We'll look
/* Program to prompt user until a value between 1 and 10 has been entered */
main ()
{
double val
do
{
printl "Please enter a number between 1 and 10 ")
(
scanf ( tval)
'"/.If",
if ( (val < 1) II ( val > 10))
printl ( "Number must be between 1 and 10\n");
}
while ( ( val < 1) || ( val > 10));
printf ( "You entered '/.If \n" val);
,
This program prompts for a value between 1 and 10, looping until the value
entered falls within those bounds. The program then displays the value read.
This loop will always execute at least once because the program will not
know what the continuation condition is until the program reaches the while
statement at the end of the loop body. If the continuation condition still holds
(that is, if the value that has been read is outside the desired range), the
program goes back to the top of the do-while loop and tries again.
The syntax for the while portion of the do-while loop is the same as that of
the regular while loop: the keyword while, followed by a continuation condi-
tion in parentheses. This while statement is at the end of the do-whiie loop
unlike its position at the beginning of the regular while loop.
Notice the if statement nested inside the loop body. This is allowed
because control constructs can be nested inside any other. It is just a coinci-
dence that the conditions for the if statement and the while construct are the
same.
Programmers use the do-while version of the while loop less frequently
than the regular form, but, under certain conditions, it is handy.
^REMEMBER The while loop tests at the top and may never execute; the
do-whiie loop tests at the bottom, and will execute at least once.
you are familiar with Pascal, you may recognize the do-while loop as
If
main ()
; ; ; ; ;: : ; : ;
Let's look
another example using the for loop: a program using the
at
loop to compute Centigrade temperatures corresponding to certain Fahren-
heit values. Both temperatures are displayed in a table. The particular tempera-
ture values computed depend on the starting and ending values, and on the
increment between successive values to be computed.
main ()
<
double cent, /* current temperature in Centigrade. */
val. /« value currently being converted */
start, ending, /* starting and ending values to convert */
increment: /• amount by which to change current val */
/* get information */
printf ( "Starting value? ")
scanf ( '"/.If". Sstart);
printf ( "Ending value? ");
scanf ( "'/.If", lending);
printf ( "Increment? "):
scanf ( "Xlf", tincrement)
introduce program ()
This version does no checking, so it's up to you to make sure your starting,
ending, and increment values produce sensible results.
starting va
; ;
In a sense, the for loop simply takes these three elements from the specific
places where they would ordinarily be found and groups them at the top of the
loop. Thismakes it much easier to make sure you've included all three
components, and that they are correct. They are far more prominent grouped
at the top than if they were distributed in the code.
When the program encounters a for loop, the following actions are taken:
false, the loop terminates, and no further action is taken in the loop
body or in the expressions.
4. After the loop body has executed, the third expression is processed.
This will generally change the looping variable.
5. The program goes back to the top of the for loop, and repeats the cycle
beginning with step 2 (testing the continuation condition).
Let's look at one more example using the for loop, an example that prints
a portion of the ASCII table:
main ()
{
count = 0;
for C curr_char = 32; curr_char < 127; curr_char++)
display ()
>
"
32 33 ! 34 35 # 36 $
37 X 38 ft 39 '
40 ( 41 )
42 * 43 + 44 , 45 -
46 .
47 / 48 49 1 50 2 51 3
82
;
-
int ch, count =0; /* valid for count == 1 */
'
'
val »= -1;
printf ( "\ninteger built == '/.ld\n" , val);
This program reads character input, and tries to build a long int from the
input. It displays whatever number it was able to build, and beeps at the user if
the input is an invalid character (that is, anything other than a digit, or a '—'in
the first position).
The main control loop is a for statement. However, this time the first
expression initializes the long int being built rather than the looping variable.
The continuation condition in this example is similar to the one we saw in a
while loop above where the loop continued until a certain character was
encountered. Finally, there is no third expression. Any of the expressions at
the top of the for loop can be omitted, if the loop's logic allows or requires this;
the compiler will not complain.
Let's look at some of the other features of this program. There are some
preprocessor definitions, which we haven't been using much in recent listings.
The first manifest constant specifies the ASCII code for the beep. It's much
easier to understand a piece of code that says to display BEEP than one that
Controlling C 199
value can be found by computing its offset from the ASCII value for '0'. For
example, the character '8'
has ASCII value 56. If you subtract 48 (the ASCII
value for '0') you'll get the digit's numerical value, 8.
The next statement in the if construct adds this numerical value to the
number that has been built so far, that is, to the current value of val. As each
new digit is read, it becomes the rightmost digit in the number being built. This
means the current number needs to be moved one place to the left that is, —
multiplied by 10 —
before adding the new digit in the I's place.
If the character read is not a digit, the program tests whether it might be a
minus sign. The minus sign is acceptable only as the first character. If this is the
case, the program sets negative to TRUE.
Finally, the last condition in the if construct (neither digit nor leading
minus As soon as such a character is encoun-
sign) catches invalid characters.
tered, the number building process comes to an end. Earlier, we saw that the
break statement immediately got you out of a switch construct. Here, the
break statement gets you out of the for loop, and back to the next level in the
main( ) function.
Here's a list of the main actions performed in building a number from
character input. The two major actions are: adding this digit in the proper way
to the number (the assignments involving val), and determining the digit (the
ch =
Controlling C 201
lent to have multiple expressions in the for loop. The following example
illustrates the comma operator (,) that provides such a capability.
/* Program to count up from 1 to 20, and down from 20 to 1 at the same time.
Program illustrates use of the comman operator.
•/
main ()
<
int up, down;
up++, down--)
printf ( "up y,5d; down '/,5d\n" , up. down);
This program counts up from and down from 20 to at the same time.
1 to 20, 1
The program uses a for loop to accomplish this. The approach is to have two
counters that are changed and used independently. The comma operator
makes it easy to specify multiple steps in each of the expressions at the top of
the for loop.
In this program two variables are changed in both the first and the third
expressions at the top of the for loop, with the assignments separated by the
comma operator.
The operands for the comma operator are evaluated from left to right: up
= 1 then down = 20. Values returned by operands of a comma operator are
discarded, except any value returned by the rightmost operand. The only effect
of the assignments is to store values in the respective variables. For now, think
of the comma operator as a convenient tool for multiple assignments . Later,
some other uses for this operator. The comma operator has the lowest
we'll see
ment is useful:
main ()
{
int count;
{
if ( (count 2))
'/a /* for odd numbers */
continue
else if ( (count % 4))
continue;
else if ( (count '/. 6))
continue
else if ( (count "/,
8))
continue
else if { (count '/, 10))
continue
else if ( (count '/. 12))
continue
else
printf ( "° /ed\n" , count)
}
printf ( "upon exit, count == '/,d\n" , count);
}
120
240
360
480
upon exit, count == 501
Controlling C 203
This program writes out the values of all numbers divisible by each even
number between 2 and 12. The logic is to eliminate a number as soon as one of
the divisors leaves a remainder. Thus, if the number is odd, there is no point
testing 2, 4, 6, etc. if the number is not divisible by 8, there is no reason to try 10
and 12.
Once a number is eliminated, we want to try the next number in the
sequence. That is, we want to go back to the top of the loop and try the next
value. This is exactly what the continue statement accomplishes.
Notice the expressions being used to test each if statement. The tests are
asking whether the resuh of the modulus operation is a nonzero number,
whether division by the number leaves a remainder, if it does, then the number
does not divide the current value of count. We could just as easily have phrased
the tests more explicitly. For example, ((count % 2) == 1)) tests the same
thing, in a manner that is less compact but easier to read.
Contrast the effects of the continue statement with the same program, but
using a break statement instead.
main ()
204 Using QuickC
encountered. Since this happens when count takes the first odd value (1), the
program ends very quiclcly.
Summary
In this chapter, you've learned about control constructs in C, and have been
introduced to several new operators that are often used in these control
constructs.The two major categories of control construct are selection (if-else
and switch) and iteration (while, do-while, and for).
Most of the operators covered in this chapter are commonly used at
various points in selection or iteration constructs. For example, the relational
(<, <=, >, >=) and equality (== and !=), and the unary negation operator
(!) are often used to create simple tests for an if statement or for the continua-
tion condition of an iteration construct.
The logical operators (&& and II) are used to connect expressions into
longer, more complex conditions.
The increment (++), decrement ( and compound assignment opera-
)
tors (+=, — =, *=, /=, and %=) are used to change looping variables in
iteration constructs. Remember, the increment and decrement operators can
behave differently, depending on whether you use a prefix or a postfix form of
the operator. Finally, the comma operator (,) makes it easier to perform several
assignments or to make several changes in one place (such as at the top of a for
loop).
In the next chapter, we'll look at functions in more detail. You'll find out
how to define functions with parameters, and how to return values from
functions.
Functioning in
A major advantage C gives you is the ability to create function libraries and
then programs using only those library functions needed to accomplish the
tasks the program requires. However, so far we've been using functions
sparingly. That will change beginning with this chapter.
205
206 Using QuickC
we've been using the values returned by library functions in various ways. For
example, the fgetchar( ) function returns an int that represents the character
read by the function.
C Functions
Think of C functions as specialists that you call when the need arises. A
specialist is someone with a particular collection of skills to perform specific
tasks. A function, then, is a collection of instructions (statements) designed to
compute a particular value or to accomplish a task.
Just as specialists can save time and effort, functions can save you
programming and debugging work. If there is a function available, call it to do
the work. If there isn't a function to do what you need, write one. Once written,
you can use it over and over again in other C programs.
For example, you might need to alphabetize several lists at different
points in a program. You could do this without using functions by writing the
instructions in the main program for each separate instance of alphabetiza-
tion. Suppose it takes 50 lines of instructions to alphabetize, and suppose you
need to do this 50 times in the program. That means 2500 lines of instruction in
the program. A 2500-plus line function is a clear example of bad programming
style. Short functions are much easier to write, understand, and debug than
If you alphabetized the same Hst each time, you could replicate this code
easily by using the copy facility in your editor. Most likely, however, each
situation would be slightly different. You might, for example, have to go in and
change the variable names, or the number of elements to be sorted, and so on.
At best, accomplishing 50 sorts in the program would require you to copy
something 50 times. Things are unlikely to be so simple, however. You will
probably have to change some proportion of those lines. Even if you had to
change only 30% of the lines, that would be 750 lines — lots of time, and lots of
opportunity for making mistakes.
if you had a function for alphabetizing lists, you could simply call the
function, give it the list you wanted alphabetized, and the list will be returned
in the desired order — you just hand over the job, then get it back when it's
done. The great thing about having specialists on call is that they're good at
doing their job, and they'll worry about the details of whatever needs to be
done.
You don't need to know the details of how a function does its job, only
how to ask it to do the work. Again, this is similar to using a specialist: you
really need to know just enough about the task to be able to specify correctly
what you want done. You also need to know what information you are going to
get back from the function. We'll look at both of these issues how to ask for a —
function and how to deal with what it gives you —
but first, let's look briefly at
what happens when you call a function in the program.
Flow of Control
When your program runs, it carries out one instruction after another. The
program carries out instructions in the order in which they occur in the source
code, unless you specify otherwise, with a selection or iterative construct. Let's
look at an example of program execution.
printf
printf
printf
printf
printf
printf
printf
printf
; ; ,
Functioning in C 209
When this program runs, it simply carries out the instructions one after
the other. That is, the sequence in which the program executes is from
statement 1 through statement 22.
Notice that the same set of five instructions appears three times in the
program (statements 3 through 7, 10 throughThe only
14, and 18 through 22).
real inconvenience this repetition caused was having to put statement numbers
on each occurrence. The actual statements were easy to copy with the editor.
As the entire program is only about 25 lines long, looking at the source
code is not very daunting, nor is the extra work required to number the lines.
But suppose we had displayed this information 100 or more times. We would
have a very boring program. At first glance, you might feel overwhelmed by
which would be well over ten pages long, even though
the source code listing,
the code would consist of the same lines repeated over and over again. You
might, justifiably, be reluctant to read such scintillating code; the prospect of
writing the 800 or so statements required is even less appealing.
The use of a function can help make the task easier. The following
example shows how, and will also let us explore the flow of control when
functions are called.
display ()
printf ( "Xd\n". 1) /• 11 •/
printf ( "Xd\n", 2) /• 12 •/
printf ( "Xd\n". 3) /• 13 •/
printf ( "Xd\n". 4) /* 14 */
printf ( "Xd\n", 5) /* 15 »/
This program performs exactly the same task as the preceding one. The
210 Using QuickC
only difference is that the sequence of instructions to display the numbers has
been made into a function, which the main( ) function calls at the appropriate
times.
When this program runs, starts executing the instructions in sequence.
it
succession.
As you already read in Chapter 4, there are differences between a prepro-
cessor macro call (which actually substituted the macro body into the code
when the macro call was encountered, before or during compilation) and a
function call (for which the code would be loaded when needed by the running
program). This loading process, and the time required to accomplish it, is what
the discussion there was about.
The flow of control in this program is: 1-3, 11-15. When the function is
calling functions. Thus, it will almost always be the main program that ends
execution, even though it may have nothing more to do after the last statement,
if that's a function call.
Functioning in C 211
Often, a function called by the main program will, in turn, call other
functions. The same process will be repeated at this inner level. That is,
/* 3a
printf ( "\nYou didn't catch that?\n"); /* 4 */ /
printf ( "Oh, all right. I'll count again. \n"); /* 5 •/ (
display () /* 6 */ t
/* 6a */ i
printf ( "\nYou STILL didn't catch that?\n"); /* 7 */
printf ( "I'll do it one more time.\n"); /* 8 */ II
display ()
<
printf ( »%d\n". 1) /• 11 */
printf ( "Xd\n". 2) /* 12 */ I
printf ( ")id\n". 3). ....
/* 13 •/ „
5
interruption ;
/* 14 */ i
/* 14a */ :
print! ( "W\n». 4); /* IB */
printf ( "Xd\n". 6); / 16 •/
interruption ()
<
printf ( "We interrupt this display to announceAn") ; /* 17 */
printf ( "The display will continue now.Nn"); /• 18 */
}
This program displays the numbers, interrupted by two lines of te.xt each
time. The output is shown in the next listing:
Hello, world.
I'm going to count for you
212 Using QuickC
3
We interrupt this display to announce:
The display will continue now.
The flow of control in this program is as follows: 1-3, 11-14, 17-18, (14a),
15-16, (3a), 4-6, 11-14, 17-18, (14a), 15-16, (6a), 7-10, 11-14, 17-18, (14a), 15-16,
(10a).
The interruption( ) function returns control to display( ), rather than to
main( ). This is because display( ) (and not inain( ) ) called interruption( ). In a
sense, the main program doesn't really know anything about interruption()
because main() has nothing directly to do with that function.
In keeping with the analogy of specialist, you can think of function calls
by functions as subcontracting to other specialists. The original contractor
need not know anything about these subcontracts, just as the calling function
doesn't know anything about the functions called by its specialists.
Function calls are just Hke other C statements, except that the function
call is likely to accomplish the work of several statements. Functions can be
used in conjunction with all C's constructs, including the selection and itera-
tion constructs. Let's look at a few examples.
#define MAX.DISPUY 5
display
{
printl ( "Hello\t");
>
main ()
{
int index;
Functioning in C 213
This program writes iiello five times on a line. In the next example, C
functions are being used as alternative actions in an if construct.
#dellne MAX.SIZE 10
#deflne URGE CUT 1000.0
tdeflne FACTOR 1.0
nainO
<
double start • 2.0;
Int index:
arge.val ()
printf ( "LARGE\n"):
mall.val
prlntl ( "SMALLSn");
In this program, the functions small _val() and large _val() just write
their size, depending on the value of start. Notice that the function calls
Function Returns
As you've seen already, you can use C functions in two different ways: by
doing something with the value returned by the function or by having the
function perform some action, but discarding or ignoring the return value, if
any. So far, the functions we've defined (as opposed to some of the library
functions we've used) have all been of the second type — where the program is
not counting on any returned value from the function. For example, the
display( ) function in the "counting" programs above just does some work for
main ()
{
#define MAX.COUNT 15 /* maximum number of times through loop. •/
and 1.0. The program works by calling a function, zero _ one _rand(), we've
defined for the purpose. This function returns a value of type double, as
indicated by the definition of the function and its declaration in niain().
Functioning in C 215
Let's look at how the zero one _ rand( and return work.
) function call
When the arguments to printf() are evaluated, the program finds a call to
zero one rand() and transfers control to this function, which computes
and returns a double. The value returned is substituted for the placeholder in
the string argument to printf( ).
The function zero one rand() works in a manner similar to the
rand( ) library function we saw earlier. The library function returned a pseudo-
random integer between and 32767 (the largest integer in QuickC). The
zero _one rand( ) calls rand( ) for such an integer, and then "standardizes"
the integer returned by rand( ) to a double value between 0.0 and .0. The result 1
in the function is done. Then, control reverts to the calling function. If the
function includes a return statement, but without an accompanying expres-
sion, control reverts to the calling function right after the return.
You can put return statements anywhere in a function. For example, you
might want to return different values, depending on the conditions that hold in
the function or the results of a computation. Control reverts back to the calling
function right after the first return statement is evaluated. The value of the
function — that is, the value returned by the function -is the result of the
expression for the return. C ignores any statements after a return in the
execution of the function.
The definition for zero _ one _ rand( ) is more elaborate than the defini-
tions we've seen in previous chapters. First, notice the function declarator for
zero _ one _ rand. The function name is preceded by a type specifier, double.
216 Using QuickC
This specifier indicates the type of value returned by the function. If the type
specifier is absent, the function is assumed to return an integer. Functions may
return values of any of the types we've seen so far, as well as types we haven't
yet covered.
^REMEMBER
• A return statement effects an exit from a function, and transfers control
to the next statement in the calling function.
Sometimes you will want to state explicitly that a function does not return
a value. There is a special type specifier, void, for this purpose. To indicate that
a function does not even return an int (the assumed default, if no return type is
specified), write void before the function name in the function heading. The
dispiay( ) function in the "counting" program would be defined as follows:
void display ()
{
printl (
Functioning in C 217
because the only possible values for the numerator would be values between
and 32767, none of which is more than the twice 32767 needed to produce a
quotient greater than 1.
Earlier, we looked at two versions of a program that counts to five and displays
the results on the screen. Suppose you wanted to count to different values each
time, again displaying the results. One way to accomplish this would be to go
back to the original program, and simply call printf( ) as often as desired at
each point. Again, that could become tedious to do. An ahernative would be to
modify the definition of the display( ) function to you specify how high it
let
should count each time you call it. This solution would use a parameter to
specify the target value each time you called the function.
Basically, a parameter is a slot through which you can pass information
when you actually call the function. This information will let you have some
control over how the function executes. Let's look at an example:
int count:
Hello, world.
I'm going to count for you
This program counts and displays the results in a manner similar to the earlier
programs. The difference is that the program counts to different values each
time displayO is The flow of control in this program, at least inside
called.
function display( ), also is somewhat different this time.
The calls to display( ) now include an argument, the value to which the
function is to count and display. The definition of function displayO also
differs from the functions we've seen so far. Corresponding to the argument,
Formal parameters are declared outside the main function body. The
storage and the names for the formal parameters exist only while the function
is in memory and running. Once the function has finished and is discarded, its
variables and parameter slots also disappear. You may use any valid identifier
for the formal parameter. This identifier will be the slot's name within the
context of the function.
The following illustration identifies the components of a function declar-
ator. The declarator has three main components: a return type, a function
name, and a formal parameter list. Each formal parameter included in the list
Function name
i
void display (how.olten)
int how.often;
h "t
Formal parameter list
When you call a function with an argument (an actual parameter), the
program copies argument value into the storage allocated for the corres-
the
ponding formal parameter. Thus, the function works with a copy of your
argument. This means that the function will not change the variables you pass
as arguments.
The information flow via the parameter list is one-directional, in what we
have seen so far. Because the function only gets a value, rather than an actual
variable, such parameter passing is known as passing by value. We'll look at
can use the same identifier in different contexts. The name you use for the
formal parameters need not be the same as the name of any variable in the
calling function or in the program. The name and the storage associated with it
void show.global.val ()
{
printf ( "Xd\n" , global.val)
>
The show global val() function can print only the current value of
global — val. When you want to use this function, you first need to store the
value you want to display in global — val, then call the function. This is
inconvenient at best, and could be a real problem. Suppose the function calling
show — global — val() needs to do something with the current value of
global val immediately after displaying some other value. To do this you
would need to store the current value of global — val somewhere, assign the
value to be displayed, call show — global _val(), then restore the value you
want to use.
With parameters, on the other hand, you can display the contents of
global val or any other integer variable, just by calling show any — val()
with the appropriate argument. Because the argument is passed by value, the
current value of the parameter variable in the calling function will not change.
Parameters offer both control and flexibility. We'll use them extensively in the
Functioning in C 221
return ( IHVALID.VAL)
>
main ()
{
•define MAX.COUHT 10
double reciprocal O, /' declare function, so main can use »/
double count;
This program displays the reciprocals of values between — 10.0 and 10. The
main( ) function calls reciprocal( ) for this information.
Function reciprocal() has one formal parameter, val to_invert, and
returns a value of type double. To avoid division by zero, which produces a
floating point error, the function checks the actual parameter passed in — that
is, the value copied into val _ to — invert. If this value is 0.0, the function
returns a predefined value (INVALID —VAL), which indicates an error.
The definition for this function has the same format as the definition for
display( ) with a parameter. The major difference between the two functions is
Functioning in C 223
The compiler also won't know if you've called the function with the wrong
type of parameters. Therefore, the compiler will make the substitutions you
request, which may cause type incompatibilities when the function being
called starts running.
Let's look at one more example, which will also let us see the differences
#define MIN.VAL 10
main {)
{
int pref post index
, , , argument
int leave_alone ();
int quadruple () , one_fourth (); /* for suggested exercise */
printf ( "Index\tPrefix\tPostfix\n\n")
for ( index • 20; index >= MIN_VAL; index--)
<
argument index;
pref » leave.alone ( --argument);
argument index; /
to make sure same value is passed */
post leave_alone ( argument--);
printf ( "y.3d\tX4d\tX4d\n" index, pref, post);,
The program writes a series of values and the corresponding value when
certain variants on the index are passed to a function that just returns the value
it was passed.
Notice that the prefix form of the decrement operator makes its change
before the value is passed to the function as an argument. The postfix form
makes its change after the value is passed.
This program would compile, but would run incorrectly, we passed a if
double into leave _ alone( ). The compiler has no way of knowing that the
argument to the function is of the wrong type, because the function declara-
tion did not provide any information about the parameters. Failure to check
for such mismatches can cause incorrect results.
In the next chapter, you'll find out about a concept designed to provide
the compiler with much better checking capabilities in such situations.
Summary
In this chapter we've discussed the fundamental properties and uses of func-
tions.You learned how to return a value from a function, and how to define
functions that return such values. You also learned how to use parameters to
make functions more flexible. The more flexible a function, the more widely it
can be used.
Functioning In C 225
Function
Prototypes
Let's look at the program from the last chapter again. This time, let's see what
happens when we pass the functions bad arguments.
u
/* Program to illustrate consequences of incorrect use ol function parameters.
*/
define MIH.VAL 10
double argument;
int leave.alone ();
printf ( "Index\tPreflx\tPoBtfix\n\n")
lor ( index - 20; index >• HIN_VAL; index--)
{
argument index;
pref leave.alone ( --argument);
argument • index;
227
; ; ;
printf ( "\n\n\nIndex\tPrelix\tPostfix\n\n")
for ( index = 20; index >= MIN.VAL; index--)
{
argument = index;
prel = leave_alone ( --argument, index);
argument = index;
post = leave_alone ( index, argument--);
printf ( "X3d\t'/.4d\t'/.4d\n" index, pref post);
. ,
index
;
In the second part, the call to leave alone() has the wrong number of
arguments. The compiler simply starts processing parameters in its accus-
tomed sequence, and stops as soon as it has the required number of arguments.
Because the arguments were passed in different sequences, the results are
different.
previous listing:
reciprocal ) with the wrong number of parameters, the compiler will warn
you.
In the preceding program, check what happens when you call the func-
tion reciprocal ) in main(), with two arguments. Now, modify main() by
including a function prototype instead of a simple function declaration for
reciprocaI( ). Then check the result of calling the function with two parame-
ters.
Using a prototype also makes the compiler better at dealing with mis-
matches between types of the argument and the corresponding formal
parameter. For example, if you call reciprocal ) with an int instead of a double,
you will get unpredictable results using a simple function declaration. If you
use a prototype, however, the compiler will convert the type of your argument
to the formal parameter's type, as long as this conversion would be valid in an
assignment statement.
Run the following program twice. The first time, run the program as is;
the second time, change the function prototype form to a simple declaration of
reciprocal( ). With the prototype, the compiler knows that the function is
: ; ;
main
C
#define MAX. COUNT 10
double reciprocal (double):
double count
int test:
When the compiler sees void in this context, it will know that the function
The fact that the compiler checks type and number on arguments when
you use prototypes should not encourage careless function calls. Rather, you
should use prototypes to take advantage of the kind of things compilers are
very good at —
catching inconsistencies —in order to weed out errors.
The Draft Proposed ANSI Standard is moving the C programming
environment toward prototype use. Currently, simple function declarations
are used almost exclusively. They are allowed in the Draft Proposed ANSI
Standard largely for the sake of existing programs, but are considered obso-
lescent by most programmers. Simple declarations are expected to disappear
from use eventually. In the remainder of this book, well use the function
prototype form.
if ( non.zero ( denom))
return ( numer / denom)
else
return ( INVALID.VAL)
double zero_one_rand ()
main
This program, which was asked to compute ten ratios, produces the
following output for a sample run:
ratio
_
nothing is specified between the parentheses, the compiler knows that the func-
tion takes no parameters; the compiler will not confuse this with an ordinary,
non-prototype declaration. That's why you don't need to put void here.
In fact, you cannot put void between the parentheses for such a function.
You will get a compiler error. Essentially, the compiler expects to see either a
non-keyword identifier or a type specifier/ identifier pair. If it finds just an
identifier, the compiler will treat your declarator as being of the non-prototype
sort.
^REMEMBER When using the prototype form of the syntax for specifying
formal parameters in the function declarator, a type specifier must be paired
with each formal parameter identifier, and multiple type specifier/ identifier
pairsmust be separated by commas. You may not use void in a function
heading to indicate that the function takes no parameters. You may use void to
indicate this when declaring the function for use in another function. (You may
also use void in a function heading to indicate that the function returns to no
values.)
Let's look at each of the functions and then work through the program's
execution. This will give us a chance to see how the program works and also to
raise some additional points about the code. We'll leave the discussion of the
non zero() function till last, because that function raises additional issues.
The safe division( ) function returns a quotient. The function first calls
non _ zero( ), to check whether the denominator is too close to zero to allow
division. If that is the case, the function returns a predefined error value,
INVALID_VAL.
Keep in mind that the zero _ one _ rand( ) function returns a pseudoran-
dom value between 0.0 and I.O.
The main() function does the bulk of the work -calling zero _ one
rand( ) and safe _ division( ) for its values. Notice the statement that assigns a
Function Prototypes 235
value to mean ratio. This assignment calls the safe division() function.
The first argument is a double; the second is an int. Because we've used a
function prototype to declare safe_division() inside main(), the compiler
makes the appropriate type conversion before passing the value of index into
the function. In addition, the decrement operator! ) isapplied to the value
of index before it is passed into the function — because we used the prefix form
of the operator. The value of index must be decreased by one because it is one
more than it should be (11 instead of 10).
The other function in the program, non zero( some noteworthy
), raises
issues when using floating point types in programs, especially in loops. The
function returns TRU E if the argument that was passed to differs from zero it
Such a "tolerance" test can be useful you have a loop where the
if
continuation condition tests strict equality of floating point types. For some
values, it's extremely unlikely that you will end up with identical bit patterns in
two floating point variables if you compute these values by different means.
Testing whether the variables are equal is, therefore, unlikely to produce
an affirmative response. The following program illustrates this. The first for
loop main program uses the non_zero( ) function to test for equality.
in the
This loop stops after three iterations. The next for loop looks for strict equality.
This version just keeps going.
_ val as the program
Use the QuickC Debugger to watch the value of test
executes, by setting debug to on in the compiler options before adding test _
val as a watch variable. The value is supposedly equal to that of VERY -TINY
at some point, but the loop keeps executing because the zero difference
reported was based on the precision you asked for, whereas the actual bit
following program.
main ()
{
double test.val;
double factor .03;
int non.zero ( double)
for ( test val - .00001; test val I- VERY.TINY; test.val *" fact
printf ( "%20.151f; '/.25.201f \n" ,
mathematically equivalent to testing whether the two values differ from each
other by less than TOLERANCE. This means you don't need a separate
function to test for approximate equality of two arbitrary values.
The second point to notice about the use of the non zero( ) function is
its use in the for loop's continuation condition. Recall that non zero()
returns an integer: TRUE if the argument is non-zero, FALSE otherwise.
Recall also that the continuation condition is simply a true-false question. As
long as the program finds a true value when it tests the result of calling
non_zero( ), the loop continues. Once the difference between text val and
VERY_TINY becomes less than TOLERANCE, the function returns
FALSE and the loop stops.
Function Prototypes 237
If you are used to programming in a language such as Pascal, you may have
noticed that we haven't nested any functions inside of others —a practice
common among Pascal programmers. There no nesting of function defini-
is
tions in C. All functions are at the same "level." The most common ways for
functions to communicate with each other is by means of
Our examples and discussion have been concerned only with int and
double variables, although C functions can also return other types, which will
be discussed after we've covered some requisite material.
Summary
in this chapter, we looked at the function prototype, a mechanism provided in
the Draft Proposed ANSI Standard. Function prototypes are essentially
elaborate function declarations that contain explicit information about
number and type of parameters, while specifying a return type or void.
By using function prototypes, the compiler is provided with the informa-
tion it needs to do much more extensive program checking than current
compilers provide. The concept of a function prototype is new to C, but it is
expected to become the standard style. We'll use the prototype form through-
out the rest of this book.
Scope, Lifetime;
and Storage Class 'I
3«
In this chapter you'll learn about the rules that determine when and how you Vii'
variable when the program moves to a new context, such as a call to a different
function.
239
. ;
; , ; ; ;
long, the program does little but display a lot of information. Work your way
through it, pay attention to the names being used (identifiers) and the values
being displayed, along with the location of each variable definition. Notice
what happens to int val in different parts of the program. Compare this with
what happens to int_val2 in the same program portions.
main ()
int.val2)
int.val2 = int.val; /* global gets value of local */
printf ( "*** in inner.fnO, after assignment, int_val2 =» '/.dXn"
int.val2)
return ( int.val)
, ; . , , ,
/• Function name comes from fact that the function uses global (outer),
rather than local (inner) objects.
•/
void outer_fn
<
printf "\n*** in outer.lnO before assignment. int_val == '/,d\n"
( .
int.val) ;
int_val2) ;
int_val) :
int.val2)
The program involves four different variables, but only three variable
identifiers, because one name (int_ val) is used for variables in two different
places. To distinguish between each use, we need to look at the scope and
visibility of the variables. The scope of a variable is the range of contexts in
which the variable can be used. The visibility of an identifier is the range of
contexts over which the identifier is valid and refers to the same object.
Let's look at the variable result first. This local variable is declared in the
function body of main( ). (Local variables are those defined within a function
or block, as opposed to the global variables defined outside any function.)
Within this block, the identifier result refers to a particular object, or area of
memory. The and its associated area of memory are unknown
identifier
elsewhere in the program such as within function inner fn( ). Thus, if you
tried to use result in inner fn( ), you would get an error message about an
unidentified variable.
242 Using QuickC
•'REMEMBER The scope of a local variable (that is, one defined within a
function or block) is from the declaration point to the end of the function or
block.
The variable int vaI2 has a different scope. This variable is defined
globally, that is, outside of any function. Both inain() and inner fn() can
refer to int val2 and can use the value stored in this variable. In fact, both
functions change the variable's value.
The scope of a global variable (that is, one defined outside any function)
extends from the declaration point to the end of the source file. In this
instance, source file refers to both the actual file containing the definition and
any files that C reads because of an #include preprocessor directive after the
variable definition.
Now, let's look at the variable int val defined inside inner fn()— a
local variable, according to syntax. By our first rule of scope, this variable
should be accessible only within function inner fn(). This seems to be the
case, since the value of 5 for int val occurs only inside that function.
What about the global int val — that is, the variable defined at the top of
the program? According to our scope rules, this variable should be known
throughout the program. A look at main() and outer fn() supports this
conclusion.
But what happens to the global int _ val in inner fn( )? We just saw that
the local int val rules here. Does this mean the global int val does not have
any scope here? If so, this would violate our second rule.
As it turns out, both int vals have scope here. However, the two vari-
ables differ in visibility. The local int val has greater visibility, meaning that
the identifier in inner fnl is interpreted as referring to the memory location
associated with the local variable. Because of this, the local variable's identifier
is said to hide the outer identifier. This is why the memory location corres-
ponding to the local int val, rather than the location of the global int val,
Both you and the other Dennis Ritchie fall within the scope of my conversa-
tion. Locally, among your friends (in your block, so to speak), you have greater
visibility, so that when I use the name Dennis Ritchie, people assume I'm
asking about you.
I were to ask about Dennis Ritchie's new lan-
Suppose, however, that
guage at AT&T 1 would almost certainly get an answer about the
Bell Labs,
Dennis Ritchie who invented C. At AT&T Bell Labs, that Dennis Ritchie has
greater visibility than you do. In fact, your scope may not even extend to the
labs. Very possibly, people would not recognize my references to you
at the labs
when I asked about your new language. Thus, at AT&T Bell Labs, the Dennis
Ritchie who invented C has greater scope and visibility than you.
In the program example, both int val variables have scope in function
inner fn( ), but only the identifier for the local variable has visibility. If you
could find a different way of referring to the object associated with the global
name int _ val, you might be able to manipulate its value in function
inner _val().
cates storage for a value of the specified type, and associates the name you
provide with this storage. When you call the function with an actual parame-
ter, program takes the value you pass (or the value associated with the
the
variable you specify as the argument) and stores this value in the memory
allocated for the corresponding formal parameter. Let's look at an example:
main ()
<
void inner_fn ( int);
lnt.val2 - 20;
printf ( "at start of mainO . int. val (at V.u) -= '/,d\n"
ftlnt.val. int_val) ;
; ;
; , .
4int_val, int_val)
printf ( "in mainO, after inner.fnO: int_val2 (at '/.u) == '/.d\n"
tint.val2, int.val2)
This program produces the following output, including address locations for
the variables in the program.
The program is similar to the preceding one in that it prints the values of
several variables to illustrate points about scope and visibility. In addition, the
program prints the addresses of the variables involved, to make it easier to see
the relationships among value, memory locations, and names.
Notice the value stored inparam __ val, the object associated with the
formal parameter of that name. It is the same value as that stored in the global
variable, int _ val. When inner _fn( ) is called with an actual parameter, the
actual parameter passed by value. When main() called inner_fn(), with
is
int — val as argument, the program passed the value stored at memory loca-
tion 218 (in our program run) into the storage area set aside for param _ val
(location 4226, in our run). Because of this call by value, the actual contents of
the global int_val are not changed in any way by this function call.
We've passed the information contained in int _ val into the function
inner — fn() through the slot created for this purpose. As such, we've taken
advantage of the fact that inner _fn( ) is within the scope of a global variable
Scope, Lifetime, and Storage Class 245
in order to get information from this variable into the function. Once the value
has been passed inside, the function can do what it wants with the value,
without the calling routine knowing about it or being affected by it. The same
value is now contained
two different objects, as you can see by the addresses
in
of the two objects (218 and 4226, for actual and formal parameters, respec-
tively). Thus, the use of formal parameters is a means of getting around
about an undefined variable. This is because the scope of the formal parameter
does not extend to other functions.
Note also that the global and the local int_val identifiers refer to two
different memory locations. In the previous program, we saw how visibility
Defining Variables
Within Blocl(S
they were declared. The following listing illustrates this, and displays the
values of the variables at each level of the program:
main
<
int val =1; /* local to main () */
void other_f n ( void)
other_fn()
printf ( "6: val == Xd; val2 - '/.d\n", val. val2) ;
void other fn ()
{
int val2 - 2; /* local to other.fn () */
void yet_another_fn ( void)
yet_another_fn ;
1: val == 1: val2 -
val »- 0; val2 = 2
2:
3: val =• 3; val2 = 2
4: val "
0: val2 -
5: val —
0; val2 = 2
6: val —' 1; val2
This program simply displays the values stored in the variables val and val2.
The different values are due to the scope and visibility rules that apply when
printf( ) is called. The indentation in the program indicates the "depth" of the
call, while the number at the start of each line indicates the source code Hne.
Scope, Lifetime, and Storage Class 247
There are two cases to look at: what happens with identifiers within
functions, and what happens with identifiers across functions. Let's look at the
situation in other fn( ) first.
This function includes a local variable, val2, whose scope is from the
definition to the end of the function. Any reference to val2 inside other fn( )
is a reference to the storage allocated for the local variable. This is why calls 2,
3, and 5 to printf( ) all have the same value for val2. Notice the reference to this
variable in call 3 to printf( ). This call is just an ordinary statement inside the
function, as far as references to val2 are concerned.
The situation is somewhat different for references to val. An arbitrarily
defined block inside function other fn() includes a local definition of val.
The scope of this variable extends from its definition to the end of the
block —right after call 3 to printf(). The three calls to printf() in function
other fn() behave differently, because the identifier val has two different
visibilities in this function.
Using Debug
A big advantage of QuickC's environment is that you can do many different
things with yourprogram file. You can use QuickC's debugging facility to step
through the preceding program slowly, in order to study the source code as the
program produces its output. QuickC's debugger allows you to look at more
than just your selected watch variables. You can, in addition, use this capability
248 Using QuickC
to distinguish the different versions of val and val2 — by looking at the values
stored immediately after the watch variables. This should work since it's
unlikely that two different variables will be followed by the same values. To do
this,
2. Type CTRL-D, then C, to clear any breakpoints you might have set in
the program.
3. Type CTRL-D, then L, to clear any watch variables you might have
specified in the program.
5. Select the Debug option, if you haven't already done so. Then press
RETURN to compile the program with the debugging option on.
printf( The cursor must be on the line where you want to set the
).
breakpoint when you select this compiler option. You will set si.x such
breakpoints in all.
10. Use the F5 key to move to the next breakpoint when you're ready.
Study the changes to val and val2 in relation to the breakpoint's
location in the program.
printf (
: ;
Don't confuse storage duration and scope. Scope determines when you
can refer to a variable; scope says nothing about whether there is anything
stored in the object. Duration determines how long a value will be stored;
duration says nothing about whether the variable can be called at a particular
point in the program. If a variable has a static lifetime, then, at any point in the
program, the memory location allocated for the variable will contain the last
value assigned to the variable. You will only be able to access this information if
the variable's scope extends to the program portion where you want to access
it.
main ()
{
int gl, g2, g3, g4;
printf ( "hello\n")
}
You should have come up with five identifiers (gl, g2, g3, main, and
another) with static storage duration, and seven (including pi and p2) with
automatic storage duration. The three global int variables have the static
duration. Remember, functions have static duration and parameters have
automatic duration. The fact that the same identifers (for example, gl) are
used in several places doesn't have any influence on the storage duration.
Using the same identifier will only affect the visibility of the identifier.
Storage Class
The lifetime of a variable specifies how long the variable's value will be around.
Ordinarily, the values for local variables will simply disappear after the func-
tion is done. Sometimes, however, it can be useful to keep a value around for
use the next time the function is called. In terms of our discussion so far, such a
variable has a somewhat ambiguous status. On the one hand, you want the
identifier's scope to be local, which implies an automatic storage duration. On
the other hand, you want the variable to retain its value for use the next time
around. Thus, you want a local variable with static storage duration.
Another situation that may arise is when a particular variable is used very
often (in a loop, for example). If the variable is stored in ordinary memory, it
can be time consuming for the program to access the variable each time. C
provides a means of suggesting that frequently used variables be put in special
C's storage class specifiers gives you influence over how and where a
variable is to be stored when the program is executing. In this section, we'll
look at the storage class specifiers available in C, and you'll learn about some
of their properties.
Static
The static storage class specifier makes it possible to define a local variable
whose value continues to exist after the function has finished —so that the
most recent value of the variable can be reused the next time the function is
called. The following listing shows the syntax for such a specifier:
main ()
{
#deline MAX_COUNT 20
int c ount
void count_by_two ( void);
22 24 26 28 30
32 34 36 38 40
This program prints the even numbers from 2 through 40, five numbers per
line. The main program loop is in inain( ), but the value being written is local to
count _ by _ two( ).
The following illustration shows the state of the various program vari-
ables during the first few iterations through the loop in inain( ).
(0) (???)
true
2 false
2 false
(2) (???)
2 false
4 false
4 false
(4) (???)
4 false
6 false
6 false
Scope, Lifetime, and Storage Class 253
After line
254 Using QuickC
with storage class static. You can also use the static storage class specifier with
global variables. Global variables already have static lifetime and global scope,
so why would you want to use the static specifier? To answer this question let's
first consider the ways in which you can incorporate variables and functions
into your C programs.
First, you can put an#include preprocessor instruction into the program
source file and have the compiler read the contents of the file to be included
during the compilation process. The other way is to link a precompiled file
with your compiled program file, in the process of creating your executable
file. You might do this by linking in a function library that you have developed
or acquired elsewhere (without source).
When the compiler reads an included file, it makes any functions and
needed global variables in that file available throughout the rest of the pro-
gram. Because you are working with source files, you can easily check whether
a global identifier you want to use is also used in the included file —to avoid a
duplicate definition error.
When you link a precompiled file into your compiled you also make
file,
any functions and global variables in the linked file available to the program,
in this case, however, you may not be able to check what identifiers are used in
the linked file. Since you don't have the source, if your program and the linked
file each use the same name for a global variable, you will get a linker error.
To avoid this problem, you must use the static storage class specifier with
the global variables in the linked file. By defining these variables as having a
static storage class, you make them inaccessible to the linker. This means the
variables become "local" to the linked file. Whenever functions from the
linked file are called, they use the values specified in their file for these
variables; whenever your functions are called, they use the values specified for
your global variables.
For example, suppose I have two files, and prog.c. The main
utils.c
program and the functions unique to a particular program are in prog.c. The
file utils.c contains general utility functions that may be used by various
programs.
Suppose I have the following definition at the top of utils.c:
If the compiler reads the contents of utils.c while compiling prog.c, the
duplicate definition causes a compiler error — regardless of whether either
definition was specified as having static storage class.
If I compile utils.c to an object file, and then try to link this with the
prog.obj object file to create an executable file, the linker will report the
redefinition as an error. On the other hand, if I give the definition of test
count in utils.c static storage, and then create a utils.obj object file, the linker
will not report an error.
You can also use the static storage class specifier to modify a function
declarator. By specifying that a function definition has static storage class, you
are making the function inacessible to the linker. This means your programs
cannot use the function directly, but functions in the file containing the
function definition can use it. This capability is very useful for making libraries
more modular in a process often referred to as information hiding. Your
program is aware only of the library routine it needs to access, rather than of
each of the smaller functions the library routine uses to perform the task. In
this way, you can concentrate on what the library routine does, rather than on
how it does it.
compiler assumes auto storage by default. For this reason, you will rarely see
the auto specifier used explicitly. You might use this specifier when you are
deliberately using the same identifier as a global variable, and want to call
attention to this fact.
The register storage class specifier also specifies an automatic storage dura-
;: :
tion; in addition, this specifier suggests to the compiler that the variable be
stored in machine registers for faster access.
Generally, only integer type variables, such as char and int, can be given
register storage. The number of registers and the types that can be given any
available register storage will depend on your implementation. QuickC and
Microsoft C 5.0 will give a variable register storage, if there is a register
available and if the variable is of the appropriate type, int.
^REMEMBER You may not use the address operator (&) with register
variables.
The following listing illustrates the syntax for the register storage class:
main ()
countl ;
counts ();
/ No register variables. */
int r_sura
int index
The two functions perform the same task, but one of them uses register
variables. When these two functions were timed, count2( ) took between one
third and two thirds as long as countl() to perform the same task. The
differences were due to the faster access to values stored in register variables.
You can also suggest formal parameters for register storage. To do this,
simply put the keyword register before the type specifier in the function
declarator. The following function declarator illustrates the use of register
storage class specifier with formal parameters:
registers. As with other local variables, the compiler may or may not comply
with your request.
When you define a global variable, its storage class is extern. This means the
identifier and its associated object are accessible to any functions after the
declaration, in the same file or in any files read because of an ^include
instruction. Global variables have static storage duration. Such variables are
also accessible to the linker, unless you hide them by using the static storage
class specifier.
You may want to use a global variable — defined in one file — in a func-
tion defined in another. If the second file is read after the file in which the
global variable is no problem. If, however, you want to use a
defined, there is
variable before it has been defined, you need to warn the compiler that a global
definition will be available somewhere else. If the variable has not been
defined, and you have not declared it, the compiler will complain about an
undefined variable.
To avoid such an error, use the extern storage class specifier The follow-
ing listing illustrates this. (Note that this example is not a good one to follow
for your own programs. It's generally a bad idea to define your global variables
somewhere in the interior of a file. You're much better off, in most cases, just
putting the variable definitions before the variable is used.)
main ()
{
extern int val; /* extern needed because val not yet defined */
In this program, the variable val is declared at the end of the source file,
but is used earlier, by the function main( The first statement in inain( ) is a
).
variable declaration, not a definition. The compiler does not allocate any
when the compiler sees this line.
storage for val Rather, the compiler warned
that somewhere else there is a definition that allocates storage for an int
variable named val.
If inain( had consisted only of the printf( ) statement, with no declara-
)
tions, the compiler would have generated an undefined variable error. On the
other hand, if the keyword, extern, were not included in the declaration, the
compiler would have treated the line as a local variable definition, and val
would have been uninitialized.
It is sometimes a good idea to declare global variables at the top of
functions that use the variables. The extern storage class specifier makes this
possible. The explicit declaration is a safeguard, rather than a necessity, if the
function falls within the scope of the global variable. The declaration indicates
that a global variable is being used and is, therefore, a form of documentation
for your code.
Use the extern storage specifier if you are using include files that share
global variables. Many programmers define the global variables at the top of
the main source file, and then declare the variables at the top of each included
file —using the extern storage class specifier in each of these declarations. The
extern specifier prevents the compiler from allocating a second set of storage
locations for the variables, and also avoids a duplicate definition error. By
declaring the global variables in this way, you again provide a certain amount
of documentation by making explicit the fact that your code uses variables
defined elsewhere.
Scope, Lifetime, and Storage Class 259
Storage Class
260 Using QuickC
does not explicitly specify a storage class. Instead it is grouped with the storage
class specifiers largely for syntactic convenience, according to the Draft Pro-
posed ANSI Standard.
Also the typedef specifier does not actually define a new information type,
as its name might suggest. No storage is allocated when you create a typedef.
Rather, the specifier lets you provide a synonym for an existing type identifier.
This can be useful when you're writing programs for specific applications,
where specific vocabulary may make things clearer. The following examples
illustrate the use of the typedef specifier:
To create a synonym for an existing type, you need to preface the defini-
tion with the keyword typedef, then the type you want your new type to
represent (for example, double), and finally the new type identifier (for exam-
ple, progit). Note that the typedef simply associates a type identifier with an
existing type. You can use typedef to create either global or local types.
The first of these typedef specifications defines distance as a synonym for
double. Once you've specified the new type identifier, you may use it in variable
definitions such as the following:
The compiler will allocate storage when it sees these definitions. The amount
of storage allocated will be the same amount as would be allocated for a
variable of the underlying or base type.
Scope, Lifetime, and Storage Class 261
Notice that once you have defined a new type identifier, you can use the
other storage class specifiers in conjunction with your new type identifier, as in
the example declaring a static distance, miles so far. You can even define
or declare functions that return a value of the specified type, as in the function
declaration, km per tank, given above.
Thus, the typedef specifier allows you to specify new names for existing
data types. These names can help make programs easier to understand, by
making the program's language fit the content area, rather than imposing the
C language's vocabulary on your content.
Although our typedef examples so far have involved simple types, you can
use typedef to create synonyms for more complex types as well, including
arrays (groups consisting of elements that are logically related) and other
structures. In fact, some of the types that we'll be using later, such as the FILE
type, are actually names for more abstract types.
Summary
and status of
In this chapter, you learned about the concepts related to the use
C variables and functions, in particular, you found that scope and visibility
rules concern the range of program contexts over which an object or an
identifier has meaning. You also learned about the concept of storage dura-
tion, which determines the lifetime of a variable value in a program. Finally,
you looked at the storage class specifiers C provides, allowing you to influence
the lifetime and scope of variables and functions. One of these, the typedef
specifier, permits you to create identifiers that are synonymous with existing
types.
In the next chapter, you'll move into the use of pointers, which are, in
many ways, the real core of the C language.
; ;
Pointers
So far, we've referred to variables directly by name. We've used the variable's
identifier to refer to information stored at a particular memory location — the
location associated with that variable name. The location and the value of a
variable are not the same, as shown in the following program:
^'i
/* Program to display values and addresses of variables. */ ;
| Uj
main ()
{
int 11 - 100. 12 - 101;
double dl - 100.0. d2 = 101.0;
float fl = 100.0. f2 = 101.0;
263
264 Using QuickC
When compiled and run, this program produces the following output on
one system:
Hams
Pointers 265
In this section, we'll look at the difference between these two aspects of an
object. Let's look at an example where we have statements that use both the
location and contents of objects.
main
<
unsigned int il • 500, i2 = 300;
il - 100; /« 1 •/
12 - il; /* 2 */
printl ( "il - y.5u; 12 = y,5u\n" , il, 12); /» 3 */
12 - 11 + 2; /* 4 •/
il++; /• 5 •/
prlntf ( "11 - '/.5u; 12 - y.5u\n" , il, 12); /* 6 »/
>
In the first assignment statement, 1 , we are assigning the value 100 to the
int variable, il. In this instance, we want the location of il, not its value. It
doesn't make sense to say that, "500 is assigned the value 100." It does,
however, make sense to say, "The value 100 is assigned to (the location of)
variable il."
Let's look at statement 2, which has two variables. Again, at least i2 must
be interpreted as referring to the variable's location, rather than to the value
stored in the variable. In this case, it's meaningless to say, "300 is assigned the
value 100."
It makes more sense to say, "The value 100 is assigned to (the location oO
variable i2," just as in our earlier example. Notice that C treats il as a value.
1. Determines the value of the right operand (the right side of the
3. Stores the value of the right operand in the location of the left
operand.
266 Using QuickC
assignment operator. The right operand, on the other hand, must be a value,
since that's what the assignment operator needs to put into the left operand.
Variables whose locations are needed, in order to do something at those
locations, are known as lvalues. Historically, the "1" referred to "left," because
lvalues often appear on the left side of assignment statements. By the same line
of reasoning, the right operand of the assignment statement whose value, —
rather than location, was of interest —
is sometimes called an rvalue. The
What about statement 5? There isn't really a left and right operand here
because the increment operator only takes one argument. The il in the
expression is still an lvalue, however, because the operator eventually changes
the contents of some location (the location of il). This may be clearer if you
think of the increment operator as equivalent to the following assignment
being made at the proper time (depending on whether the operator is prefix or
postfix). In this form, the need for an lvalue is clear.
The address operator also needs an lvalue as its operand. When the
address operator is applied, it essentially does the following:
Pointers 267
Pointers
main ()
actions, and then look at a diagram of what's going on. The program involves
two assignments and three calls to printf( ). The first assignment, target = 23, is
268 Using QuickC
just like the other assignments we've seen. On the surface, the second assign-
ment, ptr = &target, also looks equivalent. The mechanics of the assignment
do, in fact, work like the other assignments we've seen. The result is that the
address corresponding to target is stored in ptr. There are important differen-
ces, however, as we'll see.
The following diagram shows the state of memory after each assignment,
and then matches the arguments to the printf( ) calls with the appropriate
component in memory.
(a)
Pointers 269
that is written is 23; this is also the value of target. The call to printf() says,
essentially, "Write the message, then write the contents of the variable pointed
to by ptr." Think of the evaluation process for this display as going through
steps such as the following:
3. Get to the location specified by this address — that is, to the variable to
which ptr is pointing.
4. Determine the contents of this variable (23, in our case), and write
them.
In the preceding diagram, notice that target has two names after assigning
the address of target to ptr. Until now, all the references to variables that we
wanted to display have been direct references, in which the variable was
referred to by name. The variable represented as *ptr is an indirect reference to
a particular value. *ptr is "the value referred to, or pointed to, by ptr." Because
it's awkward to speak of "the value pointed to by the pointer"all the time, we'll
use "the pointer's target value" to refer to such values.
You can think of a pointer as an address. When you think of ptr as an
address, *ptr refers to the value associated with that address, or to the contents
stored at the specific memory location.
The in *ptr is C's indirection operator. This operator takes a single
operand, which must be a pointer, that is, an address. The indirection operator
returns a value, namely, the value stored at the address referred to by the
pointer. In the call to printf( ), the address referred to by the pointer is 4094,
and the value stored there (23) is displayed.
The indirection operator is a unary operator. As such, it has very high
precedence, the same level as the other unary operators we've seen so far
(including the address operator).
Before we look at another example, let's look more carefully at the syntax
in the previous listing. The definition of the pointer variable occurs in the
statement:
int *ptr;
270 Using QuickC
This statement defines the variable ptr as a pointer to an int. The * after the
type identifier (or before the variable name) indicates that the variable being
defined is a pointer to an int, rather than just an int variable. Strictly speaking,
pointers are a separate type, pointer to. For our purposes, you can just think of
a pointer definition as a way of telling the compiler that it should only expect
to find addresses stored in the variable ptr.
Once you've defined a pointer variable in a program, you can refer either
to the pointer variable itself (a direct reference) or to the pointer's "target"
value (an indirect reference). When you refer directly to the pointer in your
program, do not include the*. In the previous listing, the first reference to the
pointer variable is direct, so only its name is used.
In the following assignment, we store an address in the pointer variable,
changing the contents of the pointer variable itself, not of the target value.
This assignment looks and happens just any other assignment. The only
like
difference is that the compiler will check more closely whether you are putting
the appropriate kind of information into the variable. Had we tried to put an
arbitrary int value in here, the compiler would have warned us.
When you want to refer to the pointer's target value, you need to use the
indirection operator (*) — to want the value to which the
indicate that you
pointer is pointing. In the previous program listing, the argument to printf( )
illustrates this use.
When referring to the pointer's target value, use the indirection operator,
*, before the pointer's name.
:; ; ,
. ,
.
Pointers 271
of type pointer to some specified type, and that the compiler should therefore
expect to find only addresses stored in the variable.
Let's look at another example, this time using pointers to a different type:
main ()
A diagram similar to one we used earlier should make some points easier
;
to understand:
(a) (b)
Alter Alter
dbl.target - 23.0; dbl_ptr = idbl.target
(c) (d)
Alter Alter
dbl.other - 46.0; *dbl_ptr - dbl.other;
The first part of this program is just Hke the previous program, except
that this time the statements involve variables of type double and pointer to
double. The storage allocated to the double variables is 8 bytes instead of the 2
bytes allocated for an int.
The pointer variable, dbl _ ptr, is still allocated only two bytes, however.
Remember, a pointer contains an address, not an ordinary value. The only
difference between a pointer to in! and a pointer to double is that the program
expects to find a whole number stored at the in! pointer's target variable, and a
floating point value at the double pointer's target variable. QuickC always
allocates 2 bytes to an ordinary pointer.
Notice that when the contents of the pointer variable are displayed, the
information is written as an unsigned int, even though we never declared such
a value.The output takes this form because an unsigned int takes on the range
of values possible when using two bytes. This data type is, therefore, useful for
displaying possible address values.
The fourth assignment statement (*dbl_ptr = dbl__other) is a construct
we haven't seen yet — this
an assignment involving two variables of type
is
(such as double), then *ptr_var refers to a value of that target type (double).
Thus, ptr var and *ptr_type reference different types.
;
Pointers 273
We've seen that address operators return a location, and that indirection
operators return the value associated with a particular address. In a sense,
these two operators are inverses of each other. The following listing illustrates
this,and gives you more exposure to pointers. Essentially, the program shows
four ways of assigning the value of one variable to another:
main (}
{
doable dl - 3.14. d2, d3, d4;
double 'd.ptr;
»/
assign
by td2 -- i.e.. value of d2 —
value stored at the location returned
to variable d4.
prlntf ( »dl - Xlf; d2 - Xlf; d3 - Xlf; d4 - Xlf\n". dl. d2. d3. d4)
speaking, the operator returns a pointer which, we've seen, has an address as
its value. This means &d2 returns a pointer whose value is the location of d2.
The indirection operator, in turn, returns the value located at the address
stored in the pointer. In this case, the variable is id2. The end result is that you
assign the value of d2 to variable d4, but in a very roundabout manner.
The mechanics of evaluating the right operand, '•&d2, illustrate an impor-
tant difference between the unary operators and most of the other operators.
So far we've worked on the assumption that operators were applied in order of
precedence, and from left to right when two operators had equal precedence.
This is the way things work with the arithmetic and logical operators.
274 Using QuickC
The unary operators, however, are evaluated from right to left when
several operators with the same precedence occur in succession. The address
operator is applied first, then the indirection operator is applied to the result
from the address operator. (The other set of operators that associate from right
to left are the simple and compound assignment operators. For example, in an
assignment statement, the right operand is evaluated first, then the result is
Why Pointers?
the only methods we've seen for passing information out of a function are
global variables and the function's return value. In this section, you'll see how
to use pointers to get information out of a function.
; ; ,
Pointers 275
Suppose you wanted to exchange the values of two variables. The follow-
ing listing accomplishes this without using pointers. Study the listing to
become comfortable with the strategy for swapping, so you'll be able to
concentrate on the use of pointers in the next version. Here is the listing:
main ()
types of functions, such as sorting routines for ordering data. To swap two
values, you need three assignment statements and one temporary variable of
the same type as the values you want to swap. The main strategy is to store the
value of the first variable (vail), then move the second value into the first
variable (store value of val2 in vail). Finally, move the original value of the
first variablefrom temporary storage into the second variable (move the
contents of temp to val2).
Now let's look at a program that includes a function to perform this swap.
Don't get intimidated by the swap_dbl( ) function. The actual swapping takes
place in just three statements. The rest of the function's statements are calls to
printf( ), to show you the addresses and contents of the variables involved in
the swapping.
•lirst, •second);
; ; ; ;
main ()
<
double dl • 12.3, d3 • 39.5;
void swap.dbl ( double •, double •);
dl - 39.500000; d2 • 12.300000
Let's look at main( ) first. This function calls dbl_swap( ) to swap the
values in the two variables. Rather than passing the values themselves as
arguments, main( ) calls dbl_swap( ) with the addresses of the two variables as
the actual parameters.
Before we see how dbl_swap( ) works, look at the prototype declaration
for the function in main(). The double * indicates that the argument is a
pointer to double, rather than just a double. To indicate a pointer type in a
definition or a declaration, you need to include the *, as described above.
Generally, this will be immediately before the pointer name; however, for a
parameter in a prototype, when you don't need to include an identifier, the
indirection indicator is used to specify that the argument is a pointer type.
Pointers 277
To see how dbl swap( ) works, study the following diagram, which
shows the program's memory layout after each assignment in dbl_swap().
The identifiers in parentheses are the names associated with particular loca-
tions inside the function dbl_swap( ). The other names (dl and d2) are the
names associated with the same locations in the main( ) function.
(a)
; ; ;
You can see this in part (d) of the illustration. When control returns to
main( ), dbl_swap( ), the storage allocated for niain( ) is still
after the call to
around. Thus, the contents of dl and d2 are still defined; these contents have
been changed by the call to dbl_s>vap( ). Notice, however, that the locations
that had been allocated two formal parameters to dbl swap( ) are no
for the
longer accessible. Those variables had local scope and automatic storage
duration, so their storage disappeared when the function ended.
Earlier, we learned that parameters to functions could be passed by value.
In that case, the program passed the value of the argument into the function,
and this value was stored in the location allocated for the formal parameter.
The function worked with a copy of the argument's information.
We've seen one way of passing parameters. Rather than passing the value
stored in a particular variable, we pass a reference to the variable itself, in the
form of a pointer to the variable's location. This form is known as passing by
reference. Parameters passed by reference can be changed inside the function;
parameters passed by value cannot.
•Include <stdio.h>
main ()
{
int cl;
void switch.case ( int «)
Bwitch.cBse ( £cl):
Pointers 279
•include <8tdio.h>
main ()
starting conditions
balance - 1000.00; amtl - 200.00; amt2 - -600.00
This program changes the value of bal by the amount specified in either
amtl or amt2. then displays the result. Notice that the value of bal always
comes back changed by the appropriate amount after a call to update.
baiance( ). what passing a parameter by reference accomplishes.
This is
The second arguments are unchanged, however, despite the fact that this
parameter is explicitly changed in function update_balance( ). This is because
the contents of parameters passed by value are only copied, not used directly.
Pointer Arithmetic
main ()
<
int il - 111, i2 = 222;
int *i_ptr;
This program shows what happens when changes are made to values in
the pointer variable (that is, addresses stored in the pointer are altered) or to
the values stored in the pointer's target variable.
The first two calls to printf() display the addresses and values of the
non-pointer variables used in the program. The third call displays information
about i ptr and its target value, after assigning the address of i2 to i_ptr —
that is, making
after i ptr point to i2.
Call 4 shows the results after adding 1 to i_ptr. Let's see how this
addition works. First, note that the variable involved in the assignment
statement (i_ptr = i_ptr + 1) is a pointer, not a target variable. This means
that, in the right operand, the program first adds 1 to an address, then assigns
the resulting address to i_ptr.
It turns out that adding 1 to an address is different from ordinary
addition. Adding 1 to the value stored in a pointer variable effectively sets the
value to the next valid address value. Think of this as adding 1 address unit to
the current address. The size of an address unit depends on the type of the
target variable, as Table 10-1 shows.
For example, an two bytes of memory, so an address unit for int
int takes
also equals two bytes. If you check the values for i_ptr in calls 3 and 4 (that is,
before and after the assignment under discussion), you'll see that the two
addresses differ by two. So, in this case, adding to a pointer value meant 1
Similarly, suppose you had a pointer to double, and the current value of
the pointer variable was 4000. Adding 1 to this pointer value (that is, to the
address stored there) would give the pointer a new value of 4008 — because
you're actually adding one address unit, or eight bytes. Subtracting 3 from this
282 Using QuickC
char 1
int 2
float 4
double 8
address value would store the address 3984 because 4008 —(3*8)= 3984.
Contrast this with the effects of the assignments between calls 4 and 5.
variable is increased by the specified amount (here, 1). The resulting value is
assigned to *i ptr.
Pointers 283
The flexibility of pointer arithmetic also comes with a price, however. It's
your responsibility to make sure the pointers point to the correct type of
variable, and that the correct type is actually stored at the location you've put
into the pointer variable using pointer arithmetic.
Make sure you move only to memory locations allocated for the function
or program in which you're doing the pointer arithmetic. Also, make sure that
when you try to read the contents of the target variable, the bit pattern will
actually represent the type of value your program is expecting. For example,
you don't want your program to read the first two bytes of a double stored at a
location, expecting to find an int there.
ables. Notice the use of parentheses in the increment before printf( ) call
off:
main
<
double dl - 111.11, d2 • 222.22, d3 333.33;
double *d_ptrl, *d.ptr2:
printf ( "1) tdl - Xu; td2 - Xu; td3 - XuXn", tdl td2. td3)
,
d.ptrl - td2;
printf ( "3) d.ptrl - td2 \t :: d.ptrl - Xu: •d.ptrl - Xll\n\n"
d.ptrl, •d.ptrl);
You can use the following output from the program to check your conclusions:
Miscellaneous Points
indicate that the pointer is not pointing anywhere. QuickC and Microsoft C
5.0 both define the manifest constant NULL to represent this value.
This value is particularly handy for determining whether you are at the
end of a list — that is, whether the last list pointer is not pointing anywhere.
You can also use it to make sure a pointer has a valid target variable before
doing something with this target. For example, the following test can be useful
for avoiding unpredictable errors:
main ()
;
Pointer Aliases
main ()
<
int il - 11;
int *i_ptrl. *i_ptr2;
This program produces the following output. Notice the contents of the two
pointer variables, and compare this to the location of i2.
and the location also goes under the direct name, il When a memory location .
For example, when passing pointers as parameters, you have control over the
arguments. As long as you have only one name for the variable in the calling
function, the only changes that can occur in this context will be inside the
function, and those changes will be passed back out to the calling function in a
controlled manner.
Misguided Pointers
Earlier we saw a listing involving pointer arithmetic. By adding to or subtract-
ing from an address, we could move to the next memory location containing a
variable of the target type. If you look back at that example, you'll see that the
original pointer variable was set to point to the middle variable of three. Had
the pointer been set to point to the first or the third, the pointer arithmetic
might have produced a memory location that was either undefined or one that
contains a value other than an int.
When you use pointer arithmetic, the compiler simply makes the adjust-
ments required to move as far as your target type warrants. The compiler will
not check to make sure that the new location contains a variable of the
appropriate type.
Again, problem can usually be avoided. Most situations that call for
this
Summary
In this chapter, you've learned about pointers, a new type of data structure
which contains memory addresses as its values. Pointers allow you to refer to
Often, you'll need to work with multiple pieces of information that form a
collection, or that somehow belong together. For example, suppose you had
records of daily temperatures for a one month period. If you wanted to analyze
this information in a program, you would need to allocate a memory location
for each value.
You could define a separate variable for each temperature reading, nam-
ing them tempi through temp31. However, typing all this can get tedious.
Fortunately, C's array concept provides an easier way to deal with such data
collections.
289
290 Using QuickC
Arrays Basics
This definition creates a variable named daily temp. The type specifier
indicates that the variable contains information of type double. The square
brackets containing the number 31 indicate that the variable is an array of
double values containing 31 elements.
When the variable daily _ temp is defined, the compiler allocates enough
contiguous storage for 31 variables of type double. The first array element has
the lowest address in the array, and also represents the address of the entire
array variable.
To access a particular element in the array, specify the array name
followed by the subscript corresponding to the element's position in the array.
In C, all arrays start with the subscript 0. Thus, the subscript values in the
array daily _ temp[ ] range from to 30, not from 1 to 31 . To refer to the tenth
element, you would write daily _ temp[ 9], since the first element has index 0.
You'll almost certainly forget this at least once in your programs, so try to do it
main ()
; ;
/* initialize array */
for ( count - 0; count <- MAX SIZE count++)
<
running_8um += count;
suns [ count] <= running sum /• assign value to cell */
}
This program stores the running sum of integers between and 10 in an array,
then displays each cell of the array in a table. Let's see how the array cells are
used and how the loops are set up in the program.
The array definition has the same form as the earlier example, except that
a manifest constant is used to indicate the size. This is allowed, as long as the
constant is replaced by a positive integral value. Notice that MAX — SIZE is
11 — the array has 1 1 elements, not 10 — even though the largest subscript is 10.
The individual cells of the array are denoted by the array name followed
by the subscript, or index, in square brackets. These brackets identify the
variable as an array in a definition or declaration and identify a value as an
array element when used in an expression.
The continuation conditions in the two for loops accomplish the same
thing, but use different tests. The first test, count <= MAX SIZE minus is _ 1 ,
more awkward, but makes explicit the fact that the last array element has a
subscript one less than the array size. The second form of continuation
condition is more convenient, but makes the relationship between array size
and largest subscript value less explicit. Use whichever form you prefer. Just
292 Using QuickC
^ REMEMBER In C, all array subscripts begin with 0. The last array element
has a subscript that is one less than the array size.
Arrays are, in one sense, variables just You can define arrays as
like any other.
global or local, with static or automatic storage duration. The only restriction
is that you cannot define arrays with register storage class.
Thus, the following are all valid array definitions, and declarations:
main ()
{
static double local_array [5]; /* local array; static storage */
int another.array [ 12] ; /* local array •/
The syntax for defining array variables is similar to that for defining other
types of variables. This is also true if you include storage class specifiers in the
definition.
Array declarations and definitions are different. Once you've defined an
array, including an array size, you can declare it in other places. You don't have
to specify the array size in declarations, because the compiler allocates storage
at definition time. At declaration time, when no storage is allocated, the
compiler isn't interested in the array size because it's your responsibility to stay
within the array's bounds.
We've seen that it's possible to initialize variables when defining them. Is
this possible with arrays? Yes and no. You can initialize arrays with static
lifetime (that is, global arrays and those with storage class static); you cannot
initialize arrays with automatic lifetime (e.g., arrays declared within a
function).
Arrays and Strings 293
To initialize an array at definition time, you need to specify the values for
the cells, after giving the array name and dimensions. The values are written
within curly braces, with individual values separated by commas. Your initiali-
zation may extend over multiple lines, but you may not split an individual
value between lines.
For example, the following definition initializes the cells in the array,
static int days.ln.month [ 12] - {31. 28, 31. 30. 31, 30,
31, 31. 30, 31, 30. 31};
If you don't initialize an array that has a static lifetime, C initializes the
One exception to the rule that array size must be specified when you
define the array is when you initialize an array with static lifetime at definition
time. For example, the following is a valid definition for the days _ in _
month[] array:
Here, the compiler counts the number of initial values (12) and takes this
number as the size of the array. However, it is not considered good practice to
define arrays without specifying the size explicitly at definition time.
pointers we've seen so far, however, you cannot change the value of the array
pointer Once defined, an array's location is fixed, just like that of any other
variable. Consider these examples:
; ; ;
main
/* Initialize array »/
for ( count 0; count < MAX.SIZE; count++)
{
entry [ count] count + OFFSET;
y
In the following program output, notice the types displayed for each of
the parameters to the printf() calls, as well as the values printed:
i.ptr « Sentry [ 0]
simply a label for the address of the first array element. You cannot change the
value of entry or ask for its address because entry doesn't have one. If you do
ask for the address of entry (that is, for &entry) in the program, you get a
compiler warning, that the compiler is ignoring the & operator for the array.
After compiling, you would get the same output as in the program above.
If you want you need to ask for &entry[ OJ,
to display the address of the array,
the address of the first element, or simply for entry, which amounts to the same
thing.
; ,
You can, however, ask for the target of this pointer, that is, for the value of
the first array element. The indirection operator, *, will return the variable
located at the address specified by its operand. Thus, *entry returns the first
array element. Since the address stored in i ptr is the same as the value of
entry, asking for *i ptr also returns the first array element. Because entry[ 0]
is not an address, you don't need the indirection operator with this variable.
our example).
Accessing Individual
Array Elements
Suppose you wanted to use the value of an individual element in the array. We
already saw how to do this, in the previous section. To use the value of the fifth
element in an array, such as entry, you simply ask for entry[ 4].
In this section, we're going to look more closely at how your program gets
the element you want. Let's modify the previous program, to see what happens
when we access individual elements.
main ()
{
int entry [ MAX.SIZE]
int count, *i_ptr:
i_ptr - entry;
print! ( "entry = y.u\n" , entry) ;
entry » 4044
ftentry [ 4] = 4052; i_ptr + 4 - 4052; entry + 4 = 4052
entry [ 4] - 104; '(Lptr + 4) - 104; *(entry + 4) - 104
The program displays the same information as the previous program, but
for the fifth element rather than the first. The program also demonstrates the
essential similarity of pointers and arrays. We can access arbitrary array
elements using either array syntax (subscripts) or pointer arithmetic and
indirection. Let's study the program and its output.
The second line of the output displays the address of the fifth array
element, showing three different ways to specify this location. The first,
&entry[ 4], is the most straightforward and the easiest to interpret. The other
two explicitly use pointer arithmetic. Recall that adding 4 to an address means
adding four address units, where the size of an address unit depends on the
base type of the variable.
Thus, the 4 in this context equals 4*2 (bytes per int), or 8, bytes. The
value of entry (4044) is the address of the first array element, entry[ 0].
Therefore, an offset of 8 bytes from this yields the location of the fifth element.
QuickC actually uses pointer arithmetic to access array elements. Essentially,
using array indexing is really a convenience for the programmer.
The third output line methods of writing the value
shows three different
of the fifth array element. Specifying the cell's name, entry[ 4], is most
straightforward. The other two methods use the indirection operator to return
the contents of the target variable at the specified location. Notice the use of
the parentheses around the pointer arithmetic. These are necessary because
the indirection operator has higher precedence than addition. (Compare the
*(i_ptr + *i_ptr + 4 in the second output line.)
4) with the
We've seen that you can use pointer arithmetic instead of array indexing
to get information from an array. Let's look at one more version of this
program. Notice how the pointer variable i ptr is used.
main ()
{
int entry [ MAX.SIZE]
int count, »i_ptr;
;;; .
entry 4002
tentry [ 8] 4108; entry 8 - 4108; entry [ 8] - 108; *(entry + 8) - 108
ti.ptr C 8] - 4108; i.ptr + 8 - 4108; i.ptr [ 8] - 108; *(i.ptr + 8) - 108
when you define an array, you get storage for the individual elements, and you
get a pointer constant thrown in. The compiler does not allocate any storage
for a pointer variable.
On the other hand, when you define a pointer, the compiler only allocates
the storage for the pointer variable, not for a target value. Even if you have an
appropriate target value to begin with, it's still your responsibility to make sure
you'll have the appropriate variables computed using pointer
at locations
arithmetic. Suppose you define a pointer to int — ptr, for example and make —
it point to an int. Syntactically, you can then access the memory location
ptr[ 7], and the compiler will not complain.
Just as for pointer arithmetic, it's your responsibility to keep from access-
ing array elements that are out of bounds. Using an array definition when you
want to access variables in a collection is safer, because you can always check
the array index to determine whether you're still within the permitted range of
values.
Another important difference between the array entry and the pointer
i_ptr is that you can change the value of i_ptr, whereas entry is a constant.
Until you feel confident using pointers, you're better off using array definitions
for collections of variables, and using pointer definitions for accessing individ-
ual variables.
Because of the way in which the QuickC and the Microsoft C 5.0
compilers do their work, thereis no real difference in the speed at which you
Array Parameters
You can pass arrays as parameters to functions. When you do this, you're
actually passing the contents of a pointer to the first array element. Thus,
arrays are passed by reference, although the array's address is passed by value.
The function does not make a copy of the array contents. Let's look at an
example:
main ()
200 i:
202 <
204 M
206
J
*
208
310
213
314
216
218
220
values from 100 to 110. The program then calls a function, tinies_two( ),
which doubles the value of each cell in the array. Finally, the program displays
the resulting array in a table.
In examining function calls involving arrays, notice the function proto-
type declared in main( The prototype informs the compiler that function
).
times _two( ) does not return a value, but takes two parameters, an array of
int and an int. The array is indicated by using the square brackets. Thus, int[ ]
specifies an array of int, just as int * indicated a pointer to int. Since arrays and
pointers are equivalent, we could just as easily have used the pointer to int in
;
the prototype. That is, the following would have been an equally valid
prototype:
Notice that the function declaration did not specify the size of the array.
This was not necessary because the compiler won't allocate any space for the
array in the function. Rather, the compiler will reference the actual memory
location containing the array (call by reference). Generally, you'll pass in a
separate parameter that tells how many elements to process, as
the function
we've done in this program. In other cases, the function may look for a
particular value in the array, and will stop when this value is encountered. See
the next section's discussion about strings.
In the function call, we used the array name, entry, as the first argument.
Again, since only a pointer is being passed, we could also have used
&entry[ 0].
Note that in the definition of times two( ), no size is specified for the
array parameter. That information is passed in as a separate parameter, and
the value is used to tell when to stop rather than how much
the function
storage to allocate. The only storage allocated for the array is enough space to
store the pointer value. Everything else is done in the actual storage allocated
for the array in main( ).
We could have specified an array size in the function declarator. But, since
the compiler does no range checking, this would have had no effect. As in the
prototype, we also could have declared the parameter as a pointer, rather than
an array. Develop the habits that best suit your programming needs and style.
In this book, we'll use pointers mainly for individual variables, and array
definitions when a collection of values is under consideration.
main ()
{
int entry [ MAX.SIZE], count;
Int rand ( void) /* generates random integers */
double mean ( int [] int) /* computes mean of array elements */
, ;
double average
Int count
double running_Bun- 0.0;
for count
( 0; count < array.size; count ++)
running.aum +
vals [ count]
return ( running.sum / array.size)
which computes the average value of these 100 numbers and returns this
information. The array is not changed inside mean( ), although it could be. The
values in the array are needed to compute the mean.
In the following more complex example, see how the program carries out
the same general task —
generating pseudo-random integers, and computing
the mean of the integers generated. A major difference between the programs
isthat this program relies much more on "specialist" functions to do the work.
The program also breaks up the task into a series of small tasks before
computing intermediate results, then combines these into a final answer.
main ()
<
int entry [ MAX.SIZE]
double sampling [ MAX.SAMPLES]
int count;
double int. mean ( int [] int) ,
; ; ;; ; ; ;; ;
int count;
for count
( 0; count < array.size; count++)
running.sum +- vals [ count]
return ( running.sum / array.size)
<
Int count;
double running.sum" 0.0;
of values have been generated and their mean computed, the program calls
dbl mean( ) to compute the grand mean of all the values.
Let's look at the major points concerning this program. First, we have
two arrays, one to store the random integers as they're generated (entry[ ]), and
the other to store the means for each group of 100 integers (sainpling[ ]).
The program has given the task of generating the random integers to a
function, get rand( ), which is passed a pointer to the first element in an
array of integers and a value indicating the numberof integers to generate. The
array elements are changed by get rand( ).
Notice that the program uses two different functions for computing
means. The mean() function works with an array of int values; the
int
dbl_inean( ) function works with the array of means computed during the
program's execution. Since array indexing is done using pointers, the function
decides where the next element is on the basis of the size of a single element.
Since arrays of int and arrays of double have different element sizes, they must
be handled by different functions.
A final, minor, point concerns the last call to printf( ). Notice the expres-
sion MAX — SAMPLES * MAX_SIZE as an argument. Based on our
discussion of the preprocessor and of printf( ), you should realize that this is
Strings
Strings are an important part of any program. You can use them to write
messages to the user, to read text files,and generally to make interactions with
users go smoothly. In this section, we'll look at how strings are represented and
used in C.
Note that the QuickC Run-Time library includes functions for doing
many things with strings, including reading and writing them, comparing
them, and appending one string to another. We will discuss some of these
There is no distinct type, string, although the printf( ) and scanf( ) functions do
recognize a placeholder, %s, for displaying and reading strings. When you
define a string, you need to make the array large enough to include a null
character. For example, to store the word "hello" you would need an array of at
least six characters, as the following illustration shows:
h
Arrays and Strings 305
The string used, word[], is defined as a global array of char. Note first
that the technique for initializing a string is different from the array initializa-
tion we saw earlier. This method — initializing the array with a string
constant — is allowed only with arrays of char. This is equivalent to, but much
more convenient than
^REMEMBER Make sure you've allocated enough space to hold the longest
string you expect to store in the array, including the null character.
Notice that the for loop in the program ends, even though a null character
is not included at the end of the string constant. The compiler automatically
adds the \0'character to the end of string constants, so
' it isn't needed in your
initialization.
^REMEMBER You don't need to include the null character (' \0') in string
include <stdio.h>
#define MAX.STRING 50
main ()
Int index
int Btrlen ( char []); /* library function, return string length */
char wd.read [ MAX.STRING]
This program prompts you for a string, reads your input, then displays the
string and tells you how long it is. This time, the character array, wd _ read[ ],
is defined as a local variable. The Run-Time library function, gets( ), is used to
read the value for wd_read[]. This function is declared in the file, stdio.h,
and returns a string value.
Functions such as gets( ) allow you to read information into string vari-
ables. You can write this information, using the %s placeholder when calling
printf( ). This placeholder tells the function to expect an array of characters,
either a string variable or a constant.
Because the string array name is a pointer constant, you cannot really use
it as the left operand of an assignment statement — except when initializing a
character array of static lifetime. Thus, the library routines for handling
strings are especially handy because they let you read variable values into your
strings.
You can also build your own strings, character by character, by using a
routine such as the macro, getchar(), in a loop reading characters until a —
predetermined end of input character is entered. Usually, the loop will end
when a newline character (' \n') is read. The following program illustrates this
tinclude <stdio.h>
«define MAX.STRIHG 60
#dellne NULL.CHAR "NO" /* end of string marker in C •/
main ()
{
Int index:
Int strlen ( char []);
char wd.read [ MAX.STRING]
This program reads characters up to, but not including, a newHne character,
since the loop stopswhen this character is encountered. Each character read is
added to the wd_read[]. Notice that if you build the string yourself,
string,
you may need to add the NULL_CHAR (' \0') yourself (Many C programs
use the predefined manifest constant, N U LL, to indicate the end of the string.
Because this chapter is our first discussion of strings, we've used NULL
CHAR to emphasize that a special character ends the string.)
Notice the parentheses around (wd_read [ index] = getchar( )) in the
continuation condition of the preceding program. These parentheses are
important, since they ensure that the assignment to wd _ read[ index] is made
before the comparison with '
\n'. Without the parentheses, the higher prece-
dence of the inequality operator would dominate, causing the comparison,
getchar( ) != \n', to be made first. This would return a true as long as the
'
character read was not a newline character. This nonzero value would then be
assigned to wd_read[ index]. This is not what you want, so be careful about
using parentheses in such situations.
There's yet another way to handle strings in C, based on the fact that a
string can be considered a pointer to char. The following program illustrates
this, and also shows a situation where arrays and pointers differ:
•include <stdio.h>
»deline MAX.STRING 60
•delins NULL.CHAR '\0' /• end ol string character in C •/
main ()
{
: ; ; ; ;
int Index
char wd.read [ MAX.STRING]
/• define string as pointer to char;
Initialize by pointing to first char of string constant
*/
char 'str.ptr "hello"
For an input of "How do you do?" this program produces the following
output:
hello
Enter a string please.
How do you do?
How do you do?
LENGTH, not counting \0 -- 14
Assigning string I read to my str.ptr
str.ptr • How do you do?
Let's look carefully at this program. We've already seen the definition of
wd _ read[ ] and its use in the previous program. The str ptr variable is new,
however. This is defined as a pointer to char. In practice, this definition
amounts to a definition of a string, with some differences from the definition
of wd _ read[ ], however.
Note first that the pointer definition allocates space for a pointer variable,
whereas the array definition only does so for the array elements (since the
array name refers to the address of the first element).
Second, we could initialize the pointer variable, even though it is local
with an automatic lifetime. (Recall that arrays can only be initialized if they
have static lifetimes.) Any pointer variable can be initialized, however. In this
case, the initialization essentially stores the string constant in memory and
makes str __ ptr point to the location of the first character in the string
constant.
Notice the format of the printf( ) call. In the string argument, we specified
that it was to write a string (%s). The function takes this as an instruction to go
to the variable specified, and to start displaying characters until a
; :; ; ; ;
NULL CHAR to a string constant, the function writes "hello" when called.
Next, notice the assignment statement, str ptr= wd read. We've seen
that we can't have an array name on the left side of an assignment statement.
We can have a pointer name there, however, since this is a variable, not a
constant. The assignment statement makes str-_ptr point to the start of the
array,wd_read[]. That is, the assignment makes the two names refer to the
same string. The "hello" string is lost at this point, since its starting location,
which had been stored in str_ptr, is overwritten. The contents are still in
memory; they're just inaccessible after the assignment.
To convince yourself of this, run the following program, which adds a
second character pointer (str_ptr2) to preserve the address of the "hello"
before str_ptr has its contents changed.
#include <Btdio.h>
Adeline MAX STRING 50
#define NULL.CHAR AO'
main ()
{
Int index;
char wd.read [ MAX. STRING]
char 'Btr.ptr • "hello"
char •str_ptr2;
Let's look briefly at some of the library functions available for handling strings
in QuickC and Microsoft C 5.0. We'll just be able to scratch the surface here.
You should look through the Run-Time library documentation for more
information about these functions as well as others that are provided.
: ; ; ) ; ;
gets( ) and puts( ) We've already used gets( ), which lets you read a string
from the standard input, stdin. This function takes a string argument, into
which it places the string read. Note that gets( ) substitutes a NULL_CHAR
for the newline character the ends the input string, making the string conform
to C's requirements.
As you might have guessed, there's an output counterpart to gets( ). The
puts( ) function writes a string to the standard output, stdout. This function
takes a string argument, and returns an int, representing the last character
written. Also, puts( ) replaces the N U LL _ C H AR at the end of the string with
a newline character (' \n') when writing the string. Thus, if all goes well, this
should always be the character returned by the function.
The following program illustrates the use of these two functions:
#include <stdio.h>
•define MAX.STRING 50
•define NULL.CHAR 'XO'
main
{
int outc ome
char wd.read [ MAX.STRING]
int puts ( char *}
For an input of "turkey trot," this program produces the following output:
These functions are case sensitive. Both functions require vahd strings —that
is, strings terminated by a NULL_CHAR.
The strcnip( ) function takes two string arguments. We'll call these argu-
ments stringl and string!, respectively. If stringl comes earlier in a lexico-
graphic ordering than string!, the function returns a negative value; if the two
strings are identical, the function returns zero. Finally, if the first string is
"greater than" the second (comes later in an ordering), the function returns a
positive value.
$trncnip( ) takes three arguments. The first two are strings, just as for
strcmp( ). The third is number of characters to
an integer that specifies the
compare. This function does the same thing as strcmp( ), and returns the same
range of values, but can be used to compare just portions of strings.
The following program illustrates the use of these functions:
main ()
int outcome:
char wdl [ MAX.STRING] ;
',
For inputs of "hello here" and "hello there," this program produces the
following output.
When comparing just the first five characters, notice that the two strings are
identical, so that strncmp( ) returns 0. (Warning, if you're using strcmp( ) as a
test in a selection or loop construct, keep in mind that the function returns
— interpreted as false in C — when two strings are identical. If you want them
to be identical,you need to make the appropriate adjustments in your test.)
For inputs of "hello" and "Hello," this program produces the following
output:
Both versions of the function return 1; since the lowercase h comes later
than the uppercase H in the ASCII character set, "hello" is greater than "Hello."
^REMEMBER You must make sure that you've allocated enough space for
stringl to store the characters being added.
The $trcat( ) function adds the entire second argument, stringl, to the end
of the first string. strncat( ) takes three arguments —
two strings (like strcat( )),
and an integer specifying the number of characters from string2 to add to the
end of stringl.
:; ; ; ;; ;;
#lnclnde <stdlo.h>
•define MAX.STRING 50
•define KULL.CHAR '\0'
Bain (}
{
int outcome:
Int strlen ( char *)
char wdl [ MAX.STRING]
char wd2 [ MAX.STRING]
I
This program produces the following output when "first" and "second" are <
F
Enter a string please. (No longer than 26 characters) <
first U
Notice the last line of the output. Since we added wd2 to wdl using
strcat( ), wdl was changed. Therefore, the first argument to strncat( ) is not the
same as the wdl passed to strcat( ).
; ; ) ; ; ; ;
another, strcpy( ) and strncpy( ) copy one string over another. The functions
take two string arguments, stringl and stringZ, and copy string! over stringl.
Thus, the original stringl is overwritten when the second string is copied.
Notice that the second argument is being copied over the first.
The strcpy( ) function copies all of string2, including the NULL CHAR
that terminates the string, over stringl Whatever had been stored in stringl is
.
overwritten.
The strncpy( ) function takes the same two string arguments, as well as a
third, integer, argument. The integer specifies the number of characters of
string! to copy into stringl. Use this function carefully. If the number of
characters to be copied is greater than the length of string!, the function will
simply pad stringl with NULl CHAR characters. On the other hand, if you
are copying fewer characters than string! contains, the function will not
terminate the new stringl with NULl CHAR.
*include <8tdio.h>
•define MAX.STRING 50
#define KULL.CHAR AO'
main ()
{
int outcome
int Btrlen ( char *)
char wdl [ MAX.STRING]
char wd2 [ MAX.STRING]
you may want to study are those that convert information from one type to
another. For example, itoa( ) converts the digits of an integer variable to a null
terminated string; atoi() works in the other direction — converting a string
into an integer. There are several of these functions, and they can be useful in
various contexts.
Many string handling functions are also provided in C libraries available
from various sources. For example. The C Library, by Kris Jamsa
(Osborne/ McGraw-Hill, 1985) includes 18 functions for manipulating strings.
Studying the source code in such libraries can help you become more comfort-
able with C and can provide you with new insights into different ways of doing
things.
Multidimensional Arrays
C allows you to define two-, three-, and even higher dimensional arrays. A
two-dimensional array is essentially an array, each of whose elements is itself
main ()
<
/* define 2-dimensional array,
with MAX.ROWS rows and MAX.COLS columns
*/
int matrix [ MAX.ROWS] [MAX.COLS]
int row, col;
lor
y
; ,
main
/* display array •/
for ( row - 0; row < MAX.ROWS; row++) /* for each row */
for ( col - 0; col < MAX COLS; col++) /* for each column */
printf ( "Row Xd. Col Xd XdNn":
Row 0. Col
Row 0, Col 1
Row 0. Col 2
Row 1 Col
Row 1, Col 1
Row 1 Col 2
Row 2, Col
Row 2, Col 1
Row 2, Col 2
Row 3, Col
Row 3, Col 1
Row 3, Col 2
specified. It turns out that the compiler needs to know the size of any array
dimension beyond the first.
To see why
remember that an array is simply a consecutive
this is so,
collection of elements, all of the same type. When indexing an array, the
program simply uses pointer arithmetic to move to the appropriate cell. But to
do pointer arithmetic, the compiler needs to know the amount of storage
required by an element of the base type.
In a one-dimensional array, every element is a simple type (or a structured
type, which cover later), whose size is known. Therefore, the compiler
we'll
knows how many bytes to skip when moving to the next valid address for that
type. The compiler knows how far to move for each address unit in a one-
dimensional array. Since it's your responsibility to stay within the array
bounds, the compiler also doesn't care where the last array element is. So, in a
one-dimensional array, you can leave out the array size in the parameter
declaration.
two or higher dimensional array, each element is another array in
In a
itself The compiler has no way of knowing how large one of these arrays is,
unless you tell it. If you had declared the parameter in the preceding program
as mat[][], and then asked for niat[l][0], the compiler would not know where
the second row of mat started, because it hadn't been told the size of each row.
If the compiler is told that each row is a three-element array, it will know how
The prototype declaration for times two( ) in main( ) also includes the
size of the second dimension. The compiler issues a warning if the size of the
second array dimension is not included in the prototype. Thus, the program
will compile with the following declaration:
However, the compiler will then let you know that there's a discrepancy
between the prototype and the function declaration. Giving the compiler the
information needed for such checking is a major advantage of using
prototypes.
Multidimensional Arrays
and Pointers
Let's look briefly at how indexing works in multidimensional arrays. This
should provide an additional perspective on such arrays, and should also give
you a chance to practice your pointer arithmetic.
As in the one-dimensional case, the array name, matrix, is a label for the
address of the first element. That is, matrix is equivalent to &matrix[ 0][ 0].
Back then, we found that *(entry + 1) returned the second element in the array.
What is *(matrix + 1)? Using the reasoning in the one-dimensional case,
this is the second array element. But matrix is an array of arrays. That would
mean that *(matnx + 1) was the name of an array. The following program
shows that this, indeed, is the case:
main ()
/* initalize array */
for ( row = 0; row < MAX.ROWS; row++) /* lor each row •/
for ( col " 0; col < MAX.CQLS; col++) /* lor each column */
printl ( "Row '/.d. Col y.d y.2d at location y,5d\n",
:
*(m
; , ; ;
#define MAX.ROWS 4
#define MAX. COLS 3
main ()
/* Initialize array */
for ( row - 0; row < MAX ROWS; row++) /* for each row */
<
lor ( col « 0; col < MAX.COLS; col++) /* for each column */
matrix [ row] [ col] » 10 * row + col;
}
main ()
{
int hyper.matrix [ MAX.DIMl] [ MAX.DIM2] [ MAX.DIM3]
int diml, dim2. din3;
/* display hyper.matrix */
tor ( diml - 0; dlml < MAX.DIMl; diml++) /* lor each matrix */
{
/* display matrix */
for ( dim2 » 0; dim2 < MAX_DIM2; dim2++) /* for each row */
<
/* display row */
for (dim3 - 0; dim3 < MAX.DIM3; dim3++) /* for col */
{
printf "diml '/.d, dim2 '/.d, dim3 '/.d :",
C
Pointer Arrays
You can define arrays of just about any type, including pointers. The following
example of an array of pointers will provide the first example of more complex
definitions:
#define MAX.SIZE 5
main ()
/* initialize pointers */
ptr.array [0] -til;
ptr.array [ 1] - 412;
ptr.array [ 2] - 413;
ptr.array [ 3] - 414;
ptr.array [ 4] - 415;
index
; ,
of the base type. In this case, ptr_array[ 0] contains a/70/>7?er. Thus, writing
the value of this cell will not give us an int. Rather, we get an address. To find
the value stored at the pointer's target value, we need to apply the indirection
operator. So, we need to look at *ptr _ array[ 0] for the value of the variable to
which the first array element is pointing. The target value for this pointer
happens to be il, whose value is 1 . If you look at the output, you will see that
String Arrays
main
Hello there
Hello again
This is prompts [ 2]
This is the next prompt
And this is the last one
: ; , ,
Notice that the array is global. That way, we were able to initialize it with
several string constants. This is common
programs that need to display
in
adapt the array for different programs. We'll find a use for such arrays in later
programs.
Finally, the following program combines many of the concepts presented
in this chapter, by performing a simple sort on the strings pointed to by the
array, prompts[]:
#define MAX.SIZE B
main ()
<
Int index, outcome:
Int puts ( char *)
void bbl.sort ( char *[]. int);
printf ( "•***»»*\n")
/• display each string in the sorted array •/
for ( index - 0; index < MAX.SIZE; index++)
outcome puts ( prompts [ index]);
Hello there
Hello again
This is prompts [ 2]
This is the next prompt
And this is the last one
Summary
In this chapter, we've covered lots of material. We've looked at arrays and their
relationship to pointers, learning how to define arrays of one or more dimen-
sions, how to access individual elements in an array, and even how to use
arrays to accomplish some of the same tasks as pointers.
We've also looked at a particularly useful class of arrays, namely, strings.
Although these arrays of characters have many of the same properties as other
arrays, they also have their own unique features. In particular, the use of
pointers to define string variables ismuch more common than using pointers
to mimic other types of arrays. The QuickC Run-Time library provides
numerous functions for handling strings.
In the next chapter, we'll have more to say about functions, and we'll try to
consolidate the things we've learned so far. In the process, we'll add to the
function collection that we've been building in our programs.
More on Functions
and More Functions
This chapter contains several miscellaneous topics that add some minor points
about functions and provide an opportunity to collect some of the functions
we've developed so far. You'll learn about functions that return pointers, and
we'll look at some error handling techniques. You'll also find some new
functions to add to your collection. Finally, the chapter provides a brief
introduction to recursion, a powerful programming technique in which func-
tions can call themselves.
329
:: : ; :
You've seen how to define functions that return the simple data types we've
discussed so far. In C, functions can also return pointers to simple and
complex data types. For example, the following program uses the build str( )
function to combine three other strings into a new sentence. This function
returns the address of a string (pointer to char) containing the new sentence.
#define MAX.STR 80
main ()
{
char *subject = "The cow ";
char 'verb = "jumped over ";
char *object » "the moon";
char *sentence:
char *build_8tr( char *, char *, char •)
This program writes "The cow jumped over the moon" and the value of subject
to the screen. Let's look at the program.
The main( ) function calls build_str( ) with three strings (defined as
pointers to char, so they could be initialized) as arguments. The build_str( )
function makes a longer string by combining the three string parameters. The
function returns a pointer to the first character of the resulting string.
Look at the function definition for build_str( ). Just as you would use
char to specify the return type for a function returning a character, you use
char *when defining a function that returns a pointer to a character.
Remember that, in definitions, the indirection indicator tells you the type
involved is a pointer to the base type rather than an ordinary variable of that
type.
The function prototype for build str( ) in main( ) also uses the * to
declare the function as returning a pointer to char. Concerning the prototype,
we can ask the same kind of question we asked when defining an array of
pointers: why doesn't this syntax indicate a pointer to a function returning a
char?
The answer takes the same form as for the earlier question. The paren-
theses represent an operator in C. '\\\t function call operator, ( ), has higher
precedence than the indirection operator The precedence of the function call
main
{
int result;
int *get_value (char )
result " *(get_value ( "value to return?"));
prlntf ( "%d\n" result)
,
This program writes the target value for the pointer returned by get_value( ).
The indirection operator in the assignment to result tells the program to get the
; ;
value directly, in this case, and assign this value to result. By using the
indirection operator (thus getting a value right away), the program ensures
that an int value is stored in result, and that this value will be accessible even if
and it will probably be reused the next time the function is called. Because rptr
was pointing to the area of memory used to store information local to
get_value( ), the contents of its target location can be changed by a call to
get_value( ). Here is the program:
main ()
{
int result, reeult2, *rptr;
int *get.value (char *)
return ( ftvalue )
value to return? 38
value - 38; tvalue - 4368
value to return? 94
value S4; Icvalue • 43B6
Notice that the value of *rptr changes after the second call to get value( ),
without any assignments involving *rptr. You'll see why if you look at the
address stored in rptr and the storage used for the local variable value.
In this case, the same memory location has two names, or aliases. We saw
earlier that aliasing could get you into trouble, in a situation such as the one in
the preceding program, aliasing may be more difficult to detect because the
names occur at two different levels of scope.
String Pitfalls
String handling can also get tricky in C, because of subtle differences among
the variety of waysyou can define and manipulate strings. In this section, we'll
look at some programs that illustrate common errors to be avoided.
In an earlier program, we defined three character arrays in function
build_str( ), and copied {not assigned) the parameter strings into these arrays.
The actual string building was done using the local copies of the strings. The
following program shows what happens if the function works directly with the
parameter strings.
; ; . :
#define MAX.STR 80
main ()
<
char *sub]ect "The cow ";
char •verb "jumped over ";
char *obJect "the moon":
char »sentence;
char *bad_bulld_str( char *, char *, char *);
>
if ( ( 11 - strlen
( first)) + ( 12 - strlen ( second)) < MAX.STR)
strcat ( first, second);
if ( ( 11 - strlen ( first)) + ( 12 • strlen ( third)) < MAX.STR)
strcat ( first, third);
return ( first)
Notice that the value of subject has been changed by the function call because
strcpy( changes the value of its
) first argument. The following version of
build_str( ) won't work properly either, for a similar, but subtler, reason.
String assignments, such as tl = first, merely store an address; these assign-
ments do not transfer string contents. Therefore, the assignment just creates an
alias for the parameter string. When strcat( ) does its work, the parameter
string is changed, as in the previous example, as is shown below.
tl - first;
t2 second;
t3 third;
if ( 11 - strlen (
( tl)) + ( 12 - strlen ( t2)) < MAX.STR)
strcat ( tl, t2);
if ( ( 11 - strlen ( tl)) + ( 12 - strlen ( t3)) < MAX.STR)
strcat ( tl, tS)
return ( tl)
; ; ; ; ; ; ; : ;
The assignment just stored an address; the assignment does not make a
copy of the string contents. Therefore, any changes made to tl are also made to
subject, since the two names are ahases for the same memory location.
The library function, strcpy( ), does make an actual copy of the string.
This means any changes made in tl are confined to the function's duration and
scope. That's why the first program in this chapter worked without changing
the parameter variable.
«deflne MAX.STR 80
»define MAX.ENTRY 6
main ()
<
char *temp;
char subject [ MAX STR]
char verb [ MAX.STR]
char object [ MAX.STR]
char sentence [ MAX.STR]
char 'build.strC char *, char *, char •)
char *get. random. str ( char *[], int)
Int index
return ( tl)
}
int randO
int element, index;
char chosen.str [ MAX.STR]
Notice how temp is used. First, we assign the pointer returned by the
function get_random_str( ) or by build_str( ) to temp. Then we copy the
string to which temp is pointing to a different string. Why did we use such a
roundabout way of getting the strings we wanted? Why not return the new
string directly to the appropriate string?
You cannot have an array name on the left side of any assignment
statement other than an initiahzation at definition time. To get around this
problem, you need to define the variables as pointers to char instead of as
character arrays. The drawback there is that you're not allocating any memory
for a string.
The second reason is and explains why simply using pointers to
subtler,
char won't do the trick. Suppose we did define subject, verb, object, and
sentence as pointers to characters (char *) instead of as arrays of char.
Suppose also that the program returned the strings resulting from the function
calls directly to these strings.
When get _ random _str( ) returns its result, the function is returning an
address: the contents of a local pointer variable. The storage referred to is
local storage. Suppose this variable points to address location 4000. The
function return means that the variable on the left side, subject, now contains
the same address as the local variable. The program has not made a second
copy of the string whose contents start at location 4000; rather, the program
has merely made a second variable point to the same copy of the string.
After the first call to get_random_str( ), things may still be all right, but
the next call to get_random_str( ) will almost certainly upset things. When
the program puts the environment for get_random_str( ) into memory, there
is a good chance that roughly the same areas of memory will be used as in the
previous call — since memory for a function's local environment is given back
when a function finishes executing, theprogram generally uses the next
available memory when executing a function call, and all variables and formal
parameters in the functions are local. This means the storage allocated for the
parameters can be reused when the function ends.
When get _ random str( ) is called a second time, there is a good chance
thatthe storage around location 4000 will be used again, quite possibly to store
a new string. As a result, the memory to which subject is pointing is being
changed by the second function call. Results will be incorrect after all three
components have been determined using get _ random _ str().
By passing the result to a pointer variable, then making a copy of the
string to which the pointer is pointing, you avoid this difficulty. strcpy(
338 Using QuickC
creates a second version of the string so the program can safely write over the
first version.
Earlier, we found that some files were opened automatically when a C program
started, and were closed automatically when the program finished. We dis-
cussed stdin and stdout at that time: these two files are used by scanf( ) and
printf( ), respectively. Ordinarily, stdin refers to the keyboard and stdout refers
to the screen.
Another useful, but less frequently used, file can also be opened automat-
ically when a program starts. The file stderr is generally used to display error
messages. By defauh, this file is the screen in DOS. If you tell your program to
read the stdio.h file, stderr is opened for you, and will be closed when the
program ends. Do not try to open and close this file yourself
Although both stderr and stdout refer to the screen by default, there is an
important difference between them. You can easily use the DOS redirection
operators (> and »)
to redirect stdout. to a disk file, for example. Ordinary
redirection has no effect on stderr. It remains associated with the screen, even
if stdout has been associated with a disk file.
This means you can have your program write its normal output to a file,
but have it display any error messages to the screen. Thus, you get the
advantages of error reporting without cluttering up the output file. The
following program shows how to write error messages to stderr:
main ()
Int count;
More on Functions and More Functions 339
1: 0.00000
0.33333
0.50000
3 18 divisible by 3
0.60000
0.66667
0.7142g
e iB divisible by 3
0.75000
0.77778
0.80000
is divisible by 3
0.81818
0.83333
0.84616
13 is divisible by 3
0.86714
0.86667
0.87500
15 is divisible by 3
0.88336
0.88889
0.80474
'ii
18 is divisible by 3 '^\
10: 0.00000
30: 0.00476
The program counts from to 20, and displays an appropriate fraction at each
1
value. The program also checks whether the current value is divisible by 3, in
which case the program writes a message to stderr.
Notice in the output that everything seems to be written to the same place.
There is no distinction between stderr and stdout because the default for each
or close stderr, the status of this file is from stdin and stdout. The
different
latter two files are known to QuickC immediately, whereas stderr must first be
defined for the compiler.
The stderr file is used with fprintf( ), a version of printf( ) for which you
can specify a file to which the function is to write. In this case, we've specified
stderr. Note the order of the parameters.
Notice the condition in the By asking for the negation of the
if statement.
expression, count % 3, we get a nonzero value whenever this expression is zero.
That is, the condition is true whenever count is divisible by 3.
2. Type CTRL-d, then C, to clear any breakpoints currently set in the file.
3. Type CTRL-D, then L, to clear any watch variables set in the file.
5. If you don't have debug set to on, type D to set this option to on. Then
press ENTER to compile the program.
7. Move the cursor to the hne containing the condition for the if
statement.
12. Step through the program using the F5 key. Notice that the program
stops at the second breakpoint line (fprintf( ) ) only when count is a
multiple of 3 — that is, only when the condition evaluates to nonzero.
Any time the condition fails, the second breakpoint line is bypassed.
More on Functions and More Functions 341
To see the difference between stderr and stdout, run the program (which
we'll call errdemo) using redirection. To do this, type
qc demo out
.
or whatever the appropriate command would be for the text editor you're
using. If you run the program and then look at the file, you'll see the following:
1
342 Using QuickC
^REMEMBER To use stderr, you need to include the contents of the file
exitO
C provides two functions for leaving programs earlier than anticipated. The
program will do some cleaning up before terminating. It can also pass along
information to the operating system about the reasons for program termination.
The exit( ) function allows a graceful exit from a program. If you need to
end a program immediately, you can use this function. It closes any open
output and flushes any buffers before ending the program. This allows you
files
#lncludo <Btdio.h>
#define MAX.COUHT 20
main ()
{
Int count:
1
More on Functions and More Functions 343
The program counts and displays values until it reaches a number divisi-
ble by 13, at which point the program terminates with the error message.
Notice the argument, 0, passed to exit( ). This int parameter represents
the "terminating status" of the program. A status of generally means normal
termination. Other return codes will represent different conditions. Check
your DOS documentation for information about these return codes. This
return value can be used by the DOS batch command, IFERRORLEVEL, as
shown in the following example, which assumes the preceding program is
echo off
testis
if errorlevel 1 goto early
echo program terminated normally
goto done
early
:
The Other function for leaving a program is _ exit( ). This function is just
like exit( ), except that _ exit( ) does not close files and flush buffers before
ending the program.
You've now learned four different ways of breaking out of a program or
program portion: break, continue, exit( ), and return. Table 12-1 reviews the
Method Use
break
344 Using QuickC
whether the denominator (the second argument) is zero. If so, the function
returns a predetermined extreme value.
swap_int( ) and swap — dbl( ) These functions swap the contents of the
two arguments passed between them. The changed values are passed back to
the calling function because the parameters for these functions are pointers. In
the case of swap _ int( ) the parameters are pointers to int; the parameters are
pointers to double in the case of swap_ dbl( ).
More on Functions and More Functions 345
dbl — mean( ) and int — mean( ) These two functions compute the aver-
age, or mean, of an array of values. Each function takes two arguments — an
array of the appropriate type and an int specifying the number of values to
generate — and returns a double whose value is the average of the array
elements.
These functions, along with the functions for generating random values,
form the beginnings of a statistics toolbox. For example, functions could be
added to compute the standard deviation of arrays of values or to generate
random samples with desired characteristics.
String Functions
In this section, we'll describe a few functions for handling strings and we'll
look at a program that will let you exercise the functions. First, we'll summar-
ize the purpose and usage of each of the functions. Then we'll look at the
program that lets you loop and test whichever string function you want.
delete. Note that the starting position is in terms of counting position in the
string, not in terms of the array subscripting for the string. Thus, to delete the
first five characters of the string str, you would call delete( ) as follows:
Notice that the function call uses 1 rather than as the second argument. You
are likely to count characters in a string to determine when to start deleting.
Since more natural for us to count from than from 0, the delete( ) function
it's 1
leaving out any occurrences of the character to be filtered in the process. The
function takes two parameters: the string to clean and the character to
remove. The following call would tell a program to remove all occurrences of
the letter r from the string str:
char pos( ) This function returns the array subscript of the first time a
specified character appears in the string. This function takes two parameters
and returns an int. The parameters are the string to search and the character to
seek. The function returns the array subscript of the first occurrence of the
character in the string. If the character is not contained in the string, the
function returns — 1
Note that the position returned by this function is an array index — unlike
the starting position specified for delete( ), which is a counting value. Thus, the
following call would return 0:
You might use the value returned by char_pos( ) as an array index; in that
remove— wd() This function removes the first word in a string and
returns this word. The function takes one string parameter and returns a
More on Functions and More Functions 347
The parameter is the string from which a word is to be removed, and the
string.
returned value is the word removed from the string. A word is any sequence of
characters followed by a blank.
If the string contains no blanks, then the entire string is returned as the
word, and the string is reduced to an empty The function deletes any
string.
leading blanks in the string before removing a word. Such a function can be
useful if you want to change the line breaks in a text file — for example, if you
need to print in smaller or wider margins.
The following call would assign the string "why," to word and would
remove this string from sentence, leaving that string with the value, "hello
there."
the global array of strings, nienu[]. This contains the messages displayed for
the menu options. You can tailor this menu to different programs, as we'll see
later in the chapter.
The show menu( ) function displays the desired menu and prompts the
user for a selection.The show menu( ) function takes two parameters: an
array of strings and the number of menu items currently initialized. This
function can be used, without modification, by any program containing the
appropriate type of menu. The only thing you need to change is the menu itself
The main( ) function contains one long switch construct. Each case in the
switch corresponds to a menu selection. Each selection lets you test a different
string function. The appropriate actions are specified for each selection. This
switch construct is inside a loop that continues until the user wants to quit.
Again, this strategy can be adapted for different programs. The only changes
required are in the actions corresponding to each switch case, and the value
displayed at the end of the switch. The program follows.
; ; ; ; ;
GLOBALS MAX.MENU
:
PARAMETERS :
printf ( "\n\n");
printf ( "\n\n");
/* prompt user */
printf ( "Your choice? (W to Xd) ", 0, MAX.MENU - 1);
main ()
default:
break;
"Be 1: /» delete ./
printf ( "Start? ");
scanf ( "Xd", ftetart);
printf ( "How many? ")
Bcanf ( "'/.d", fthow.many)
delete ( test.str, start, how many)
- J"
break
*^*° ® 2: /» reverse str •/
reverse.str ( test. str)
break
<="« 3: /, clean str */
pnntf ( "Char? ");
scanf ( "Xs", char.str)
to.clean = char.str [ 0];
clean.str ( test. str. to clean);
break;
= "« 4: /, char.pos */
printf ( "Char? ");
scanf ( '"/.s", char.str);
to.clean = char.str [ 0];
result - char.pos ( test.str. to.clean);
printf ( "Xd\n", result);
break;
case 5: /, remove.wd •/
temp. str = remove.wd ( test.str)
strcpy ( new. word, temp.str)
printf ( "'/,s\n" new.word),
break;
>
printf ( "'/.sVn" , test.str)
PARAMETERS :
/* swap first with last, second with second to last, etc., until
middle of string is reached.
PARAMETERS
char *str string form which to remove character occurrences;
:
*/
int old, new; /* array indexes for old and new version of string */
CALLS strlenO
:
PARAMETERS :
character to delete:
int how_many number of characters to delete.
:
PARAMETERS :
•/
Remove the first word from a string; a word is any sequence of characters
followed by a blank. Strip this word from the string being processed.
If no blanks are found in the specified string, the entire string is
returned as the word.
CALLS charposO. deleteO. strcpyO
:
PARAMETERS :
•/
Finance-Oriented Functions
In this section we'll introduce a few functions for computing some values
commonly encountered in financial contexts, such as banking, borrowing, or
saving. After the discussion of the individual functions, you'll again find a
program you exercise the finance functions. You'll see how to adapt
that lets
the menu array and the switch construct for this new group of functions. You'll
also find additional general purpose functions that can be used in other
programs.
resulting interest as a double. The following call illustrates the usage for
compound _interest( ).
More on Functions and More Functions 353
/* assume result, princ . interest, freq. and yrs «ire all double */
result comppund.interest ( princ, interest, freq, yrs);
annuity( ) This function computes the size of an annuity when the annual
interest rate, annual deposit, and number of years are specified. For example,
for an interest rate of 8.5%, with $300 deposited annually for 21 years, the
function returns $16,046.72.
The function takes three double arguments — a/7/7wa/ deposit, annual
interest rate, and number of years —
and returns the annuity size as a double.
The following call illustrates the usage for annuity( ):
includes additional functions useful for testing the finance functions. Try
running the program and trying out the functions to get a feel for what they do.
There are several things worth noting about the program. First, notice
again the global array of strings, inenu[]. This contains the messages dis-
played for the menu options. It was easy to initialize menu[] to the selections
required for the finance functions.
Again, the inain( ) function contains one long switch construct. Each case
in the switch corresponds to a menu selection. Each selection lets you test a
different finance function. The appropriate actions are specified for each
selection. The switch shell and the loop within which the shell is found are the
same as in the string testing program. The only differences are the actions
carried out when a specific switch case arises.
This program makes use of some of the functions we've built during the
earlier chapters, such as non zero( ) and safe division(). There is also a
new function, get dbl(), which takes a string parameter and returns a
double. The string will usually be a prompt of some sort.
The include files float. h and math.h contain definitions used by library
functions such as pow( ).
#lnclude <float.h>
#lnclude <math.h>
define MAX.MENU 10
#define TOLERANCE l.Oe-7 /* acceptable difference or error •/
define INVALID VAL -99999.999 /• val to return for invalid double */
define FALSE
define TRUE 1
int count;
print* ( "\n\n");
printf ( "\n\n"):
/• prompt user */
printf ( "Your choice? ('/.d to '/.d) ", 0. MAX.MENU - 1);
Divide num by denom, checking for division by zero before doing so.
Return quotient or a default value ( INVALID.VAL) on division by zero.
*/
if ( non.zero ( denom))
return ( numer / denom)
else
return ( INVALID.VAL);
>
Return true if val differs from zero by more than a predefined amount.
*/
main
show.menu ( menu, B)
scanf ( "Xd" ^selection)
,
switch ( selection)
{
default:
break;
case 0:
break;
case 1
prlnc - get.dbl ( "Mortgage? ");
interest • get.dbl ( "Interest? ");
months get_dbl ( "Months? ");
result " monthly.mortgage ( princ interest ,
months)
break;
GLOBALS :
PARAMETERS :
•/
PARAMETERS :
*./
GLOBALS :
PARAMETERS :
PARAMETERS :
Several of the functions in this section compute intermediate values on the way
to returning the desired value. Sometimes it can be helpful to check these
intermediate values, so you can follow the solution more easily, or because you
need to show your work.
Rather than embedding printf( ) commands throughout your source
code, you can use QuickC's debugging facility to watch the variables asso-
ciated with these intermediate values. For example, to check the values of term
and result in annuity _ deposit( ), you just need to compile the program with
the Debug option on, add term and result as watch variables, and set break-
points at the lines assigning values to these variables. Then step through the
program, noting the values of term and result at the appropriate times.
Recursion
Earlier, you learned about the flow of control when a program is executing and
when functions are called. Essentially, when main() (or any function) calls
another function (let's say, speciali$t( )), execution of main( ) is suspended
temporarily while specialist( ) executes. When specialist( ) finishes, control
returns to main( ).
Now, suppose that specialist(), itself, calls a third function (let's call it
does its work. Thus, while superspecialist( ) executes, there are two other
functions temporarily on hold in our example. If the program terminates
normally, these suspended functions will eventually be completed.
A recursive function is, in some ways, the ultimate specialist. It calls itself
to do part of its task. This means that one version of the function is put on
hold, while a different version is activated.
•
A recursive function takes a task. If the task is simple enough to be solved
right away — that is, if there is nothing left to do — the function solves itand
returns control to the calling function. On the other hand, if there is still work
to be done, the function calls a copy of itself to work on a slightly easier version
of the function. This continues until one of the calls occurs with a simple
enough version of the problem.
Let's look at a few examples. The
example simply counts to the first
the recursion. The recursive function, count( ), checks whether the parameter
is 1,which case the function displays the value of its parameter. If the
in
parameter is not the function calls itself with an argument one less than the
1 ,
parameter that was passed to it. Thus, if count( ) is called with a parameter
other than 1 , versions of count( ) will be put on hold, while other versions work
on a simpler task. Eventually, the parameter passed to a version of count( ) will
be at which point the function can display its value and return control to the
1 ,
#include <stdio.h>
int level;
main ()
For an input of 4 for how _ high, the program produces the following output:
This program counts to the desired number, but also displays some adminis-
trative information to help us see what happens in recursive calls. Let's look at
the output first, to see how the recursion process works.
The program begins executing in main( ). This function calls count( ) with
an argument of 4. At this point, main( ) is on hold, and count( 4) begins
executing.
The function count( 4) displays information about level of recursion. The
function then checks whether it can do parame-its work — that is, whether its
ter has value 1. Since 4 != 1, the function calls count( ) with an argument of 3.
At this point, main( ) and count( 4) are on hold, and count( 3) begins execut-
ing. Notice, count( 4) has not yet done its main task, which is writing the
"displaying" line.
Just as its calling routine did, count( 3) displays information about level
of recursion. The function then checks whether it can do its work — that is,
whether its parameter has value Since 3 != the function calls count( ) with
1 . 1 ,
an argument of 2. At this point, main( ), count( 4), and couni( 3) are on hold,
and count( 2) begins executing.
Just as the two previous calls to count( ) did, count( 2) displays informa-
tion about level of recursion. The function then checks whether it can do its
work — that is, whether its parameter has value 1 . Since 2 != 1 , the function
calls count() with an argument of 1. At this point, main(), count( 4),
count( 3), and count( 2) are on hold, and count( 1) begins executing.
The function count( 1) displays its information about recursion level.
This time, however, the test condition for the if statement fails. Control thus
passes to the next statement in the same function, which turns out to be the call
to priiitf( ) for officially displaying the value of val. Before leaving, count( 1)
writes some additional information about level of recursion. Control then
transfers back to its calling function, namely count( 2).
Notice that the last version of count( ) called was the first version to do
any work. One feature of recursive function calls is that the last version called
is the first one to do its work. The last one called is, therefore, also the first one
io finish its work. You can see this in the output from the program.
The function count( 1) writes the recursion level (4) when the function
starts. The function then displays its value for val, namely, 1. Finally, the
When control transfers back to count( 2), this function continues execut-
ing at the next statement — namely, ils call to printf( ) to display the value of
val. Notice that the value written this time is 2. You would expect this, since this
value was passed in as a parameter, and since the parameters are merely being
passed by value (rather than by reference) in the function. This means that,
while count( 1) was executing, there were 4 versions of val allocated — one for
each call to count( ). The versions do not interfere with each other, since each
version has a scope local to the function itself, and since different calls to the
same function are essentially different functions.
After count( 2) writes its value and its recursion level information, the
function transfers control back to its caller. Notice that count( 2) was the
second to last function called and is the second function finished. The same
process is repeated for count( 3) and for count( 4). Again, notice that
count( 4), the first function called, is the last to finish.
Without the statements that write information about the recursion level,
this function turns out to be very simple. A recursive function must include the
following types of statements:
1. The statements needed to carry out the task of the function. In the
preceding function, this would be the call to printf().
: . .
again — that is, to determine whether the function can simply do its
3. A statement that calls the function itself This call must be made with
parameter values that will eventually make the test condition take on a
value that will bypass the recursive call. In the preceding function, this
is accomplished by calling the function with arguments that approach
the termination value of 1
The function must test whether a recursive call is necessary before making
such a call. If you write your function so that the function calls itself first, then
tests whether it's all right to stop, the function will never terminate. Thus, the
following version of the recursive function will never terminate:
if ( val > 1)
prlntf ( »\t\t\t\t»*»» Displaying val: X3d\n" , val);
ttinclude <atdla.h>
#define NULL.CHAR 'NO'
»define MAX.STR 80
mainO
void recurse.reverse ( char *);
char curr.str [ MAX.STR]
recurse.reverse ( curr.str)
More on Functions and More Functions 365
Costs of Recursion
Recursive functions can make your code much more compact and easy to
read, but not necessarily more efficient. In fact, sometimes recursive versions
of a function can be very inefficient. In this section, we'll look briefly at an
example of a recursive function that becomes more inefficient as the values
used increase. If you don't feel comfortable with the numbers used here, don't
worry. The main point is to give you an idea of how the number of recursive
calls can grow. The example also shows that you can make more than one
recursive call in a function.
The following program uses a recursive function to compute a Fibonacci
number. Fibonacci numbers are elements in a mathematical series that seem to
occur in all sorts of situations. The definition of a Fibonacci number is simple
but recursive.
The two Fibonacci numbers, fib( 1) and fib( 2), are defined as I.
first
Number Value
nb( 1)
; ;;
The following program records each call to the fib( ) function. Otherwise,
it is identical to the preceding program.
•Include <Btdlo.h>
•include <nath.h>
long nrcalls 0;
main ()
<
long fib (int), fibans;
int seed;
Notice that the number of calls gets unacceptably high for even a rela-
tively small value of seed.
long fl - 1. /• fib ( n - 1) »/
12-1. /• fib ( n - 2) */
368 Using QuickC
if ( X > 2)
{
/* cases 1 and 2 are handled by the else *t
for ( Index » 3; index <» x; index++)
{
curr_fib = fl + f2;
/* as index increases, fl becomes f2, and
curr fib becomes fl.
*/
f2 - fl;
fl " curr.fib;
}
return ( c\irr_f ib) ;
>
else
return ( 1);
In this section, we've only been able to introduce some of the main
concepts associated with recursion. We'll see more examples in Chapter 17.
You may want to consult other sources. The C Library, by Kris Jamsa (Osborne/
McGraw-Hill, 1985) has a chapter on recursion, and includes numerous
additional examples.
Summary
In this chapter you learned more about functions, and learned some proce-
dures for error handling. The chapter also provided some additional examples
of C source code, including two short programs for exercising some special-
ized but useful functions.
In the next chapter, you'll learn about a variant of the main( ) function
that includes parameters. You'll learn how to write programs that allow you to
specify options on the command line when invoking the program. In the
process, you'll get another exercise program you can add to or adapt to your
needs.
13
Command Line^
Arguments
Under many operating systems, including DOS, you start a program simply by
typing the program's name. Thus, to start QuickC, you can type
and press RETURN. At that point, the QuickC environment will start up,
placing you in a file named untitled.c.
You can also start up QuickC with various options, including a file name.
For example, suppose you wanted to edit, then compile the contents of the
string exercising program from the previous chapter. Suppose this program
was in a file named str.c.
The following command line will start QuickC and will immediately put
you in the str.c file, ready for editing:
qc 8tr
369
370 Using QuickC
qc /Iqcstulf finance
So, we've seen three different ways to call up the QuickC program. Each
invocation puts you into a somewhat different mode. These three ways repre-
sent only a small fraction of the options you can specify when invoking the
program. QuickC is a very flexible program, capable of operating in different
ways.
This chapter will discuss how to give your programs the same kind of
flexibility.
main( ) as Program
and as Function
You've seen that C programs start executing at the function main( ). If a
program terminates normally, the program ends when main( ) has finished
executing. In a sense, main( ) is the program. On the other hand, because the
main( ) function can be put anywhere in the program, it doesn't really look any
different from other functions. In this sense main() is just an ordinary func-
tion.
Functions, as you know, can have parameters. You can define a niain( )
Ifyou define niain( ) with parameters, you are essentially passing parame-
ters to the program, since main() will start executing. Thus, the qc str
command invokes the QuickC program with the name of a file as a parameter.
c
When you interact with the operating system, you are generally giving com-
mands of one sort or another. We've already seen that typing a program's name
amounts command to run the program. A line containing such a
to giving a
command is called a command line.
As the examples above and your own experience should demonstrate, you
can specify more than just a program name on a command line. Such
additional terms on a command line are known as command line arguments.
Command line arguments are separated by blank spaces. Thus, str was a
command line argument for the qc command; /Iqcstuff and flnance were
command line arguments for qc in another example.
You may have been using command line arguments all along, perhaps
without even knowing that you were doing so. All of the following examples
include command line arguments:
A> diskcopy a; b:
A> qc /Iqcstuff /b
The first of these copies the file finance.c from the current directory to the
same name on a disk in drive B. In addition to the program name (copy), there
are two command line arguments (finance.c and b:). The second command,
diskcopy, also has two command line arguments (a: and b:). The next example
has only one argument in addition to the program name. The following
example actually has four command line arguments, besides the program; this
command displays the contents of each of the four files on the screen. Finally,
the last example invokes QuickC, using the qcstuff.qlb Quick library, and
rur\ning theprogram on a monchrome monitor.
Eventually, you will learn to write your C programs so that you pass the
372 Using QuickC
echo This line and the same line without the echo will be written
If you type showecho or showecho.bat at the DOS prompt line (let's say,
A> echo This line and the sane line without the echo will be written
This line and the same line without the echo will be written
You can also use echo in other ways, and we'll look at these different uses
of echo to find out how the command works.
echo with No Arguments If you type echo at the DOS prompt, the
following message will appear on your screen:
ECHO is on
Command Line Arguments 373
This tells you that DOS is in its defauh mode, which echoes (or displays)
whatever you type and whatever DOS has to say to you.
When you itself, DOS checks whether or not the default
type echo by
mode is to echo input and other information. Thus, if DOS finds no command
line arguments for the echo command, the response is to tell you whether the
echo mode is on or off
"on" or "off" as the first command line argument, the echo mode is set to the
specified value.
echo with a String If you type echo followed by an ordinary string at the
DOS prompt, two lines (such as those in the first echo example) will be
displayed on your screen. Thus, if DOS sees a sequence of characters, not
starting with either"on" or "off," the remainder of the command line is
interpreted as something to be displayed. (There are certain characters that
will make echo do additional processing, but we won't get into them here.)
Depending on how you give the echo command, DOS will take different
actions, and will interpret the contents of the command line differently. You
don't need to worry about how DOS does this. Essentially, DOS checks a
certain area of memory for anything you might have typed on the command
line. Based on what, if anything, it finds there, the echo command does what
you've asked.
such a listing by simply typing dir, or you can include command line argu-
ments to elaborate your request.
dir with No Arguments If you just type dir, DOS will list the files and
directories contained in your current directory. DOS interprets the absence of
command line arguments as an instruction to display the contents of the
374 Using QuickC
directory in which you are currently working, and to use the defauU directory
display mode.
Thus, typing dir at a DOS prompt produced the following listing:
dir with Switches You can also control the way in which the contents of a
directory are displayed. DOS allows switches to specify certain processing
options. Switches usually begin with a predefined character (/ in DOS and —
in UNIX), and are used to tell a command orprogram what options you want
to use when invoking the command.
The DOS dir command can understand two switches, /p and /w. The/p
switch tells DOS to fill the screen with directory entries, then to pause until the
user presses a key. The following command line leaves the partial directory
This command line tells DOS to display the contents of the directory
chl2, contained in the current directory. The /p switch tells DOS to pause
when the screen is filled, until the user presses a key.
After a key is pressed, the remainder of the listing is displayed.
DOS looks for a / in the command line arguments. If the line contains
such a character, DOS checks the character after it and acts according to the
intei'pretation it gives that character. A switch can be a separate command line
argument, or it can be at the end of a command line argument. Thus, the
following command — which leaves no space between the directory name and
the switch — would have the same effect as the command in the previous
example:
The /w option tells DOS to list only the file names and extensions,
writing five names on each line displayed. Using a /w switch in the previous
example would produce the following listing:
In this case, the /p switch has no effect, since the listing is not long enough
to fill the screen. Notice that DOS processes each switch included on a
command line.
Switches are a commonly used technique for letting users control the way
aprogram is to work. You will come across switches in many contexts, and will
probably find yourself using them in your own programs.
Arguments to main(
The basic strategy for writing a C program capable of handling command line
arguments is to pass the arguments to main( ) as strings. Once inside, it will
argc "5
; ,
The following command line produces the output shown below the
command line:
This program writes the command line arguments, but writes them in the
order the arguments were entered on the command line. Internally, the
which you're working. In some environments (including DOS 3.0 or later), the
first string in the argv[ ] array contains the program's name. This is not the case
\ -'th earlier versions of DOS.
function with either zero or two parameters, if you define the function with
two parameters, one must be an int and the second must be an array of strings
(array of pointers to char).
word on the command line is treated as a separate
In this context, each
string, thusbecoming a separate command line argument. Blanks separate
words from each other. The int parameter specifies the number of words on the
command line, including the program name. This value will always be at least
1, because points to the program name.
Although you can have only two parameters to main( ), you can have
virtually any number of command line arguments. This is because one of the
arguments is an array that can be of variable size. The actual number of
command line arguments supported is always operating system dependent.
The fact that the array elements must be strings is not a real limitation, as
some examples will show. You can enter numerical values on the command
line, read them as strings, then convert them to numerical form. The following
For each of the following calls to the program temp, the program pro-
duces the outputs below them:
temp f 98.6
98.60 F •= 37.00 C
temp c 100
100.00 C •" 212.00 F
temp f
Usage : temp <'f' or 'O <temperature>
This program reads the command line arguments as strings, just as in the
earlier examples. The program then calls the QuickC library function, atof( ).
This function converts a string argument to a floating point type, and returns
this as a double. Thus, the function lets you read numerical information into
your programs. The stdlib.h header file is needed to make atof( ) usable in your
programs.
Notice how we referred to the first letter in argv[ 1]: as element in this
is its on-line help with C and with the available library functions. Use this
facility to find out about atof( ). To get on-line information about the library
function atof( ):
;
3. Use the arrow keys to move the cursor downward, and select the Data
Conversion label from the list.
4. Select atof( ). Notice that there are other functions, with similar names:
atoi( ), atol( ).
5. Press RETURN.
The top part of the screen will contain information about the function,
including any header files required, a prototype for the function, and a brief
contains the keyword const before the declaration for the string parameter.
This keyword is a type specifier, and indicates that the lvalue associated with
the specifier cannot be modified. In this case, the const specifier makes the
parameter that is being declared a constant. This arrangement protects against
undesirable changes to the parameter that might surprise the calling function.
You can use the const specifier in conjunction with other type specifiers
(such as int, double), in definitions or declarations. The following two defini-
example, where the number of command line arguments may vary drastically
: ); ; ;
each time you run the program. The following program uses one of the
functions mentioned in the last chapter, int mean():
double result
int atoi ( const char *);
int index « 0;
int values [ MAX.SIZE]
{
int count:
double running. sum- 0.0;
This program computes the mean of a collection of int values which have
been entered on the command line. In this program, you can enter up to 30
values. In DOS, there is a limit to the number oi characters a command line
can have ( 127 bytes). This will indirectly determine how many arguments you
can pass to a program. If you think this restriction may cause problems in your
programs, check the documentation for your operating system to find out
more about the command line, and the possibility of increasing its size.
the listing does not include the source for the conversion functions.
The main program action occurs in two switch statements. The one in
main( ) acts on the basis of the number of command line arguments. The
second switch statement, in handle all values( ), acts on the basis of the
conversion requested by the user.
ffinclude <stdio.h>
#include <math.h> /* needed for atofO »/
ftinclude "units.c" /• contains unit conversion functions •/
^include "utils.c" /* contains miscellaneous utility functions */
break;
y
printf ( "XlO.Blf Xs -- XlO.Blf Xs\il" .
int Index;
• /• do nothing */
If ( index >- how.many) if no match was found */ /
return ( -1);
else
return ( index) ;
} /* END what.caseO */
/ function prototypes */
double cent.to.fahr ( double), fahr to cent ( double)-
double cm.to.inch ( double) inch to.cm ( double) ,
double result;
case 1 :
case 3 :
case 6 :
returns the subscript value of the first occurrence of substr inside str.
Returns -1 if substr is not found in str.
*/
This file contains three general purpose functions that are useful for the
conversion program. The make str lower( ) function converts a string to
all lowercase characters. This is important for the conversion program, since
the array containing the units uses only lowercase characters. The header file,
ctype.h, is needed for the tolower( ) macro. This file is actually a nested include
file, since the utils.c file is read into convert.c.
The str pos( ) function returns the array subscript corresponding to the
first occurrence of substr in str. If substr is not found in str, the function
returns — 1
Let's look at the algorithm used in this function. The basic strategy is to
move through str from left to right, one character at a time. Starting with each
character, the next step is to check whether substr is in str, beginning at the
return ( cm * INCH_PER_CM)
return ( kg • LB.PER.KG);
return ( km * MI.PER.KM);
return ( lb * KG.PER.LB)
return ( mi * KM.PER.MI)
return ( oz * GRAM.PER.OZ);
return ( qt » LITER.PER.QT)
390 Using QuickC
Summary
In this chapter you learned about a means of making your C programs more
flexible. By defining the main function to accept arguments, you make it
possible to call the program with command line arguments, thereby making it
easy to do things quickly with the program.
One of the most common uses of command line arguments is to specify
files to read and write. In the next chapter, you'll find out how to create and
open files, and how to use command line arguments to tell your program what
files to use.
14
Files and Memory
So far, all our programs have involved just the standard input and output
sources, stdin, stdout, and stderr. In the previous chapter, you learned how to
pass arguments to a C program. You should now be able to handle files
conveniently in C programs.
In this chapter, you'll learn how and close files. We'll also
to create, use,
look at some Run-Time library functions for working with files, and simple
programs for manipulating files, such as removing blank lines.
Memory is the second major topic of the chapter. We'll look at why you
would want memory while the program is running,
to get additional how to get
the memory when you need it, and how to use it when you have it.
391
392 Using QuickC
Files
the end of the stream. You also found that the standard files automatically
available to your program were closely related to streams. In this section, you'll
learn to define and use your own files.
From your perspective as programmer, files in C are pointers. When you
create or open a file in a C program, you get a pointer to a variable of type
FILE. This is a data type that contains information about a stream, such as its
location, size, your current position in the stream, and so on. FILE is defined
in stdio.h, so you need to include this header file in any program that involves
files.
Let's look at a very simple example, useful only for demonstrating how to
define a file. (If, by some coincidence, you have a file named zqzqzqzq on your
disk, change its name temporarily or change the string argument to fopen( ) to
a name that doesn't match one of your disk files.)
main
{
FILE •lile.ptr;
FILE *fopen { char *. char *);
Files and Memory 393
declared in stdio.h.
In line 3, the program opens a new file. The library function, fopen()
(which is declared in stdio.h), associates the variable name, file_ptr, with a
text stream. If saved properly, this stream will become a file on your disk. In
addition to returning a pointer to information about a stream, fopen( ) fills in
some of this information.
The first argument to fopen( name to use for
) is a string that specifies the
the disk file. When saved, the stream associated with nie_ptr will be a file
named zqzqzqzq on your disk. The second argument tells fopen( ) how you
want to use this file. In this case, you are telling the program that you want to
be able to write to this file.
If, for some reason, fopen( ) cannot open a file, the function returns a null
pointer. This indicates that no suitable target location could be found. The
example just shows the syntax for calling fopen( ). Ordinarily, you'll check for
an unsuccessful outcome when you first try to open a file, as in the next
example. In the current program, line 4 checks for a null pointer.
a
If was opened — that
file is, if the pointer returned by fopen( ) pointed
somewhere — line 5 closes the file. The function fcIose() takes a file as its
argument, and saves the file's contents to disk. This preserves anything written
file. Nothing was written
to the in the program, so the file, zqzqzqzq. contains
zero bytes, as you'll see if you do a directory listing. You can remove this file
• Open an existing file for both reading and writing, starting at the
• Open an existing file for both reading and writing, starting at the last
as the argument to fopen( ). If you already have a disk file with the specified
name, you'll lose the existing disk file. The w option always creates a new file.
You can't read from such a file; you can only write to it.
This option tells the function to try to open an existing file for writing. If the
function is able to do this, it moves you to the current end of the file and starts
writing from that point. If the you specified doesn't exist, the function
file
creates a new file with that name. You can only write to such a file.
Sometimes, you may need to read from and write to the same file. You can
enhance the preceding options to tell the function you want to be able to do
both.
Use r+ if you want to start at the beginning of an existing file, so you can
read and write your way through it. As with r, this option tells fopen( ) to look
for an existingand return a pointer to it. However, the r+ option
file, lets you
both read from and write to the file, once it is opened.
Use w+ to create a new file that you'll be able to read from and write to, as
needed. As with W, this option will destroy any existing file with the name you
specify.
Finally, use a+
add to the file (starting at the current position in the
to
file), while being able to read from or write to it. The current position is the last
^REMEMBER To open a file, use fopen( ), with two arguments. The first
argument is the name of the disk file to be saved. The second argument
specifies what you want to do with the file.
To close a file, use fclose( ), with the file pointer as its argument.
) ; ; ; ; ; ; ;
#include <Btdio.h>
{
FILE *notes; /* essentially, the file you defined */ ;
if argc < 2)
( /* if user didn't specify a file name */ •
j;
•/ '
i
if ( ( notes - fopen ( argv [ 1], "w")) !" NULL) ;
< ';
gets ( curr_Btr) ^
gets ( curr_Btr)
> /* END while no empty string */
else /
if unable to open file */
printf ( "Couldn't open file\n");
} /» END else (if argc >= 2) */
•} /* END mainO */
The program will create a file that will be saved to disk as file 7.10. Each line
you entered while the program was running will be written to file 7.10. When
you finish your note-taking by entering a blank line, the program will close the
disk Look at the contents of the file with your editor or by using the DOS
file.
TYPE command.
First, let's look at the major portions of the program. The body of inain( )
consists of an if statement and its alternative. If the program does not find a file
name as a command line argument — if there is only one element in argv[ ]
—
you see a "usage" message. The real work occurs if you have specified a file to
be opened.
The else construct also contains an if statement. If the log file was
successfully created, the program reads your input and writes it to the log file.
If the program is unable to open a file —
that is, if the call to fopen( ) returns a
null pointer —
the program displays an error message.
The pointer variable notes is used to refer to the file, and is defined the
_
same way as file ptr was in the earlier program. Similarly, the call to fclose( )
has the same format as in the earlier example.
The call to fopen( ) is made in a different context than in the first example.
This syntax for using fopen( ) is common in C programs. The main work of
creating a file is done in the assignment statement. This time, however, the
assignment is made within a test for the if statement. If the returned pointer is
NULL, the file could not becreated, the condition would befalse, and the else
portion of the selection construct would be executed. (Remember that argv[]
contains strings, so the first parameter to fopen() is valid.)
The other statement involving files is the call to fprintf( ). Recall that we
used this function when discussing stderr. The function works just like
printf( ), except that you can specify the file to which it writes. This program
writes to notes, the program's name for disk file 7.10.
^include <stdio.h>
) ; ;
feof ( notes)) if (
printf ( "\n\n End of file reached. \n")
fclose ( notes): /* close file */
} /* END file reading activity »/
else /• if unable to open file */
printf ( "Couldn't open fileVn"):
} /• END else (if argc 2) */ ~
} /• END mainO »/
This program, called reader, opens the file specified in a command line
argument (stored and reads from this file, line by line, until an
in argv[ 1])
error is encountered or the end of the file is reached. The program displays
each line as it's read from the file.
The only difference between the calls to fopen( ) in note and in reader is in
the option specifying what you want to do with the file. In this case, the r
option says you want to read from the beginning of the file.
This program introduces two new functions, fgets( ) and feof( ). The
fgetsO function is from a file. It is similar to the gets()
for reading strings
function you've seen already. There are some important differences between
the two functions, however. The functions differ in the number of parameters
they take, and in how they handle the newline character.
Notice that fgets( ) takes three parameters, whereas gets( ) takes only one.
The first parameter to fgets( ) specifies the string to be read. The second
parameter specifies the maximum number of characters to be read. The third
parameter specifies the file from which to read the string.
in the call to fgets( ) in reader, the function reads characters from the
specified file and builds a string (curr str) from these characters. The
(notes),
function keeps adding to curr str until it reads a newline character or until
MAX _ STR — 1 characters have been read. After it has finished reading, the
function adds a \0' to terminate the string properly. Notice that the
' maximum
398 Using QuickC
number of characters the function reads is one less than the function call
specifies. This is because the function needs to add the null character at the end
of the string.
Recall that gets( ) replaces the newline character with a null character. On
the other hand, fgets( ) keeps the newline character and adds a null character.
Because of this, the output from this program is double spaced. One newline
character is contained at the end of the string read, and another is at the end of
the string argument to printf( ).
Notice how fgets( ) is being used in the continuation condition for the
while loop in reader. If the end of the file has been reached, the function
returns an empty, or null, string. This is just a null character, 0, which evaluates
to false in C. Thus, the continuation condition tests whether an empty string
has been read.
Ordinarily, when you call a function in C, you'll assign its returned value to a
variable of the appropriate type. In that case, the function call will be on the
right of an assignment statement, and the variable to which the returned value
is being assigned will be the left operand.
Sometimes, however, you may want to call the function in order to
accomplish a task or to get information back through one of the parameters.
In that case, you needn't use an assignment statement; just call the function
with the appropriate parameters. We've generally been doing this when using
printf( ) and scanf( ). Recall that these functions actually do return values —
the number of characters printed, and the number of arguments processed,
respectively. However, we've been using these functions just to display or read
information, and haven't made use of the returned value. For this reason, the
calls to these functions have not been assignment statements.
Similarly, we've been using gets( ) and fgets( ) without making use of their
returned values because we've gotten the desired strings through the parame-
ters. However, unless the function is defined as returning void, it always
returns the appropriate type of value — whether or not your program uses this
value.
In the previous listing, we've implicitly made use of this property of C
functions by using the value returned by fgets( ) — looking for an empty string
in the continuation condition for the while loop. Thus, when the fgets()
; . )
function cannot read a proper string — because the end of the file has been
reached or because of another error —the function returns an empty string. If
the function returns a null string, then the function's "value" can also be
interpreted as false.
The following program removes blank lines from a file, then writes the
remaining lines to a new file. The program opens and closes both files, in for
reading and out for writing.
«define FALSE
#deflne TRUE 1
#define MAX.LIHE 100
<
FILE 'in; /» File used for input »/
FILE *out /* File used for output */
; ; ; ; ; ; ; ; ; ; , ;;
if (argc < 3)
{ /* Check if enough arguments */
fprintfCstderr. "BLANK LINE removal utilityNn")
fprintf(atderr, "Usage: deline in.filename out_filename\n")
exit(l):
}
/* Report on activity •/
printf ( "\n\nXB > Xs\n\n" , argv [ 1], argv [ 2]);
>
if ( index >- strlen ( the.str))
return ( TRUE)
else
return ( FALSE)
switch ( the.char)
{
case '
'
case '\n'
case '\r'
case '\t':
case *\0'
return ( TRUE)
break
default
return ( FALSE)
break
>
This program opens the first file specified, and reads its contents line by
line. Each line is examined to determine whether it contains anything other
than whitespace characters. If so, the line is written to the second file; other-
wise, the line is discarded, and is not written to the file.
Notice the commands for opening the two files. They are similar to the
commands in the earlier examples, and are just the standard way of opening a
file. You've seen fprintf( ) for writing material to a file. You've also seen puts( )
Let's look at one more program involving files. This one replaces any occur-
rences of the tab character (' \t') with the appropriate number of spaces:
•include <stdio.h>
; ; ; ) ; ; : ;; ;
#define FALSE
#deline TRUE 1
#deline CZ •\032- /* Ctrl-Z */
#deline TABSKIP 8
>
This program replaces any tab character encountered in a file with the
number of blanks needed to get to the next tabstop. The program then writes
the resulting string to a new To do its
file. work, the program reads character
by character from in, the source file, and writes character by character to out,
the detabbed file. The program uses the character handling macros you
learned about in Chapter 5, putc( ) and getc( ).
The CZ case in the switch construct is designed to deal with text editors
that pad out files with Control-Z characters. The detabbing program just
ignores these characters, and does not write them to the detabbed file.
Although you'll use files often in your programs, the number of things you'll
actually do with files is quite small. Before you can do anything with files
you've defined, you must open them, using the fopen( ) function. This function
gives you a pointer to a FILE.
Depending on what you intend to do with the file, you'll use either the
of numbers read from a file. Sometimes the file might contain only a few
values, and sometimes it might contain several hundred. You could define an
array large enough to hold the largest number of values expected. This would
use up lots of memory each time you ran the program, but would probably be
acceptable if you only had one such array.
On the other hand, suppose you had several arrays you needed to manipu-
late, each with a range of possible sizes. In such a situation it won't be practical,
and may not even be possible, to allocate enough storage for the largest
possible size of each. For example, suppose you had 10,000 bytes of available
memory, and you had five arrays, in each of which you might need to store as
many as 400 double values. For each of these, you would need 3,200 (400
values * 8 bytes per double) bytes. The 16,000 bytes required exceeds what you
have available. So you won't be able to define all five of these arrays — even if
you'll never need all the space in each of the arrays at the same time.
What you really need to be able to do for each array is get enough memory
to store the number of values you'll need to read for that particular run of the
program. For example, suppose you had 400, 200, 100, 100, and 200 values for
the 5 arrays. Using space efficiently means you would need 3,200 + ,600 + 800 1
+ 800 + 1,600 = 8,000 bytes, which is under the 10,000 byte limit. With a
means of allocating the required storage when you need it, you would be able
to run this program.
The solution is a technique called dynamic memory allocation, which
memory as you need it. For a situation such as the above,
allows you to allocate
a dynamic storage allocation strategy would grab enough memory to hold 400
double values, then 200, then 100, and so on. C implementations include
functions for allocating memory in this manner. Before we look at some
examples, you need to find out about another operator, for converting infor-
mation from one type to another.
So far, you've seen that the C compiler can convert information from one type
to another to evaluate an expression. Sometimes you'll want to force such
conversions.
For example, suppose you have a double variable, which you need to
display in integer format. You can't simply include an int placeholder and pass
printf( ) a double argument. The outcome will not be correct, as the following
:
program and output show. One way to solve the problem is to assign the
double to an int variable, then pass this variable as the parameter to printf( ).
This is effective, but very roundabout.
The C cast operator () — lets you make such conversions much more
—
directly. The following example illustrates the use of this operator, and shows
main ()
<
double dl • 10;
This program produces the following output. Notice that the first line is
incorrect:
as int
as (Int) 10
This program tries to write the contents of a double variable as an int. The
first time, the double is passed directly, and an incorrect result is written. The
second time, the double is first converted to int by using the cast operator — in
this case, (int) — to copy the value to an integer internally, so that the proper
result could be displayed.
To use the cast operator with an expression, simply precede the expres-
sion with the type to which you want to cast the expression. This "target type"
must be in parentheses, because these parentheses represent the cast operator.
The cast operator is a unary operator. As such, it has very high precedence at —
the same level as the address, indirection, and sizeof( ) operators. For example,
to convert the result of evaluating 7.0 * test val to integer, you would write:
.Recall that the unary operators associate right to left. This means that if
you have multiple unary operators in succession, they are applied from right to
left. For example, what will be displayed in the following?
main ()
<
int il - 10. 12 - 10;
. ;
This program writes the values - 1 1 and —9 in double format. Let's see how the
program arrives at these values. The first call to printf( ) builds its argument as
follows, by applying the unary operators from right to left.
The second call works in a similar manner, except that the value of 12 after
step 1 above == 9, rather than II.
Why is the call to printf( ) in the following program invalid? If you're not
sure, compile the program to see the error message.
main ()
<
int 11 = 10;
Applying the cast operator produces only values, not variables. That is,
the cast operator never returns an lvalue. The operator simply returns a
temporary value of the appropriate type, for use in an expression. The pro-
gram can assign this temporary value to a variable, if necessary.
In the next section we'll see why converting to a pointer variable can be
useful.
#include <stdlib.h>
#define HAX.SIZE 10
main ()
{
int int.array [ MAX.SIZE]
int *int_ptr, index;
if ( int ptr)
{
for { index - 0; index < MAX.SIZE; index++)
dnt.ptr + index) - int. array [ index] ;
printl ( "\n\n");
for ( Index - 0; index < MAX SIZE; index++)
{
printf "%2d: int_ptr + index == y,u: ",
(
[ index] ^= 9
This program initializes an array of int, then calls the function malloc( ) to
allocate space for as many integers as are contained in the array. The starting
address of this allocated memory is assigned to the pointer variable, int ptr.
The contents of the array elements are assigned to the successive storage
locations in the memory allocated by the call to malioc( ).
The program then displays the contents and addresses of the array cells
and of the MAX SIZE integers stored in consecutive integer slots starting at
location 4306 (the first cell allocated by the call to nialloc( )).
The dynamic memory allocation takes place in the statement that assigns
an address as the value of int ptr. Notice that the right operand of the
assignment statement involves a cast operator. The malloc( ) function actually
returns a pointer to an unspecified type. The cast operator makes the compiler
Files and Memory 409
interpret the returned value as a pointer to int. In this way, the appropriate
amount of storage is allocated, so that integer pointer arithmetic will work.
The call to malloc() returns enough space to store a MAX _ SIZE
element array of int. In addition to ensuring that the space is available, the use
of mallocO also guarantees that the storage is in consecutive memory loca-
tions.The advantage is that there is no need to define a huge array at the
more closely at nialloc( ). If you look at the documentation for malloc( ), you'll
see that the function is defined as returning a pointer to void, void *. You'll also
see that its parameter is a variable of type size_t.
The argument to malIoc() represents the number of bytes of storage
requested. The identifier size_t is simply a typedef for unsigned int, as you'll
see if you look in the file stdlib.h. This type is intended to represent values of
the sort the sizeof( ) operator returns. Making argument unsigned
the
increases the range of values you can pass. This can be useful if you need to
allocate very large amounts of storage.
The return type, void *, is really a placeholder that makes malloc( ) usable
with any variable types. The Draft Proposed ANSI Standard says that a
pointer to void may be converted to a pointer to any type. In our program, the
conversion was to a pointer to int, so that each address unit is two bytes. Had
we cast the result from malloc( ) to pointer to double, the size of each address
unit would have been eight bytes.
The nialIoc( ) function returns a null pointer if it cannot allocate the
amount of storage your call requested. It is always a good idea to check that
the storage was actually allocated, as in the next example.
Using malloc()
The following program generates a specified number of random numbers, then
writes these to a file:
•include <Btdlib.h>
; ; ;
#include <stdio.h>
#deflne MAX.SIZE 200
if ( argc < 3)
printf ( "Usage: getrand <# values> <flle naTne>\n");
else
{
/* convert the first command line argument to a number */
val " atoi ( argv [ 1] )
if int.ptr 1= NULL)
(
exit (1);
}
41
; ; ; ;
il ( argc < 3)
printf ( "Usage: readvals <# values> <Iile nanie>\ii");
else
{
/* find the number in the first command line argument */
val - atoi ( argv [ 1] )
With the preceding program stored in a file named readval.exe and the
data in the randout file created in an earlier example, the following function
call would produce the output below it:
Files and Memory 413
readval 23
414 Using QuickC
callocO
The mallocO function simply you the number of bytes you want, and
gets
leaves it up to you to partition those bytes and to assign values to them. There
is a related function, calloc( ), that also returns a pointer to an area of
contiguous storage. The calioc( ) function works in a slightly more structured
manner, and it even initializes the memory returned.
The calloc( ) function takes two arguments — the number of elements and
the size of each element — and returns a pointer to void, which your program
Usage for calloc( ) is the same as for malloc( ). Thus, the following would
allocate storage for a 200 element array of double and would make dbl_ptr
point to the first element of this array. Each element in this array would be
initialized to 0.0:
C provides the realloc( ) function for dealing with situations such as this.
arguments —
a pointer to the existing storage collection, and the new size (in
bytes) —
and returns a pointer to void. This pointer references the first element
of a contiguous area of memory having the desired size.
main ()
<
double dl , *dbl_ptr;
11 ( dbl_ptr I- NULL)
<
/* display starting and ending addresses */
printf ( "ftdbl.ptr [ 0] == Xu; tdbl.ptr [ 149] == '/.uNn"
tdbl.ptr [ 0] tdbl.ptr [ 149] )
,
if ( dbl.ptr !- HULL)
/* display starting and ending addresses */
printf ( "ftdbl.ptr [ 0] =« Xu; ",
ftdbl.ptr [0]):
"ftdbl.ptr [ 199] =- y.u\n"
printf (
ftdbl.ptr [ 199] )
} /» END if not a null ptr */
else
printf ( "null pointer\n")
and writes the addresses of the first and last elements in this array. Then the
program tries to reallocate storage for a 200 element array of the same type.
Finally, the program writes the addresses of the first and last elements in this
array.
The intent when using realloc( ) is to expand the original array to a larger
Data from the original array should not be lost. When realloc( ) does its
size.
work, it will simply return the same starting address if enough contiguous
416 Using QuickC
storage is available immediately after the last cell of the original array, as in this
example.
If this is not possible, realloc( ) will try to find the storage for a large
enough array elsewhere. If successful in this search, the function will then
transfer the contents of the original array to the new location, and will release
the storage that had been allocated for the original array. In the second case
reaIloc( ) moves the array into a new, bigger location.
In the previous program, the function was able to simply allocate the 50
additional double storage spaces immediately after the original array. In other
situations, this may not be possible because the storage right after the original
array is being used. In that case, the function moves the array's contents to a
new location. Because the original array may be moved, you should not count
on the value of the pointer parameter being the same before and after the call
to realloc( ).
The realloc( ) function can be useful, but you must use it carefully. Like
other storage allocation functions, realIoc( ) returns a null pointer if it is unable
to allocate the desired storage. I n this case, however, a null pointer means that
your original storage has been lost.
^REMEMBER Be careful when using realloc( ). You can lose your original
data if something goes wrong. Always test the value of the pointer realloc()
returns.
So far, you've seen three ways of obtaining storage space and reserving it for
use in a program. In this section, you'll learn about a way of returning storage
to the program for reuse.
Suppose you had a program that needed to manipulate as many as five
large arrays, but that your available memory could only fit four of these arrays
at any one time. The free( ) function lets you "deallocate" storage. This makes
the storage available for use at another point in the program.
To deallocate storage, call the free( ) function with a pointer parameter. In
the function definition, this parameter is a pointer to void, for which you can
substitute a pointer to the appropriate type. The pointer must refer to storage
allocated with either the inaIloc( ), calloc( ), or realloc( ) functions. The free( )
function does not return any value. Using free() with a pointer allocated by
other means causes an undefined result, and is likely to cause storage errors.
: ;
^REMEMBER You can only use free( ) to deallocate storage allocated with
mallocO, callocO, realloc(), or similar Run-Time library functions.
2. When you've finished with one of these arrays, use free( ) to deallocate
the storage.
The following program lets you see how the available storage changes as
the amount of storage allocated varies. n some cases, the program is unable to
1
allocate the storage, in which case the return from _memavi() remains
unchanged.
main ()
<
double dl 'dbl.ptr •dbl.ptr2
, ,
unsigned memory
int rand index, how.many;
.
/* allocate storage •/
dbl.ptr " (double *) calloc (how.many / 2, aizeol (double));
.memavl = 61286
Files and Memory 419
There are certain common errors to watch out for when dealing with dynamic
memory allocation. These errors concern errant or lost pointers and unavail-
able space.
First, do not lose access to your starting value. This means you should
always have a way of getting to the first element of the array to use as an origin
for pointer arithmetic.
Second, don't change the value of a pointer to a collection of data. You'll
lose your real data,and may damage your environment when you try to write
to the new (and unknown) data area.
Finally, your program should always make sure it has gotten the storage it
requested. Don't assume you have the storage and begin writing to it. You may
overwrite important information by mistake.
Summary
In this chapter you learned how to open and use files, including how to read
from and write to them. File handling is particularly convenient if you use
command line arguments, since that makes it much easier to specify the files
with which the program should work.
You also learned how and deallocate storage dynamically, as
to allocate
needed while running a program. This capability is handy for certain types of
data structures whose size will not be determined until the program is actually
running.
In the next chapter, we'll cover some miscellaneous topics and operators
for manipulating individual bits in variables.
Bit and Othei
Operators
The first chapter claimed that C lets you manipulate data at the level of
individual bits. In this chapter, you'll find out how this is done. In addition,
you'll encounter a new operator.
Bitwise Operators
421
; ; ; ; ;; ; ; ; ; ; .
variables are also stored as bit sequences. However, for these types, different
parts of the bit patterns serve different functions — fraction and exponent, for
example. Therefore, the bitwise operators do not apply to floating point
types.)
Bit Patterns
Before discussing the bitwise operators themselves, some examples let's look at
main ()
Int value
int make_bin_str ( char *, int);
char curr_8tr [ MAX.STR]
char val.str [ MAX.STR]; /* value to convert, read as string */
do
{
printf ( "%d " value) : ,
else
printf ( "string not built correctly:: y,s\n"
curr.str)
else
value X* index;
: 0000000000000000
? 2
2 : 0000000000000010
? -2
-2 : 1111111111111110
? 10
10 : 0000000000001010
? -10
-10 1111111111110110
:
? 1024
1024 0000010000000000
:
? -1024
-1024 1111110000000000
:
? 32767
32767 0111111111111111
:
? -32767
-32767 1000000000000001
:
The program uses gets( ) to read a string, then converts this input to an int. The
16-bit pattern for this value is returned in a string by make bin str(). The
program displays this string.
getting input than using scanf( ), because scanf( ) can get confused if it encoun-
ters the wrong type of information, such as a string, when it expects a
numerical value.
The make _ bin _str() function works with a long MAX PWR value
to avoid difficulties with the leftmost bit, the sign bit on regular int variables.
Note that in the program you can only get the bit pattern for if this is the
first binary representation you ask for. After the first time through the loop,
the program checks at the bottom of the do-while loop, so the bit pattern for
is never computed again.
Although the bitwise operators have the same kinds of names as the
logical operators — AND, OR, NOT — there are some important differences
between them. The logical operators work with entire expressions, and always
return a value that can be interpreted as true or false. The bitwise operators, on
the other hand, work with each individual bit of their operands. The results
can be any integral number within the allowable range. Thus, the logical
operators return logical values, whereas the bitwise operators return numbers
having the appropriate bit patterns.
main ()
int value
int make_bin_str ( char *, int);
char curr.str [ MAX.STR]
char val_Btr [ MAX.STR]; /* value to convert, read as string */
else
printf C "string not built correctly:; %s\n"
curr.str)
else
printf ( "string not built correctly:: XsNn"
curr.str)
unsigned index;
int outcome, bin.place 0;
for ( index - MAX.PWR; index > 0; index /- 2)
{
outcome • (int) ( value / Index)
if ( (outcome --1) (outcome -- -1)) I I
value X* index;
>
?
: 0000000000000000
" -- -1 1111111111111111
:
? 1
1 : 0000000000000001
- -- -2 1111111111111110
:
? -1
-1 : 1111111111111111
== 0000000000000000
:
? 2
2 0000000000000010
"
:
-3 1111111111111101
:
? -2
-2 1111111111111110
~
:
1 0000000000000001
:
? 32767
32767 0111111111111111
~
:
? -32767
-32767 1000000000000001
"
:
? -32768
-32768 1000000000000000
' ~ 32767
:
0111111111111111
:
This program does the same things as the previous program and, in
addition, it computes and displays the binary representation of the bitwise
negation of each value.
Notice that the bitwise complement, or negation, of a number is not the
same as the arithmetic negation. Thus, —5(1111 1111 1111 1010) does not
represent the same number as —5 (1111 1111 1111 1011). However, both the
arithmetic and bitwise negation operators produce a positive number from a
negative one, and vice versa.
I
The results of such an operator can often be shown in a truth table. For
the bitwise AND operator, combining bits from left and right operands, the
left
bit
; ;
;; ;
; ; : ; ; ; ;; ; ; ,
curr_str)
unsigned index;
int outcone, bin.place • 0;
value X- index;
}
<
return ( value ft the.nask)
}
?
0000 0000 0000 0000
"
:
? 266
256 0000 0001 0000 0000
:
value ft FF "
0000 0000 0000 0000:
7 1023
1023 0000 0011 1111 lUl
"
:
7 1001
1001 0000 0011 1110 1001
value ft FF
:
233 -
0000 0000 1110 1001 :
This program displays the bit pattern for the number you enter, and
displays the bit pattern of the number resulting when you mask this number
with the number OxFF, which has all 1 's in its rightmost byte and all O's in the
rest of the number. The bit patterns are displayed in groups of 4 bits, some-
times known as a nibble.
The only differences betwen this main( ) function and the previous one
are the calls to put _ nibble( ) instead of printf( ), and the activity involving
masked _ value. This program has two new functions to do the work: ma$k( )
Similarly, the value OxAF represents the bit pattern 0000 0000 1010 1111.
This bit pattern is much easier to determine from the hexadecimal representa-
tion than from its decimal value, 175.
The function makes it much easier for you to read a binary number than if all
would you mask oif just the rightmost byte? You can accomplish this by
substituting the following assignment for the one in the preceding program,
and changing OxFF to ~OxFF wherever you find it.
For a session with the same input as for the previous program, the new
version of the program would produce the following output:
?
0000 0000 0000 0000
:
? 266
256 0000 0001 0000 0000
"
:
? 1023
1023 0000 0011 1111 1111
:
? 1001
1001 0000 0011 1110 1001
:
How would you use masks to distinguish even from odd numbers?
Bit and Other Operators 431
pattern contains 1 wherever the corresponding bits of the left or the right
operand, or of both operands, are 1 . The only zero bits in the result of a bitwise
OR operation come in places where both operands have bits. For example,
1101010101010101 I
1010101010101010 would producelllllllllllllUl, since one
or the other operand has I's in each place in the result.
This addition differs from ordinary addition, however. First, there are no
carries from one bit position to another. Second, 1 + ==
1 1 in this addition.
The truth table for the bitwise OR operator provides the same results, as shown
here.
left
bit
432 Using QuickC
«deflne
Bit and Other Operators 433
"pure" powers of two, so that each of these constants has a different bit
pattern.
The regions have been defined in a way that will keep them from overlap-
ping; that is, no two regions have a 1 bit in the same place. This makes it
Once you've created new value for terrl and other territories, you can
the
ask such questions as whether a given region is in a particular territory. The
following expression would determine whether WEST was in terrl:
If the bit corresponding to WEST is not on in terr2, the expression on the right
side will be 0, since that expression could contain a number with a 1 bit only if
take the bitwise complement of the region. Thus, the mask to remove NORTH
would have the bit pattern, 1111 1111 1111 1110.
The bitwise AND operator can then be used to remove the region. The
434 Using QuickC
The XOR operator {) shares properties with the other bitwise opera-
bitwise
tors. The XOR (for exclusive OR) operator, fits somewhere between the
left and right operands, returning if the two bits have the same values, and
returning 1 if the two bits have different values. That is, the XOR operator
returns a 1 when either the left bit is 1 or the right bit is 1, but not both.
Whereas the bitwise OR operator includes the cases where both bits are 1 , the
XOR operator excludes the cases where both bits are 1. For example,
1101010101010101 1010101010101010 would produce 0111111111111111. This is
almost identical to the result for the OR operator, except that the leftmost bit in
this result is 0, because both operands had a 1 in this position.
The truth table for the bitwise XOR operator is shown in the following
illustration:
lalt
bit
436 Using QuickC
#deflne
Bit and Other Operators 437
*define
;
merits, which have the same low precedence as all the other assignment
statements. The XOR operator, on the other hand, has a precedence between
that of the bitwise AND operator and that of the bitwise OR operator. This is
reasonable considering the fact that this operator seems to share properties
with each of these other operators.
Let's look at one more example, to illustrate that you can toggle on
multiple bits at a time.
main
<
int 11 - 32787. 12 • 32767;
void toggle.val ( int * int)
,
11 •• 7fff; 12 "
7fff
11 odd - d555; 12 even • 2aaa
This program simply flips the even numbered bits in one number and the
odd numbered bits in a second number. Toggling is a very common type of
program change. For example, many printers have controls that simply toggle
between on and off Keeping various printer settings in distinct bits on a
variable can be a compact way of storing the information, just as changing
settings by making bitwise changes to the variables can be an efficient way of
controlling the printer.
; ; ; ; ;; ; ; ; ;
; ;
Shift Operators
C also provides operators for simply shifting a sequence of bits to the left or
right by a specified number of positions. When bits are shifted to the left, the
bits at the left end are "pushed off" the variable and discarded. Positions being
vacated (that is, postions to the right, in this case) are filled in with zeros as the
shifting process occurs.
The left shift operator («) lets you carry out such bit moves. The
following program illustrates the use of the left shift operator.
#include <stdlib.h>
•define NULL.CHAR AO'
fdefine MAX.STR 80
main ()
int value
int make_bin_8tr ( char », int);
int shifted.val, shift.amt;
char curr.str [ MAX.STR]
char val.str [ MAX.STR]; /* value to convert, read as string */
void put.nibble ( char *)
unsigned index;
int outcome, bin.place 0:
value % index;
>
7 100
Shift by? 5
100 0000 0000 0110 0100
«
:
? 18
Shift by? 5
16 0000 0000 0001 0000
«
:
? 2000
Shift by? 8
2000 0000 0111 1101 0000 j
«
:
'{
?
i
5
This program lets you specify the value you want to change and the i
number of places you want to shift the value to the left. The program then .:
The main( ) function has been changed somewhat from earlier versions of i'l
the program to present the left shift operator. In addition, the function
bQund_int( ) has been added to make sure the program doesn't try to shift by
more than the allowable amounts. This function brings any values entered for
shift_amt within the range through 15.
When the left shift operator is applied, the bits on the left simply disap- li|
pear as they are overwritten by the bits coming from the right. As the positions la
on the right are vacated, those bits are replaced by 0. The following version of
the program makes this even clearer. The middle part of main( ) has been
changed from the previous program -to loop through the left shift process,
one place at a time.
main ()
•
getB ( val.Btr)
8hif t.ant - atoi ( val.atr)
hif t.amt bound.int ( shllt.ant
shif ted.val «- 1
printf ( "\nvalue %d Xd " « " :
unsigned index;
int outcome, bin.place • 0;
else
printf ( "Xc", str [ index]);
>
For an input of 127 as the value and 6 as the number of places to shift, the
program produces the following output:
« 254 "
0000 0000 1111 1110
value «
1
2 608 • :
In this version of the program, you can literally see the bits marching across
the number.
^REMEMBER When the left shift operator («) is applied, the bit pattern
is always filled in with O's on the right.
There is also a right shift operator (») that moves bits towards the right
end of the number. Bits at the right end disappear as they are overwritten by
the new bits. The bits at the left end are not necessarilyfilled in with zeros,
however. The action taken at the left of a number to which the right shift
operator is being applied is implementation dependent.
Logical and Arithmetic Shifts There are two strategies that can be used
when filling in bits at the left end of a number to which the right shift operator
is being applied. A logical shift always fills in these bits with 0. An arithmetic
shift may fill them in with or 1, depending on the value and type of the
;; ; ; ; ; ;; ; ; ; ; :
Testing the Right Shift You can take advantage of QuickC's integrated
environment to experiment quickly and easily with the way the implementa-
tion handles right shifts. The following program currently defines the variable
shifted val as an unsigned int.
#include <8tdlib.h>
#define NULL.CHAR AO'
#define MAX.STR 80
main ()
{
do
printf ( "\nvalue Xd » Xd —
". :
unsigned index;
int outcome, bin_place » 0;
else
printf ( "He", str [ index]);
}
return ( lower)
else if ( val > upper)
return ( upper)
else
return ( val)
using the same values as before. Notice the difference in the bit patterns that
result.
^ CAUTION Be careful when using the right shift operator. If your imple-
mentation uses an arithmetic shift (as does QuickC), then the left bits may
sometimes fill with and sometimes with 0.
1
Try the following to see how QuickC handles signed and unsigned vari-
ables when right shifting:
shifted— val,x
shifted_int_val,x
index
5. Set a breakpoint at the first call to printf( ) after the right shifts in the
for loop.
6. Step through this program, comparing the values of shifted— val and
shifted_int val as the right shift proceeds.
Because the bitwise shift operators have a precedence just below the
arithmetic addition operators, and just above the relational operators, the shift
operators have a higher precedence than the other binary bitwise operators.
^include <Btdio.h>
#inelude <stdlib.h>
*deflne MAX.SHIFT 12
*define HAX.STK 80
•define NULL.CHAR •\0'
Dain
unsigned index;
int outcome, bin.place • 0;
int Index
This program produces the following bit shift values for a starting value of 5:
Value to shilt? 5
; : ; ; ; ; : ; . ;
nain
unsigned index;
int outcome, bin.place » 0;
value % index;
)
alternativel
alternative2
I I
z - (x < 0)
operator
main ()
int index;
41
; ; ;
main ()
{
int rand (void)
int array [ MAX.VALS] , val
int index:
41
Bit and Other Operators 455
Operator Comments
Summary
This chapter covered some miscellaneous operators. In particular, it examined
the bitwise operators, which can be very useful for situations involving collec-
tions of objects or for systems programs, which often have to manipulate
individual bits.
You have also learned about the conditional operator, which allows you to
select between two alternatives in a compact manner. This operator can be
So far, the data types you Ve seen have contained only one type of information.
Even arrays, which contain multiple elements, store only one type of informa-
tion. Sometimes, however, it's convenient to group different types of informa-
the items are components of a larger unit. In this chapter, you'll learn about
all
the structure and union data types, which allow you to group different types of
inforrnation together in one variable. You'll also learn about the bit fields
available in structures. Finally, you'll find out about enumerated data types,
which are available in many implementations, but won't become an official
part of C until the Draft Proposed ANSI Standard is adopted.
457
458 Using QuickC
Structures
struct weather {
double temp;
double wind;
};
Structure tag
i
i
structure \
'*"'"=* weather {
'*° """ ^""'P' Structure
declaration { I
double wind; / members
I
; ,
then, to define a variable of this type, you could enter the following:
struct weather {
double temp
double wind;
malnO
{
struct weather today;
number structure, complex, and, at the same time, defines two variables of this
type, compl and comp2:
main ()
{
struct complex {
double re
> compl, comp2;
/• code here */
This Structure has the tag name complex, and has two members, re and im.
Both members are of type double. Notice that you can allocate storage for
structure variables at the same time you create the structure itself In this
listing, only function main( would know about the structure of type complex.
)
The storage allocated for the two variables of this type would also be local to
main( ).
main ()
{
struct person sample [ MAX.PERSONS]
/* code here •/
The person structure declared here has four members — a char( first), a
string (last[]), an int (age), and a double (income).
The preceding listing also defines an array of type person which contains
—
MAX PERSONS elements of this structure. Each element in this array
refers to an entire structure. That is, each array element contains first initial (a
char variable) and name, age, and income information. Thus, sample[ 0]
last
refers to the first person in the array, and sample[ 9] would refer to the last
person. The following listing shows you how much storage each array element
takes up:
struct person {
char first, last [MAX.NAME];
int age
double income:
}:
main ()
{
struct person sample t MAX.PERSONS]
int index
If you compile and run this program, you'll find that a person structure
requires 36 bytes of storage — 1 byte for the person's initial (char), 25 bytes for
the last name (char []), 2 bytes for age (int), and 8 bytes for income (double).
Notice the use of the sizeof( ) operator with a structure as an argument here.
How would you determine the amount of storage allocated for the entire array,
sample[]?
In this example, the structure template is global, so any function in the
program could have variables of structure type person. The storage allocated
for sample[] is local, however.
; :
struct weather {
double temp;
double wind:
malnO
{
struct weather today:
assigned the value 10. 1 . Notice that no space has been left between the variable
name, operator, and member identifier. This is common programming prac-
tice, but is not required. Thus, the following assignment statement would also
be valid, although it is rarely seen:
struct person {
char first, last [ MAX.NAME]
Int age
double income;
};
main ()
int index;
char inlo [ MAX.STR] ; /* used to read info for conversio
This program prompts the user for values for each of the person elements
in the sainple[] array. Notice the use of gets( ), in conjunction with a conversion
function (atoi( ) or atof( ), in this case), to read the information. This is often
safer than using scanf( ), which is sensitive to entries of the wrong type. The
info[] string is used solely for the purpose of getting the strings for conversion,
so it can be used for each entry.
3. A member list
Tag Names The tag name specifies the identifier for the structure. Once it
has been created, you can think of the structure as being of type struct <tag
name>, although it will beeasiertospeaksimply of type<tagname>. In our
earlier examples, tag names included: person, complex, and weather.
You can refer to the structure as a unit by using its tag. For this reason,
you can assign an entire structure to another, provided the structures have the
same members. Thus, the following assignments are all valid:
struct card {
char suit
int val;
};
nain ()
{
struct card hand [ 5]
hand [ 0] val « .
hand [ 1] . suit
hand [ 1] .val =
hand [ 2] . suit
hand [ 2] . val •
hand [ 4] . suit
hand [ 4] . val
The preceding listing shows examples of nested structures, that is, struc-
tures declared within other structures. Notice how the naming process works.
When you want to access a member of a structure, use the structure
member operator. For example, you would use this operator when you access
pers.first, pers.last, and so on. You would use the same operator to access the
learning member: pers.learning.
Once you've accessed you can access any of its members,
pers.learning,
since pers.learning is a structure. To access its members, use the dot operator.
For example, pers.learning.id and pers.Iearning.yr_grad are valid members.
Again, you can access school in the same way: pers.learning.school.
Once you've accessed that, you can access its members as follows:
pers.leaming.school.major, pers.leaming.school.degree, and pers.leaming.school.gpa.
The access rules for nested structures can get long-winded, but they are
straightforward.
struct card {
char suit;
int val :
>:
main (}
<
struct card hand [ 6]
hand [ 0]
hand [ 3] suit
.
, ; ; ; .
, .
hand [ 3] .val - 3;
"
. .
This program displays the values and addresses of five cards. Notice that
the locations of a structure's members are consecutive. The C compiler guar-
antees this. Similarly, the compiler guarantees that successive array elements,
which are structures in this case, have consecutive addresses.
^include <stdlo.h>
include <Btdlib.h>
#define MAX.STR 80
fdeflne MAX NAME 26
«deflne MAX.EDUC 15
struct education {
char major [ MAX.EDUC]
char degree [ MAX.EDUC]
double gpa;
};
struct student {
struct education school:
char id [ MAX.EDUC]
int yr.grad
};
int age
double income:
struct student learning:
};
main ()
Major? Cfl
Degree? bs
GPA? 3.85
cs, bs, 3.86
Student ID? 1234667890
Graduated what year? 1964
1234667890. 1964 t
Notice the function prototypes. You must include the keyword, struct, in the
#include <stdlo.h>
#include <stdlib.h>
•include <math.h> /* for sqrtO •/
define MAX.STR 80
struct weather {
double temp, wind;
>;
struct chill i
struct weather weath;
double windchill:
};
/• Compute the windchill factor for the given temp and wind values */
struct chill get chill ( struct weather today)
<
double numl nuiii2
,
main ()
Temperature? -10
Wind Speed? 10
Temp —
-10.00, wind •- 10.00, windchill - -33.62
Temperature? -10
Wind Speed? 20
Temp ~
-10.00, wind 20.00, windchill
Temperature?
Wind Speed? 10
Temp " 0.00, wind - 10.00, windchill -- -21.20
Temperature?
Wind Speed? 20
Temp " 0.00, wind - 20.00, windchill - -38.90
Temperature? 10
Wind Speed? 10
Temp —10.00, wind -- 10.00, windchill --
Temperature? 10
Wind Speed? 30
Temp -10.00. wind - 20.00, windchill
; ; ; ; ;, ; ; ;
Pointers to Structures
As you learned earlier, structures are ordinary variables, not pointers. If you
pass a structure as an argument, you are passing by value, not by reference.
The contents of the structure are not changed by the function called, because
only a copy of the structure is passed.
To change a structure directly within another function, you need to pass
the address of the structure, and declare a parameter that is a pointer to the
structure. The following program shows this:
Include <Btdio.h>
^Include <stdllb.h>
•include <niBth.h> /• for sqrtO */
#define MAX.STR 80
struct weather {
double temp, wind:
};
struct chill {
struct weather weath;
double windchill
};
/» Compute the windchill factor for the given temp and wind values */
struct chill get.chill ( struct weather today)
<
double nuffll num2
,
main
{
struct weather todays.weather;
struct chill todays.chill;
char info [ MAX.STR]
struct chill get.chill ( struct weather)
void get.temp ( struct weather *)
void get_wlnd ( struct weather •)
todays.chill.windchill)
Notice the use of the address operator when passing the structure. This
ensures that the function will work directly in the structure's storage.
The syntax for specifying a pointer to a structure is straightforward. You
simply use the indirection indicator to specify that the variable is a pointer
rather than a structure. Notice also the declarations in the function prototypes
in niain( and the parameter declarations in get_temp( ) and get_wind( ).
)
The arrow operator, — >, takes two operands, and returns the value of the
structure member accessed or this member as an lvalue, depending on the
context in which the operator is used. The right operand is a member of the
appropriate type of structure. This operand is thus the same as for the dot
operator. The left operand is the structure pointer that references the structure
whose member you want to access. The following version of get_temp( ) uses
the arrow operator. This version is equivalent to the function used in the
preceding program. The only difference is in the operators used in the last
lines.
The left operand here is a pointer, namely, the node referencing the structure
whose member you want to access. For the dot operator, the left operand is a
target structure, rather than a pointer.
Most C programs use the arrow operator when pointers to structures are
involved. The precedence of the arrow operator is the same as that of the dot
operator, the highest precedence of any operator in C.
#lnclude <stdio.h>
#lnclude <stdllb.h>
#dellne MAX.EDUC 15
#dBline MAX.ELEM B
define MAX.STR 80
struct ed <
char major [ MAX.EDUC]
double gpa;
> struct.arrayl [ MAX.ELEM]
fflainO
/* sort structures */
bbl.sort ( struct.arrayl . MAX.ELEM)
Major? astron
GPA? 3.5
Major? bio
GPA? 2.7
Major? chem
GPA? 3.7
Major? math
GPA? 3.65
Major? psych
GPA? 3.4
0: astron major, gpa -- 3.50
1: bio major, gpa 2.70 "
2: chem major, gpa -- 3.70
3: math major, gpa 3.65 •
4: psych major, gpa •• 3.40
This program is supposed to sort structures based on the values of the gpa
member in each of the structures. Unfortunately, the result is not what you
want; the sorting function has simply exchanged the gpa members in the
structures it compared.
To sort properly, it is easier to use an array of pointers to structures. The
: ; ; ; : : . ; ; : :
sorting routine would then exchange pointers and actually change the order in
which the structures are referenced. The following program accomplishes this,
struct ed <
char major [ MAX.EDUC]
double gpa:
} Btruct_array [ MAX.ELEM] /* array ot structures */
,
mainO
void get.struct ( struct ed •)
void bbl.sort.ptr ( struct ed *[] . int)
void show. struct ( struct ed)
int index
This program produces the following, for a session with the same responses as
in the previous example:
Major? astron
GPA? 3.6
Major? bio
GPA? 2.7
Major? chem
GPA? 3.7
Major? math
GPA? 3.86
Major? psych
GPA? 3.4
0: astron major, gpa 3.60 •
1: bio major, gpa 2.70
2: chem major, gpa 3.70 •
3: math major, gpa 3.66 •
4; psych major, gpa •• 3.40
;
This program sorts the structures properly. The entire structure is moved by
making a different pointer reference the structure.
Notice that the arrow operator is only used for the comparison in the test
condition, in function bbl_sort ptr( ). After the comparison has been made,
addresses, not structure members, are transferred. On the other hand, when
showing the array, you need to use the indirection operator to get the actual
structure, because the array element is a pointer. Inside the show_struct( )
function, the arrow operator is not needed, because the entire structure, rather
than a pointer to the structure, has been passed.
In this program, the information was first read into a static array of
structures. Then the locations of these structures were assigned to the structure
pointers in ptr_array[] in order to allocate storage for the structures. In the
next section, you'll learn how to allocate storage dynamically for structures.
When using multiple structures in a program, think carefully about what
you want to do with these structures. The answer to this question may
determine whether it makes more sense to work with an array of structures or
with an array of pointers to structures.
Self-Referential Structures
Often, a program will use multiple elements of the same type, where the
number of elements needed is determined at run time. A dynamic storage
allocation strategy is advisable here. Structures are essential in such situations.
Look at the following template:
This structure has two members. The data member contains the actual
information to be stored in the node. The next member is a pointer to another
Inode. Such nodes can be used to make arbitrarily long chains of structures,
linked together by the next members of each Inode.
Such chains of structures are called linked lists. The first Inode in a linked
list is often referenced by a pointer to Inode — that is, an ordinary pointer, not
a structure member member
This pointer will reference an Inode, whose next
may, in turn, reference another Inode. The list may contain as many Inode
elements as your implementation can handle. The next member of the last
Inode in the list should be a null pointer.
The following program includes functions for adding an Inode to a linked
list. You can add the element to the front or back of the list, or at the position
*include <atdio.h>
«include <Btdllb.h>
#include <nalloc .h>
/• add the structure pointed at by new to the front of the list pointed
at by list.
•/
struct Inode • front.of.list ( struct Inode *new, struct Inode *list)
{
new->next • list;
list new;
return (list)
}
/* add Inode to which new points to end of list to which list points. •/
struct inode tback.of.llst ( struct Inode *new, struct Inode (list)
{
If ( list " NULL)
{
list • new;
return ( list)
}
else
<
/* return results of most recent search. */
li8t->next " back_of_list ( new, li8t->next)
return ( list)
if ( list ~ BULL)
{
list • new;
return ( list);
>
else if ( llst->data > new->data)
{
new->next " list;
list new;
return ( list)
}
else
<
/• return results of most recent search. •/
list->next mlddle.of.list ( new, list->next)
return ( list)
main ()
{
struct Inode *front_of.list ( struct Inode , struct Inode *)
struct Inode 'back.of _list ( struct Inode ». struct Inode *);
struct Inode «middle_of .list ( struct Inode *, struct Inode *)
; ;; ; ; ; ;
: ; ;
if ( root I- NULL)
show_list ( root)
/* return true if val differs from zero by more than a predefined amount. */
int non.zero ( double val)
This program produced the following three sessions, when instructed to add to
the front, middle, and back of the list, respectively:
The program lets you build lists using three different insertion methods.
The following illustration shows the sequence of steps for adding to the front
of the list. The names in parentheses are the names that correspond to root and
temp, respectively, in the front— of _list( ) function.
The basic strategy in front— of _list( ) is to make the next member of the
new structure point to the beginning of the list — that is, to the same place as
Once this is accomplished, the contents of list are modified to point to the
list.
same Inode as new. Thus, the list is built as follows: 2, then 7, 2, then 3, 7, 2,
then 8, 3, 7, 2. The process is illustrated here:
root — HULL
temp - H 2| -I
— NULL
tWNULL
(llstrvNULL
(new)-
Structures, Unions, and Other Types 483
/ llBt new;
new and list both point to the same Inode
•/
(llstV^^Q* N^t-
(new)
/• back to malnO */
root H 2[ -K NULL
temp
temp -H 7| + - NULL
(listJ-TanV- NULL
/* new->next list;
make new node's next member point to the same Inode as list
•/
(new MTH
/• list new;
make list point to same Inode as new
*/
(new)^
/• back to main */
root—i-{2Q— I 2| -K NULL
temp /
root—^tQ— I 2| —
-t NULL
temp —H 31 +- NULL
484 Using QuickC
/* etc. */
/* new->next « list
/* list - new */
(iist)*ciE3^
(new)
"^
m - nn- null
root
temp —-
-H3| +*
I 81 -f —
["Til
NULL
- m - HULL
(ii8t)H"3ri->
(new) -fsln
m ->
nph null
/* new->next - list */
(list)*[2]3*
(new)— rsTl
m - CO- NULL
/* list - new */
(listKJCB-
/*
[13* QQ- ran- NULL
(new)
root — NULL
temp -H 2| -( - NULL
(list)— NULL
(new) H 2l + - NULL
(list)-Q]3-- NULL
(new)
/• back to main •/
root -H 21 + NULL
temp -H 71 -K NULL
(liBt)-^5C3- NULL
(list)— NULL
(new)— [7Q- NULL
/• list new;
make list point to sane node as new, since list is empty
(new)''^
(listKlHQ- I 7| -t - NULL
(new) /
/* back to main */
root -H 2| -I— I
7| -K NULL
temp
/
root—r2r^ I 7l -K NULL
temp —H 31 +- NULL
(listj-TzTl - I 7| -K NULL
(new) -H 31 +- NULL
/* new->ne3ct list:
make new structure's next member point to same Inode as list.
*/
(new)—13^
;;
/* list - new;
Insert the new structure before the start of the shortened list,
since 3 < 7
•/
(new)'^
(new)' y IIII3-"
I* back to main */
root -> 2|
) +— riTT— FtPI— null
teap
root —H 2| +~ I 3: -I
— I 7| -1 — HULL
tenp — I 81 —
-I - NULL
(new) - I 81 +- NULL
(new) — I 81 -t
— NULL
(new) H 81 4- NULL
^
;;
(list)— NULL
(new)-— ran— NULL
/* list - new;
make list point to same node as new, since list is empty
*/
/* return ( list)
returning to the calling function, and to the longer list
*/
/* return ( list)
returning to the calling function, and to the longer list
•/
(list)—TaPl — I 7| -I
— I 8| -K NULL
(new) ^
/* return ( list)
returning to the calling function, and to the longer list
»/
(list H 2 I ^1 3l 4 — 1 7 I +— I 8 -I NULL
(new) ^
/* back to mainO */
root — ^ 2| —
-|
- 1 3|-| H 7 |-[ — FbTI
-
NULL
temp '
If the end of the list has been reached, makelist point to the same Inode as
new, and return this value. Otherwise, the function will need to do some more
processing. If the function is not at the end of the list, then list— >data exists
(since list is pointing to an Inode). If the data member of list is greater than the
data member of new, the new structure should be inserted just before the Inode
to which list is pointing. Once accomplished, the value of list is changed to
point to the Inode referenced by new. The new address stored in list is returned
to the calling function.
;
If list is pointing to something, but the data member of this Inode is less
than the new— >data, then the function must keep searching for the proper
place to insert the Inode to which new points. The function accompUshes this
by calling itself with a shortened version of the list — namely, one starting with
the Inode referenced by list— >next. The idea is that, if list — >data is still too
small, the data member of the next Inode in the list may be larger.
When the recursive call returns with the modified list referenced by
list— >next, the new structure has been inserted at the appropriate point. The
current version of the function can then return its value, list, to the calling
function. Once that's done, the various versions of the function will return the
appropriate values to their calling functions.
There are several important points to notice about the program under
discussion. First, each inode is allocated as it's needed. The get_node()
function is responsible for getting this storage, initializing the structure, if
situation.
#lnclude <stdlo.h>
•include <stdlib.h>
•include <malloc.h>
•define MAX.STR 80
•define FALSE
•define TRUE 1
•define TOLERANCE le-7
^
; ; ; ; ;; ; ; ; : ; ;
/* add the structure pointed at by new to the front of the list pointed
at by list.
•/
struct Inode * front_of list ( struct Inode *new. struct Inode *list)
{
printf ( "FRONT: ftlist == y,u; list == y,u; ", tlist, list);
printf ( "tnew == */,u: new == 5tu\n", Imew, new);
new->next = list;
list = new;
printf ( "front: ftlist == y,u; list *- y,u; ", frlist. list);
printf ( "toiew == %u; new == y,u\n", ftnew, new);
return (list)
}
>
main ()
>
else
done TRUE;
get_node (temp)
it ( root I- NULL)
8how_ll8t ( root)
/* return true il val dillers from zero by more than a predefined amount. •/
int non.zero ( double val)
When run with the same data as in our previous example, this program
produces the following.
Data? 2
MAIN: troot - 4972; root -• 0; ktemp 4074; temp "
5182 —
FRONT: kllst - 4882; list -- 0; tnew -- 4880; new •- 6182
front: tllst - 4882; list "
5182; tnew 4880; new 6182 " "
main: troot 4972; root -- 6182; ktemp -- 4974; temp -- 5182
Data? 7
MAIN: kroot - 4972; root -- 6182; frtemp -- 4974; temp •5194
FRONT: felist - 4882; list 5182; tnew — •• 4880; new -- 5194
front: tlist • 4882; list 5194; tnew — -- 4880; new "5194
main: ftroot 4972; root -- 5104; ttemp -- 4974; temp -- 5194
Data? 3
MAIN; kroot - 4972; root -- 6104; ktemp 4074; temp -- 5206 -
FRONT: tlist - 4882; list -- 6104; toiew -- 4880; new -- 5206
front: tlist - 4882; list -• 5206; tnew -- 4880; new 6206 -
main: troot 4072; root -- 6206; ttemp -- 4974; temp 5206 -
Data?
8.00
3.00
7.00
2.00
Let's see how this program does its work. The goal of each call to
492 Using QuickC
front _ of _list( add a new Inode to the front of the list. This means that
) is to
root will point at a new Inode after each call to front _ of _list(). More
specifically, this means that the contents of storage location 4972 (the location
front_of_list( ).
Let's look at the output within front. of _list( ) to see whether this can
happen. First, notice the address and contents of list, the parameter corres-
ponding to root in the function. This variable has the same contents as root,
but this address is stored at a different location, because all that has been
passed to the function is a copy of the address stored in root — that is, stored at
location 4972. The address of root itself (4972) has not been passed.
Because the value passed is an address, you can make changes in the
variable located at that address. For example, after making list point to the
same place as new, you could make changes in *list, the variable at which list is
pointing. Because *list is an alias for *root, these changes would remain after
you left the function.
However, you cannot change the contents of root itself, even though you
can change the address stored in list. This is because root and list are not
aliases.By returning the desired address from the function, and assigning this
address directly to root in the calling function, you make root point to a new
Inode after the function call. (You could also have accomplished this by passing
a pointer to a pointer to structure, struct Inode **, but that's another topic
altogether.)
Bit-Fields
settings or options. You saw examples ofthis in Chapter 15, when you assigned
regions to territories in some programs, and learned to define manifest con-
stants to represent mutually exclusive regions. These constants were all pow-
ers of two, so that exactly one bit was one in any constant.
Bit-fields are particularly useful in programs that need to keep track of settings
or options that are in effect — such as driver programs (for controlling things
like printers or screens) and operating systems.
Suppose you have a program that needs to keep track of the settings on a
printer. In particular, let's assume the program needs to maintain information
You could define each of these as a separate variable. In that case, each would
take up at least one byte, and more likely two bytes because it would be more
convenient to represent most of them as integers rather than characters. That
means you might use as many as 16 bytes to store this information.
Bit-fields let you represent all of these variables in two bytes. The follow-
ing program shows you how.
unsigned bold 1 :
unsigned italic 1 :
unsigned size 2: :
unsigned single.space : 1;
unsigned emulation 4; :
unsigned quality 3; :
unsigned paper 3; :
>;
^
;; , ;;
main ()
laser. quality 0;
laser. paper • 0;
(unsigned, in this case), an identifier for the member, and the number of bits to
be allocated for the member.
When you declare bit-fields, the compiler stores the members in adjacent
bits, subject to some constraints. That is, the compiler does not start each
; :
and the next bit for italic. Then two bits were allocated for size. At this point,
only one byte of storage had been used.
The next byte was and the four after that for
allocated for single_space.
emulation. Notice that four bits were allocated for this member, since ten
values cannot be stored in three bits. This means that several possible values in
this bit-field will be unused. The two bits following were allocated for quality.
paper completely in the next word. Check your compiler to see how bit-fields
are handled when they cross word boundaries.
You can also use padding to control how the compiler handles fields that
cross word boundaries. You can pad the storage allocation in a structure by
declaring a nameless bit-field. If you specify just a type and the number of bits,
the compiler will skip the specified number of bits. The special width, 0, tells
the compiler to pad to the nextboundary appropriate to the variable type. For
example, the following version of the printer declaration would assure that
paper did not straddle a word boundary, regardless of how the compiler would
handle this situation by default:
unsigned bold 1 :
unsigned italic 1; :
unsigned single.space : 1
unsigned emulation 4; :
unsigned quality 2; :
The Draft Proposed ANSI Standard says that bit-fields can be declared
as having int, unsigned int, or signed int types. If you declare a bit-field as
being signed, the action taken if you assign an out-of-range value is implemen-
tation dependent. In QuickC and Microsoft C 5.0, the value is treated as if it
496 Using QuickC
B;
:
Int not_a_field;
};
The two nameless members each pad the structure to the next long
address boundary. The first one pads 26 bits (32 bits allocated for long, less the
6 bits for one). The second one pads 27 bits. The compiler pads to the end of
the third long, ensuring that structure members that are not bit-fields will be
aligned at the proper address boundaries. You can have both bit-field and
other members in the same structure.
Restrictions on Bit-Fields
While bit-fields can be convenient for certain types of tasks, there are several
restrictionsand implementation dependencies associated with them. Because
of these restrictions, you should be very careful about making assumptions
concerning the particular locations at which you will find specific fields.
Some implementations allocate storage to bit-fields from left to right
(high-order to low-order bit), while others allocate them in the reverse direc-
tion. This can be a crucial difference if you're reading data generated by a
program.
Bit-field locations are not accessible, so you can't apply the address
operator, &, to a bit-field. This means that you can't have arrays of bit-fields
(since arrays are actually pointers) or pointers to bit-fields. Nor can functions
return bit-fields.
: ;
Unions
Unions are aggregate types that have members, Hke structures. Unhke struc-
tures, however, unions may hold different types of information at different
times. That is, the same variable may be used to store different members at
different times. For example, the union variable, number, defined below may
contain an int at some and a complex number
times, a double at other times,
on other occasions. At any one time, however, only one of these types is stored
in the union variable.
struct complex {
double re, Im:
};
union value {
int int.val;
double dbl.Tal;
struct complex comp.val;
> number
This data structure can store the value for either an int, or a double, or a
struct complex variable at any one time. The union cannot have more than one
member present at any given time. Rather, the union has a certain amount of
space allocated, and this space can contain information that may be inter-
«include <8tdio.h>
*include <8tdlib.h>
main ()
<
union value number;
y.d\n".
sizeof ( number. comp val))-
printf ( "Size of complex
re"-- y,d\n».
sizeof ( number. comp_ val re))-
printf ( "Size of complex
im -• y.d\n"."
sizeof ( number. comp. val. im)),'
tnumber == 4256
tnumber.int.val == 4256
tnumber.dbl.val =- 4256
tnumber. comp. val == 4256
tammber.comp.val.re »» 4256
tnumber.comp.val.im == 4264
In this case, we have three different types declared, with one of these being
a structure. In a structure, these three different types would coexist. In a union,
however, only one can be represented The compiler allocates enough
at a time.
storage for the largest union member declared. In our example, this means 16
bytes, since the complex is the largest union member. The program displays
this value as the size of the structure. If you're storing an int, the compiler
would use only two of the bytes.
The storage allocated for the union will be interpreted differently, depend-
ing on the union member being accessed. Storage for all members will start at
the same location, as the program shows. This means that the union members
overwrite each other. You are responsible for making sure your program asks
for the kind of information that was last stored there. If you overlook this,
Unions of Structures
A very common use of unions is to store either of several structures, depending
on the context. For example, the following declarations would let you record
information about either a book or a journal article:
•include <Btdio.h>
«deflne HAX.SMALL 40
struct book {
char author [ HAX.SMALL]
char title [ HAX.SMALL]
}:
struct article {
char author [ HAX.SHALL]
char title [ HAX.SMALL]
char journal [ HAX.SMALL];
};
union entry {
struct book bk;
struct article art;
};
With these declarations, you could build entries for a data base, where
some entries would be for books and some for articles. In the next chapter,
you'll learn how to use some unions declared in QuickC and Microsoft C 5.0 to
Enumeration Types
C you provide names for values that
lets
certain variables can take on For
example, suppose you wanted to
create a template for a deck of cards
It would
be convenient to have meaningful
names for the mdiv.dual cards. C's enumer-
ation type gives you this
capability. The enumeration
type will officially
become part of the C language when the Draft Proposed
ANSI Standard is
adopted. This type is already available in many
implementations, so you'll
probably run across programs that
use it.
struct card {
enum suits suit;
enum face value-
} deck [ MAX.CARDS]
^" '""•" '^P'' y° " ^'' specifying names for the range of
vph-e^'"
values T, 'Tu
a variable of that type can have.
Thus, in the example, variables of enum
type suits can take on the named
values clubs, diamonds, hearts, or
spades
Similarly, variables of enum
type faces can take on any named
values in the list
between ace and kmg, inclusive.
Enumerations are really nothing more than
names for integer constants that represent
specified values. They are useful for
specifying actions in a more
memorable way, however
When you declare an enumeration type,
you specify a set of names to use
when referring to particular values such
variables could take on. Internally the
compiler numbers elements in order,
starting from 0. Thus, the values
ciubs
through spades would correspond
to the numerical values through 3- the
values ace through king would
correspond to through 12. You worry
about
the names, the compiler will
take care of the values.
You can use these enumeration values
in expressions wherever an int
value IS allowed. When the compiler sees such a value
in your program it
substitutes the value it has associated with the name into '
the expression
Although, by default, the compiler
starts numbering enum values from
you can override the default by
simply associating a new number with
an
; ;: ; ;; ; ;
*define HAX.CARDS 62
•define MAX.SUIT 4
•define MAX.FACE 13
enum faces { ace - 1, two, three, four, five, six, seven, eight, nine,
ten, jack, queen, king};
struct card {
enun suits suit
enum faces value;
} deck [ HAX.CARDS]
main ()
{
int rand ( void)
enum suits suit_val, face.val;
break
default
printf ( "W " . faee.val) ;
>
Value Names
b. h, u
Summary
Congratulations, you've done it! You've mastered all the
fundamental topics in
the C
language. This chapter has covered
the last major topics -
the structure
and union aggregate types and the "new"
enumerated type
structures, Unions, and Other Types 503
This chapter provides more tools and information for your C programming. In
it, you'll find brief discussions of some of the more commonly used Run-Time
library functions. You'll also find useful functions for doing things such as
getting the time and date, determining elapsed time, and using DOS com-
mands in your programs. Although it presents a great deal of information, this
chapter should be enjoyable and helpful — especially if you take time to play
with the programs.
The discussions you whether a given function is built into QuickC. If
tell
it is, you can use the function anywhere in your program without declaring the
function or including any header files. If a function is not built into QuickC,
you may simply need to include the header file in which the function is
declared. Or, the function may be included in the Quick library, qcstuff.qlb,
505
^
506 Using QuickC
qc /Iqcstuff myprog
stdio.h
int fclose (FILE *f_ptr) This function closes the specified file. Before
closing, the buffers associated with the file are flushed and written to the file. If
the file is successfully closed, fclose( ) returns 0. The function returns EOF if
there is an error.
f eof (FILE This macro determines whether the end of the file has
*f _ptr)
been reached. The macro returns a if the end-of-file has not been reached,
and a nonzero value if the end-of-file has been reached.
read from a file that has been opened for writing. The macro returns a if «o
error has occurred, and a nonzero value if an error has occurred. The error
indicator remains set until you use clearerr() (discussed later) to reset the
indicator, close the file, or move back to the beginning of the file.
char *fgets (char *str, Int size, FILE *f_ptr) This function reads a string
from the specified file, The string's maximum length is
and returns this string.
specified by size. Newlines are included in the string read, and the function
terminates the string with a null character. The function returns the string read
if successful, or a null pointer if unsuccessful.
FILE *fopen (char *name, char *mode) This function opens a file and
returns a pointer to the stream associated with this The function also
file.
associates a disk file, called name, with the stream. The mode parameter
specifies how the file is to be used. The function returns a file pointer if
int tprintf (FILE M—ptr, const char *str, . . .) This function works just like
printf( ) except that fprintf( ) writes to the file specified by f_ptr. The function
has the same string argument as printf( ), and can contain optional arguments
of types specified in the string argument. In addition, fprintf() takes a file
pointer as its first argument and returns the number of characters printed.
508 Using QuickC
int fscanf (FILE *f_ptr, const char*str, .) This function reads from the
. .
char *gets (char *str) This function reads a string from stdin and
returns
this string in a modified form. The
newline character read is replaced by a null
character, which terminates the string
returned. If the function is unsuccessful
at reading a string, it returns
a null pointer.
int puts (const char This function writes a string to stdout, replacing
*str)
the null character that terminates str with a newline character. The function
returns the last character written (usually the newline character), or EOF if
there is an error.
int scant (const ctiar *str, . . .) This function reads the types of informa-
tion indicated in the string argument. Itmust include additional arguments
corresponding to any placeholders in the string arguments. These additional
arguments must be addresses so that the information can be assigned directly
to these locations. The function returns either the number of items that were
read and assigned, or EOF if the end-of-file has been reached. A return value
of indicates that an error has occurred, since no fields have been assigned.
New Functions
The following functions have not appeared in the programs so far, but you may
want to use them in your programs.
This function takes a file reference as its argument and simply changes the
file's error indicator
The following call resets the error indicator for the file referenced by
f_ptr.
clearerr ( l_ptr)
; )
int fcloseall (void) This function closes any open streams, or files,
except
those opened by the system-that is, except
stdin, stderr, stdout, and so on
The function returns the number of files closed
successfully, or EOF if there is
an error. The following listing shows how to call fcloseall( ).
This function call effectively accomplishes two things, it opens a file and
It redirects the output of a stream to
this file. The variable new_f_ptr
references the newly opened file, which is associated with the disk file error.tex
The effect of the w+ option is to create a new file that can be read from and
written to. Note that freopen( not buih into QuickC.
) is To access the function
you need to include the stdio.h file at the top of
your program. If you intend to
compile and run your program within the
QuickC environment, you need to
add freopenO to a Quick library, as described in
Appendix B.
void perror (const char .str) This function writes a specified error mes-
sage to stderr. You pass the message to the
function, as a string argument In
addition, perror( ) prints a system error
message appropriate for the nature of
the error encountered.
For example, you want
if to delete a file using a C function, but no file
with the specified name is found, the following line at the appropriate poim
in
your program will write the message below it to stderr.
The perror( ) functionoften used after errors are detected using the
is
ferror(
or feofO macros. Call perror()
immediately after the error has occurred.
;
Otherwise, subsequent errors will overwrite the error you wanted to report.
Note that perror() is not built into QuickC.
The "Deletion unsuccessful" message (passed as argument to perror( ) ) is
stored in the appropriate header file. The "No such file or directory" message is
an internal one associated with the error type encountered when attempting to
delete the file.
intremove (const char *name) This function removes the disk file speci-
successful, the function returns - 1 . The function does not give you a chance to
change your mind. So before calling this function make sure your program is
remove ( "zzzzgone.bye");
void rewind (FILE *f_ptr) Every file has a pointer associated with it. This
pointer indicates the current position in the file. Generally, the next action will
occur at this position. Thus, if the file position pointer is at the second element
in the file, the next read process would read this element into the program. The
file position pointer would then move to the third element. If your program
writes to the file at this point, it and the file
will overwrite the third element,
position pointer will move to the fourth element. (These actions assume that
your program has been opened in a mode that allows the actions, and that the
specified elements exist.)
The rewind( ) function moves the file position pointer to the beginning of
the file. The function also clears any end-of-file or error indicators. It does not
return a value. The following would move the file position pointer to the
beginning of the file referenced by f — fptr:
rewind ( l_ptr)
A Program to Exercise
the I/O Functions
The following program shows how these functions work. The program, which
basically reads and writes files, uses each of the functions you've just read
about.
; ; ;
; ; ; ; : ; ; ; ; ;
The program first redirects the stderr stream to the file referenced by
err_ptr, which is written to disk as zzzztest.log. (In the unhkely event that you
have such a file, please change the name of your file or the file name in the
include <stdio.h>
include <stdlib.h>
define MAX.STR 80
define MAX.ENTRY 50
mainO
void 6how_error_file ( FILE *)
void get.src.file ( FILE •)
void build.new.f lie ( FILE *)
void test_new_f ile ( FILE *)
if ( src != NULL)
{
/* read and display contents of src */
get_src_file ( src);
fclose ( src)
if ( f ptr)
{
/* generate a new source file */
build.new.f ile ( f _ptr)
fclose ( f _ptr)
} /* END if successfully created new file */
else
perror ( "Could not create a new ztestsrc");
} /* END if could HOT open source */
\
/* read each line of source file as a string.
*/
—
get a number from the string
and display the number.
which discards 4 numbers on the line
<
char *info, line [ MAX.STR];
int value
This program will run in either of two different ways, depending on the files
that exist when the program executes. If the program is run when you do not
have a file named zzzztest.src in your current directory, it will create such a file
and produce the following output:
files closed
This creates a source file for next time. The complete contents of the
zzzztest.src file that has been created are shown here.
5705
QuickC Library Functions 515
The following listing shows the output from this version of the program;
41
15724
5705
491
32391
12382
5447
19912
28703
4664
Removing current version of source file
files closed
This version displays ten numbers, then writes various items, including
the contents of the error file. Notice that the program only displays ten
numbers, even though the file contains fifty. This is because of the way atoi( )
works.
The program reads the entire line (with five values) as a string. Note that
atoi( ) processes from the beginning of the string until it has built a number. In
our example, the number buih will be the first number on the line. Once a
number has been returned, the rest of the string is discarded, and the program
reads the next line of the file. When the string is discarded, the remaining four
numbers on the line are also discarded. (To get around this, you could use the
remove_wd() function defined in Chapter 12.)
stdlib.h
This header file contains declarations for several commonly used functions,
including the conversion functions such as atoi( ), the rand( ) function, and the
dynamic storage allocation functions, such as malloc( ). (The allocation func-
tions are also defined in the header file malice. h.) In addition, stdlib.h con-
tains the declaration for errno, a global variable whose value is set by various
functions to a number representing a specific error.
516 Using QuickC
Familiar Functions
int abs (int val) This function returns the absolute value of an int, val. If
val is negative, the function returns —val, otherwise the function simply
returns val. The abs( ) function is not built into QuickC.
long atol (const char *str) These functions convert the string, str, to a
number of the appropriate type, and return this number. The string is assumed
to contain characters that could be read as numerical input. The function
builds a number until it encounters a character it does not recognize as
belonging to the desired type of number. The functions return the value built,
void exit (int outcome) This function ends the program, closing any open
files The outcome value indicates the conditions under
before terminating.
which the program ended. Usually, an outcome of indicates a normal
termination, and a nonzero outcome indicates an error condition on termina-
tion. This outcome information is accessible to other programs, and also to
DOS batch files, as you saw in Chapter 12.
void free (void *buff) This function deallocates the specified area of
memory. This area must have been allocated using calloc( ) or malloc( ). Note
that buff points to the beginning of the storage that will be released.
int rand (void) This function returns a pseudorandom integer within the
range of values allowed by an int, to 32767 in QuickC and Microsoft C 5.0.
void *real!oc (void *buff, size_t size) This function lets you change the
size of a block of memory previously allocated with calloc() or malloc( ). The
contents of buff are unchanged up to the original size of buff. The function
returns a pointer to the new location of buff if buff was moved, or to the
current location if the additional storage was available right after the end of
buff. The function returns a null pointer if the additional storage cannot be
allocated.
New Functions
The following functions are also declared in stdlib.h, and may be useful for
your programming tasks.
div_t div (int numerator, int denominator) This function returns a struc-
ture containing the results of using integer division to divide numerator by
denominator. Two items of information are returned, the whole number
quotient and whole number remainder. The returned type, div t is a typedef
that specifies a structure containing two int members, quot and rem, for
storing the quotient and remainder, respectively. Look at the stdlib.h file to see
the exact definition.
Essentially, div( ) returns the results of applying both integer division and
the modulus operator to numerator. Be aware that div( ) is not predefined in
QuickC. The following statement would assign the structure returned by div(
to thediv_t variable, outcome.
char *getenv (const char *str) This function reads environment informa-
tion stored by the operating system. You can set various environment variables
; ;; :
when using your computer. In DOS, these include path, lib, include, and
others.
Calling getenv() with an environment variable name tells the
function to
check the system's environment table to determine
the current value of the
specified environment. The function returns
a pointer to the string associated
with the environment, or a null pointer if the
environment variable is not
defined at the time or if the variable name is not
one the system recognizes. The
following call would assign the contents of the
include environment string to
whats_included.
#include <8tdlib.h>
#define MAX.STR 80
mainO
{
char *result. temp [ MAX.STR]
result itoa ( -2300, temp, 2);
-
printf ( "base 2 '/.sXn" :result) ,
base 2 : 1111011100000100
base 8 : 173404
base 10 : -2300
base 16 : f704
Notice that only the base ten representation still has a minus sign in the
resulting string. For other bases, the number is handled as if it were unsigned,
and the bit pattern for the number is merely converted. You can see this if you
look at the bit pattern from the binary representation and then translate these
bits into hexadecimal digits: 1111 == F; 0111 == 7; 0000 = = = 0; 0100 = = 4.
You need to make sure that the second argument is a string for which
enough storage has been allocated to build a value up to 17 bytes long— 16
bytes for the int and 1 byte for the null terminator.
long labs (long val) This function returns the absolute value of a long
argument. It is used in the same way as abs( ), but with long variables instead of
int. The labs( ) function is not built into QuickC.
Idlv_t Idiv (long int numerator, long int denominator) This function
returns a structure containing the results of using long integer division to
divide numerator by denominator. Two values are returned, the whole
number quotient and the whole number remainder. The returned type, ldiv_t
isa typedef that specifies a structure containing two long int members, quot
and rem, for storing the quotient and remainder, respectively. Look at the
stdlib.h file to see the exact definition.
Essentially, ldiv( ) returns the results of applying both integer division and
the modulus operator to numerator. Note that ldiv() is not predefined in
QuickC. The following statement would assign the structure returned by ldiv(
to the ldiv_t variable, l_outcome. This function is not built into QuickC.
char *ltoa (long nr, char *str, int radix) This function converts a long
integer value to a string. The resulting string is returned. The value you want
converted is the first argument, nr; the second argument is str, the string built
(and eventually returned). The third argument, radix, is the number base in
; ;
mainO
char *result . temp [ MAX.STR]
result = Itoa ( -2300L, temp, 2);
printf ( "base 2 "/.sU" :
result) ,
;
base 2 : lllllUlllllllllUlloiUOCOGOlOO
base 8 : 37777773404
base 10 : -2300
base 16 : fffff704
Notice that, as with the itoa( ) function for int arguments, only the base ten
int system (const char *str) This function passes the string argument to
the operating systemcommand interpreter, which takes this string as an
command line. Thus, the effect of a call to system() with a DOS
ordinary
command can be the same as if you had typed the string at a regular
DOS
prompt outside your program.
;; ; : ; ;
; ;; ;
If you call the function with a null pointer, the function returns a nonzero
DOS command and if the command
processor present,
value if the is
The following program illustrates the use of the functions that are declared in
#include <stdio.h>
#include <stdlib.h>
#deiine HAX_STR 80
mainO
<
void show_system C void)
void show_itoa ( void)
void show_ltoa ( void)
void show_div ( void)
void show_ldiv C void)
void Bhow.setenv ( void)
show_setenv ()
system ( "pause")
show_system ;
system ( "pause");
show_itoa ()
Bhow_ltoa ();
show.div ();
show.ldiv ()
void Bhow_itoa ()
void Bhow_ltoa ()
do
{
printf ( "Base? ");
inlo = gets ( temp)
radix = atoi ( info);
while ( ( radix < 2) I | ( radix > 36));
result = Itoa ( value, temp, radix)-
printf ( "value == '/.Sid; base == •/.2d;
result == -/.sU"
value, radix, result);
do
; ; ; ; ;;; ;; ; ;; ;: ; ;; ;; ,
void 8how_ldiv ()
<
char *info, temp [ MAX.STR]
ldiv_t results:
long int numer, denom;
void Bhow.systemO
8how_setenv ()
char *what8_included;
setenv :
SoH
DETEX EXE
^l
9776
lilt;
7-01-87
^d
lO^Bla
*° "'"'*'^*' (° ^° stop) -6
Base? S^*'"'"^
l?or° v=i„ »
''*" ° " ? "^"" == 10022220002
convert? (0 to stop) -5
Base? 10
T?i"^ ° u ,
'^'- ''^^ "
1° : "^iiit == -5
Base?' 16* *° ''° "^^''*- ^° *» ^top) "5
Tti"*
itoa 7, Value
1
"^' ''*"
to convert? (0 to stop)
:
"
18; result == fffb
ltor° v.i„/^
itoa "J^/'
Value to convert?
**" "S"" == fffffffb
.
(0 to stop)
div Numerator? (0 to stop) -57
:
Denominator? 23
Denominator? 23
numer == -57; jeno^ ==, ,„
quotient "
-2; remainder ==
-n
Numerator? (0 to stop)
Let's look at some of the functions in this program. The show _setenv()
function checks the current path
and the directories that your programs will
search for mclude files. The calls to
systen,( ) invoke the DOS pause
command
which waits for the user to press a key
before proceeding
h
String.
This header file contains declarations for the string handling functions de-
scribed in Chapter 11. In addition, the file contains declarations for several
functions useful for handling arbitrary data stored in memory.
Familiar Functions
Before moving on to some new functions, let's look briefly at the functions
described in Chapter 11.
of characters. The functions return — 1 if strl < str2, if the strings are equal,
and 1 if strl > str2.
size_t strlen (const char *str) This function returns the number of
characters in the specified string. The value returned does not include the
terminating null character in its count.
New Functions
The next group of functions (those whose names begin with mem) are useful
for moving data around in memory. Rather than working only with strings, the
functions can manipulate arbitrary data types. Some of the functions are mem
counterparts to some of the string handling functions briefly described earlier.
void *memchr (const void *target, int ch, size— t nr) This function
searches target for any occurrence of ch. It will search up to nr bytes or until it
finds ch, whichever comes first. Note that target is declared as a pointer to
void, which you can cast to a different type of pointer. The memchr( ) function
returns a pointer to the location of ch in target, or a null pointer if ch is not
found in the first nr bytes of target.
The following statement returns a pointer to int, after searching the first
int memcmp (const void *buffer1, const void *buffer2, size_t nr) This
is a comparison function, like strcmp( ); memcmp( ) compares the first nr bytes
of the two buffers. The function returns — 1 if bufferl is less than buffer!, if
the two buffers are equal, and 1 if bufferl exceeds bufferl. You can cast the
buffers to the type of pointers you need in your program. The following listing
shows how to call menicnip( ).
void *memcpy (void *target, const void *src, size— t nr) This function
copies the first nr bytes of src to target, and returns a pointer to the target
buffer. Be very careful using this function if the two buffers overlap. For
example, suppose src starts at address 4000 and target starts at address 4050. If
you want to copy 100 bytes of src to target, the compiler may overwrite the
contents of src starting at location 4050 before copying them to target. The
next function described, memniove( ), is better suited for transfers involving
overlapping storage sections.
The following listing copies nr bytes from src to target and returns a
pointer to the resulting buffer:
/* assume target, src, and result have been defined as pointers to int. */
void *memmove (void *target, const void *src, size_t nr) This function
number of bytes from src to target. Unlike memcpy( ),
also copies the specified
however, menimove( ) moves overlapping bytes before writing over them. The
function returns a pointer to the target buffer.
The following call moves nr bytes from src to target:
/* assume target, src, and result have been defined as pointers to int. */
void *memset (void *target, int ch, size t nr) This function copies ch
into the first nr bytes of target, and returns a pointer to this target buffer. The
following call puts *w' into the first 50 bytes of target. The memmove()
function is not built into QuickC.
char *strchr (const char *str, int ch) This function searches for the first
occurrence of the character, ch, in str, and returns a pointer to the first
occurrence of ch in str, or a null pointer if ch is not found.
The following listing finds the first 'a' in str:
char *strdup (const char *str) This function makes a duplicate of str, and
returns a pointer to this duplicate. If the function can't allocate storage for a
copy of the string, the function returns a null pointer. Otherwise, the function
returns a pointer to the newly allocated space containing the string.
The following call assigns a duplicate of "hello there" to the string,
message:
char *strlwr (char *str) This function converts any uppercase characters
in str to lowercase, then returns a pointer to the resulting string. The following
listing converts "HELLO THERE" to lowercase, returning "hello there."
char *strpbrk (const char *str1 const char *str2) , This function looks for
the first occurrence, in strl, of any character from str2. If one is found, the
function returns a pointer to this character — which effectively returns the rest
of the string. If no matching character is found, the function returns a null
pointer.
QuickC Library Functions 529
The following listing searches for any character from "hello" that appears
in "California." The value of str will be "lifornia" after the function call.
char *strrchr (const char *str, int ch) This function looks for the last
it is found. If the character is not found in the string, the function returns a null
pointer.
The following listing finds the last T in "Walla Walla," and returns a
pointer to this character. In the example, this would result in the string "la"
after the call.
char *strrev (char *str) This function reverses the characters in str (except
for the null terminator) and returns a pointer to the reversed string. The
following listing shows how to call strrev( ). You also have reverse _str( ) in
your function collection from earlier.
char *strset (char *str, int ch) This function fills str with the character ch,
except for the null terminator, which the function leaves as is. The strset( )
char *strstr (const char *str1, const char *str2) This function looks for
the first occurrence instrl of str2, and returns a pointer to the beginning of this
first occurrence. If str2 is not found instrl, the function returns a null pointer.
The following listing looks for "law" in " Wallawalla" and returns a pointer
to the beginning of its appearance.
; ;
•define MAX.STR 80
•define MAX NR 80
•define MAX MENU 15
•define NULL.CHAR 'XO'
system ( "pause");
PARAMETERS :
printf ( "\n\n");
printf ( "\n\n");
/« prompt user */
printf ( "Your choice? (Xd to Xd) ", 0. MAX.MEHU - 1);
main
<
void show.menu ( char * []
void show.memchr ( void)
void show.memcmp ( void)
void show.memcpy ( void)
void show.memmove ( void)
void show.memset ( void)
void show.strchr ( void)
void show.strdup ( void)
void show.Btrlwr C void)
void show.strpbrk ( void)
void show.strrchr ( void)
void show.strrev ( void)
void show.strset ( void)
void show.strstr ( void)
void show.strupr ( void)
char info [ MAX.STR]
int selection;
do
gets ( info)
selection atol ( info)
switch ( selection)
{
default:
break;
case
break;
case 1:
show.memchr ();
break;
show.memcmp ()
break
; ; •• ; ; ;; ; ; ;;; ;;;;;•; ; ;
case 3:
show_memcpy ()
break;
case 4:
show.memmove ()
break
case 5:
show.memset ()
break;
case 6:
show.strchr ()
break;
case 7:
show.strdup ()
break
case 8:
show.strlwr ()
break
case 0:
show.strpbrk ()
break;
case 10:
show.strrchr ()
break
case 11:
show.strrev ()
break;
case 12:
show_strset ()
break;
case 13:
show.strstr ()
break;
case 14:
show.strupr ();
break
>
while ( selection I- o)
void show.memchr ()
4
;
;: : : ; ; ; ; ; ; ; ;
•laa
<
printf ( " Xc found: return value Xa\n", ch, cresult)
void ahow.memcmp ()
{
char Btrl [ MAX.STR] , atr2 [ MAX.STR]
Int reault
void ahow.memcpy ()
<
char atrl [ MAX.STR] , *Btr2 , *reBnlt , temp [ MAX.STR]
wait ():
void ahow.aemmove ()
{
char strl [ MAX.STR], *str2, *result, temp [ MAX.STR];
Btrcpy Btrl.
( "abcdefghljkl„mopqrstuv^z"r
Btrcpy ( temp, strl)
str2 - strl + lo
prlntf ( "str2 •- Xs\n".
strj)
result memfflove ( str2 atrl nt-yi.. / ^ nw
void Bhow_memset ()
printf ( "Initialization
character' ")
gets ( info) '
ch = info [ 0]
snjrriii?j^i,^VJ5-<^-) 1)^
' "'^ " '• ^""It);
'
wait 0;
void show_strchr ()
gets { info)
ch - info [ 0]
result . strchr ( strl. ch)
printf^ "For first occurrence,
( result - %B\n". result);
void show.strdup
()
void show.strlwr ()
void show.strpbrk
{
char Btrl [ MAX.STR] . Btr2 [ HAX.STR] . *result
ait ();
wait ():
wait ():
void show.strset
void show.Btrstr
wait ():
The best way to become comfortable with the way these functions work is
to play with this program, trying various values for each of the available
functions to see the results.
The session with meinchr( ) may need a word or two of explanation. This
function lets you look for a particular byte in an array of pseudorandom in!
values (generated using rand( )), and then in a string. To emphasize the fact
that memchrO works with bytes rather than 16-bit in! values, the program
displays the array values in two ways: as a regular int (all 16 bits at a time) and
by displaying the high and low bytes, which must add up to the 16-bit value.
Remember that inenichr( ) compares the specified byte against the two bytes
that make up each array element, not against the entire array element.
ctype.h
Table 17-1 summarizes the routines, and includes the conditions the int
Routine
538 Using QuickC
If you are going to use theisXXX macros defined in ctype.h, but wish to
use the tolower( ) and toupper( ) functions built into QuickC, you need to use
the #undef preprocessor directive to undefine the macros. The next program
will provide an example of how to do this.
portable.
#lnclude <8tdio.h>
#lnclude <ctype.h>
#ifdef tolower
thuidef tolower
«endif
GLOBALS MAX.MENU
:
PARAMETERS :
*/
printf ( "\n\n");
printf ( "\n\n"):
/* prompt user */
printf ( "Your choice? (W to Xd) ", 0, MAX.MEHU - 1);
main ()
<
void report ( int)
int ch, selection, result;
char info [ MAX.STR]
selection 0;
show.menu ( menu, MAX.MEHU);
gets ( info)
selection - atoi ( Info)
if ( ( selection > 0) bti selection < MAX MEHU)) (.
<
printf ( "Character to test (enter int value)? ")
gets ( info)
ch - atoi ( info)
}
switch ( selection)
<
default
break;
; : ;; ; ; ; ; ; ;;;;;;;;;; ; ;;;;•;;;;;;;;
case 0;
break;
case 1
result - isalnun ( ch)
report ( result)
break
case 3:
result • Isalpha ( ch)
report { result);
break;
case 3:
result - isascil ( ch)
report ( result)
break
case 4:
result - iscntrl ( ch)
report { result)
break;
case 6:
result - isdigit ( ch)
report ( result)
break
case 6:
result isgraph ( ch)
report ( result)
break;
case 7:
result = islower ( ch)
report ( result)
break;
case 8:
result = isprint ( ch)
report ( result)
break;
case 9:
result = ispunct ( ch)
report ( result)
break
case 10:
result = isspace ( ch)
report ( result)
break;
case 11:
result = isupper ( ch)
report ( result)
break;
case 12:
result - Isxdigit ( ch)
report ( result)
break
case 13:
result « toascii ( ch)
printf ( '7.c\n", result);
break;
case 14:
result - tolower ( ch)
printf ( "Xc\n'', result);
break
case 16:
result - toupper ( ch)
printf ( "XcXn". result);
break i
while ( selection !- 0)
QuickC Library Functions 541
dos.h
This file, which is not part of the Draft Proposed ANSI Standard, provides
information and data structures for taking advantage of some of the services
DOS can provide. In this file, QuickC and Microsoft C v5.0 provide a REGS
union type that you can use to access the machine registers in your computer.
You can access entire (16-bit) registers at one time, or you can access the
individual bytes of a register. This union is used when you want to call DOS or
the BIOS (Basic Input Output System) to perform a particular task.
In addition to this union declaration, dos.h also contains the function
declarations you need to access the registers. In this section, you'll learn about
one of these functions, and how you can use it to get various types of
information from DOS.
Background
You don't really need to know the details of how things operate internally when
you ask the operating system to do something. A few words about how the
hardware and software interact to provide DOS services might be helpful,
however.
int intdos( union REGS *inreg, union REGS *outreg) This function
initiates a DOS software interrupt, 0x21, to request a specified operating
system service. Before you you need to put a value corresponding
call intdos( ),
to the desired service in one of the members of the inreg union. You may also
have to provide other information, depending on the service requested. In all
cases, this information should be stored in the appropriate union member,
before calling intdos( ). Note that intdos( ) returns the value in register AX
after the requested DOS service has done its work. Let's look at an example, to
Determining Availabie DIsIc Space The following program tells you how
much storage space is available on the default drive of your computer. The
function get _ free _ space( ) calls intdos() for software interrupt 0x21 and
main
result = get_lree_space ()
printf ( "y,ld total bytes f ree\n" , result) ;
total'fr.?iTr^-P"-'^"*
total.free - bytes * sectors * clusters;
"
''•^^''"- clusters.per drive) ,-
sectors -4
clusters - 1204
bytes " 612
clusters.per.drive —
16318
Total disk capacity ~
33418264
Total bytes free —
2466792
2466782 total bytes free
Getting Date and Time The following program adds three new functions
to the preceding program. Two of these use DOS services to do their work; the
third, how _ Iong( ) is used to compute the time elapsed between any
starting
and finishing points.
; ; ; ; ; ; ; ; ; ;
main ()
return ( total.free)
; ;; ,
Notice that month and date are stored in bytes, whereas year
is stored in a 16 bit register value.
*/
void get_date ( int *month, int *day, int *year)
{
/* Ask lor DOS service Ox2a */
inreg.h.ah = Ox2a;
after midnight, the results will be incorrect ifyou simply subtract one value
from the other, because elapsed time starts from again at midnight. The
how _ long( ) function returns the amount of time elapsed between two calls to
get_time( ), and corrects the computations for the cases where you cross the
midnight boundary.
The program uses these three functions to determine how long it takes to
call DOS for the current date 1,000 times.
Be careful when using DOS interrupts, especially if you are using interrupts or
services that will actually change something on the disk. Before you use
services that will reset the current date or write to particular parts of a disk,
make certain that you understand exactly how the interrupt or service works,
thatyou know exactly what values you want to change, and exactly what new
values you want. You can get into lots of trouble if you are sloppy here. Again,
548 Using QuickC
the references mentioned earlier can provide more details about the interrupts
and services.
io.h
This file, which is not specified in the Draft Proposed ANSI Standard,
contains definitions and declarations for doing lower-level input and output,
and file handling.
Familiar Functions
You've already learned about one of the functions declared in io.h: the
remove( ) function, which deletes the specified filefrom disk. The function
takes a string argument, which specifies the file's disk name, and returns if
the file has been removed, or a negative value if it has not been removed.
New Functions
The following functions provide additional ways to use and change files:
most common modes are read only, write only, and read and write. In DOS all
files can be read, so effectively there are only two modes: read only or read
and write. You also can tell the access( ) function simply to check whether the
file exists.
The mode argument can take the following values: 0x00 (check for
existence only), 0x02 (check for write permission), 0x04 (check for read
permission), or 0x06 (check for read and write permission).
Int chmod( char *file_name, int mode) This function changes the file
access setting associated with a particular file. For example, the function lets
you make a read only file out of one that you could previously read and write.
The function does not open the file; it only changes the file's status in the disk's
directory.
As noted in the previous function, there are essentially three modes for
interacting with a file: read only, write only, and read and write. The chmod( )
function lets you specify a new setting for the file you pass as the first
and third are equivalent, since all DOS files have read permission.
This Statement will modify the status ofmyfile.tex so that it will be possible to
write to the disk file with that name. Note that chmod( ) returns if the change
was successful, and — 1 if there was an error.
int rename ( const char *old, const char *new) This function lets you
rename an existing disk file or directory, from old to new. The two arguments
are strings that specify the file name, including any necessary path informa-
tion.The function returns if the name change is successful, and a nonzero
value if there is an error. The rename( ) function is not built into QuickC.
There are some restrictions on this function. First, old must exist, and
new must not exist. If a file with the same name as new exists, the function will
return an error value. Another restriction is that you can't transfer files from
one drive to another. For example, you could not use this function to transfer a
file from drive B: to drive A:.
The following listing illustrates the use of rename( ). The statement would
effectively move the file 17.5 from the \bk \src directory to the \bk directory.
; ; ; :
The following program shows how some of the functions declared in io.h
work. The program checks whether a file named zzzztest.io exists. If not, the
program creates such a file, opening it as write enabled. The program then
changes the access mode to make this file read only. Notice that one conse-
quence of this is that the DOS command to delete the file fails, as it does in the
calls to system( The "Access denied" message comes from DOS.
).
Notice the way access( ) is used in the tests. You are always looking for the
negation of the value returned. This is because access( ) returns a if the
outcome is as you expected. So, you need to make this true, which you can do
by negating it.
•include <stdio.h>
•include <Bys\typeB.h> / needed for chmod () */
•include <sy8\stat.h> /* needed for chmod () */
•include <io.h>
•include <stdllb.h>
main ()
FILE *f.ptr;
fcloseallO:
if ( I access ( "zzzztest.io", 0x02)}
{
chmod ( "zzzztest.io", S.IREAD);
printf ( "File changed to read only\n");
}
else
printf ( "File is already read only.\n");
Access denied
File changed to read / write
New file is in directory.
anymore.
math.h
This file contains the declarations for many functions useful for mathematical
work. The file also includes a structure declaration for complex numbers. The
math functions are not built into QuickC.
Familiar Functions
You've seen a few of the functions declared in math.h in some of the example
programs.
552 Using QuickC
abs()
labs( ) These functions compute the absolute value of an int and a long,
respectively. Recall that the absolute value is the positive form of a number.
Thus, abs( 5) == 5, and abs( —5) == 5, as well.
pow( ) This function takes two arguments, raises the first argument to the
power specified by the second argument, and returns the result as a double.
sqrt() This function returns the square root of the value passed in. The
argument to sqrt( ) must be or greater. If you pass the function a negative
value, the function will return and will set errno to the appropriate error
value.
New Functions
The following math functions represent only a few of the functions declared in
value of errno (a global variable containing a code for the last error encoun-
tered), and prints an error message to stderr.
Note that asin( ) also handles only arguments between — and In this 1 1 .
case, the function returns a value between — tt / 2 and n- / 2. The asin( ) function
^Include <stdio.h>
#include <math.h>
main ()
double counter:
Value
; ; ; ; ; ; •
#define MAX_STR 80
#define TOLERANCE le-7
#define TRUE 1
#define FALSE
main ()
i:t":n":z"ern '.izi
{
'' "
v^if"
'"^ "^° "^ "° " ^'^^^ ^ ^^^^^"-^ — */
^
r:tu;n° (° TRUE)° '"'''"'^ " "^^' " °°^ ' -TOLERANCE))
else
return ( FALSE)
#include <Btdio.h>
Vlnclude <math.h>
main ()
<
double counter;
Value
556 Using QuickC
Summary
This chapter has provided a brief discussion
of some of the header files that
come with your QuickC package, and of some of the functions, data types and
constants declared in these files. With this
information, you're all set to explore
C more thoroughly on your own. Happy programming!
I
ASCII CODES
Table A-1 lists the ASCII codes for characters.
DEC
558 Using QuickC
One of C's most important features is the ease with which it allows you to build
and use function libraries, precompiled collections of functions that you can
use in you programs. The advantage of using such libraries, as opposed to
having the source code for the Ubrary functions in your program, is in the time
saved because the compiler doesn't have to compile the functions every time
you make changes to your program.
QuickC is particularly flexible in the way it you work with
lets libraries.
You can build two types of Ubraries in QuickC. The first type. Quick
libraries.
are built for use when you want to run your program inside the QuickC
environment, as opposed to building an executable version of your program.
559
560 Using QuickC
That IS, Quick used when you want to compile your program
libraries are
to
memory while inside QuickC, and then run the program.
Quick libraries have
the extension .qlb. You don't need to use a Quick library if you plan to compile
the function to executable code each time - that is, if you're going to create an
•exe file whenever you compile. Quick can only be used with QuickC.
libraries
The second type of library is a stand-alone library, which contains
precompiled functions that can be linked into your
program when you're
building an executable version. Such libraries
generally have the extension
.lib. Stand-alone libraries
have the same format as the libraries included with
QuickC or Microsoft C 5.0.
In this appendix, you'll learn how to
build both types of libraries. You'll
build a Quick library containing Run-Time
library functions that are used in
our example programs, but that are not built
into QuickC. Chapter 6 of the
QuickC Programmer's Guide contains a table of the
functions built into
QuickC. As you'll notice, several of the functions
randO, pow(), and fgetc()-are not built imo QuickC.
we've used including —
In the next section
you'll see how Quick library file, qcstuff.qlb, that contains these
to build a
functions. (This library has been mentioned in
earlier chapters.) You must
build the qcstuff.qlb library yourself; it is not
part of QuickC.
You'll also learn how to build a stand-alone library, and how to link this
library into your programs. We'll only
be looking at a small number of the
possibilities here; read the discussion in
your QuickC Programmer's Guide for
details.
2. Compile and link this source file to create a compiled version of the file
with the extension .qlb.
3. Load the Quick library when you want to run your program within
QuickC.
;
#include <math.h>
#include <stdio.h>
main ()
{
char ch;
double dbll. dbl2;
randO ;
Brand ;
IgetcharO ;
IputcharC ch)
_ftol();
pow ( dbll, dbl2);
sqrt ( dbll);
>
As you can see, the format for this file is simple. The entire "program" consists
The program does nothing with the results of
of calls to the desired functions.
these function calls.
The basic strategy QuickC to link the desired functions into a file
is to get
file is then trans-
that really does nothing with them. The resulting object
formed into a .qlb file having the format required for QuickC to be able to
use
these functions when you compile and run your program within QuickC.
Once you've created your source file, you need to create a Quick library from it.
The following line contains a batch file you can use to build the file qcstuff.qlb.
qmckhb ''u^Tr'
m-k' obj file is to be found.
The quicklib.obj file is needed for building
Quick ibranes. If you set up your QuickC
files using the default
directories
you only need to specify quicklib here-that
is, you don't need to
specify an
entire path. ^ ^
The first invokes the qcl program as in the earlier example. The /c option
line
Such files will generally have the extension .obj, but they may also
be
together.
stand-alone libraries — that is, .lib files.
The qcstuff.qlb argument specifies the "run" file you want the linker to
one-Hne version. Notice that linker parameters, or fields,
create, just as in the
type of information,
are separated by commas. This tells the linker where one
or set of parameters (object files, for example) stops
and the next type (run
files, for example) starts.
Two commas with nothing between them indicate that the linker can use
its default values for that field. In this
case, the space between qcstuff.qlb, and
the next comma indicates that the linker can use its default
names for a file
know as the file, which the linker can create.
map (Such a file contains
information about where in the module the segments of a
program are to be
default, in this case, the
found. For most purposes, you won't need this file.) By
linker would write this information to qcstuff.map.
search for
also specify additional libraries for the linker to
You could
routines needed in other modules. In our case, there were
no other libraries
the qcstuff.c file that started it all: qcstuff.obj (created by the compiler),
following pair of
qcstuff.map, and qcstuff.qlb (created by the linker). The
command lines would have accomplished the same thing, except that no .map
file would be built.
Notice that the /Q option has been moved to an earlier part of the line. This
makes no difference, but the semicolon immediately after the name of the run
564 Using QuickC
qc /Iqcstuff srclile
*include <stdlo.h>
#include <dos.h>
double get_time ()
"
/* Total elapsed time (in seconds)
3600 (sees / hr) * hrs +
60 ( sees / min) * mins +
1 ( sees / see) * sees + , , ^^^
.01 ( sees per 1/lOOth see) * hundredths.
USAGE :
*° -uppercase characters
USAGf" ^make.str.upper
USAGE ^J'''"*
( my.str)
:
^^
void make.str.upper
( char *8tr)
int index:
,/
"SAGE
val)
Return true If val (iiff=-_
test.relSJt'^.^^LTzerTc-^LtTt":^:)^"' ' ^"'^«^^-<*
:
if non.zero ( denom))
(
'"^*"" ( n'»er / denom)
else ;
'*
^c^^-rt-h^viiu^ers^^^ff !^4-"« ^---''^
USAGE swap.dbl '"' *"""'^-
^^
:
( fir^t! second J"*
void swap.dbl double *first. double
(
.second)
double temp-
P. /* <-„ .^
/* to store one of the values during
swap ./
temp - *first;
*first • .second;
.second - temp;
/* double zero_one_rand
Retoms a pseudorandom value between and 1, inclusive.
USAGE : pseudo zero_one rand ()
•/
double zero_one_rand ()
{
define MAX.VAL 32767.0 /* NOTE: defined as floating point type */
int rand ( void) ; /* returns a pseudorandom integer */
int rand.result
2. Build a .lib library file from this object file, using the library manager
program, lib.
A batch file containing the following two command lines will let you build
a stand-alone library file from a source file. In the discussion, assume the file
qc 1 /c '/.I . c
lib Xl.lib +'/.i.obJ;
The first line invokes the qcl program to compile the source file specified in the
command line argument when starting the batch file. In our case, this argu-
ment would be utils, without an extension (since the extension is added in the
batch file). You would include this argument on your command line when you
invoked the batch file.
line in the batch file builds the stand-alone library. The first field specifies the
name of a library file to be modified. If no file with this name exists, the library
manager will create a new one, which is what we want in this case. The second
field specifies that the hbrary manager should add (+) the contents of the
object file specified (in this case, utils) to the .lib file. The semicolon again
indicates that the program should not look for additional fields or options.
(See Chapter 10 of the QuickC Programmer's Guide for details on these
additional fields.) The outcome from executing this command line is that a file
named utils.lib is created.
568 Using QuickC
#include <stdio.h>
#define MAX.TRIALS 20
main ()
double)-
double safe.dlvislon ( double,
mt double)-
''
Index;
^'n^/c-lJjto^^lJ^irUJ-.-cond);
if ( ratio > max.ratio)
The following command line would let you compile this program which
we 1, call rat.os.c, linking
,n the uti.s.lib hbrary while'creatmg'the
ex";:table
or in the standard
searched for any functions not found in tiie ratios.obj file
libraries.
The resulting ratios.exe file behaves just the same as if the program had
included in both
been built out of a large source file containing the functions
this case, compiling just ratios.c, then linking in utils.Iib, is almost 20% faster
than working with the source files alone.
on your
You can explore additional options and ways of using the linker
own. One thing to notice is we didn't include the /AM option. By default,
that
QuickC compiles with a small memory mode. The resuUing files are different
than they would be if they had been compiled with a medium memory model.
make sure the libraries and the source files are
When you link in libraries,
Summary
In this appendix, we've looked at two ways to use precompiled files to make
libraries let you
program development quicker and more convenient. Quick
the advantages of
compile and run your programs within QuickC, which has
compile, and revise
speed and ease of modification. This means you can create,
without ever leaving QuickC —
one of the nicest features of the software.
to, you can
Once you've got the program working the way you want it
you'll often want to save
compile it to an executable file. During this process
tested, and can now be
time by linking in precompiled libraries that have been
the compilation process
used in any of your programs. This approach makes
gives you flexibility for building modular
programs. By collect-
faster, and also
ing related functions in libraries and then linking these libraries into the
smaller (since only the
programs you're building, you can make your programs
functions will be linked in) and also make your
program-
librafies for needed
ming tools more widely usable.
QuickC Commani
Summary
The vertical bar, |, means "or," so that | 1means "0 or 1," as in the
command to set warning levels for qci.exe, for which any of the following
would be valid:
watch variables. This material may be repeated more than once. Both of
571
572 Using QuickC
val
val ; even
Use as many
screen lines as possible, given the
display adapter being used
/|,
qc /h
Editing Commands
The following commands are available while editing a file. Many of these
Movement Commands
Move up one line UP ARROW
or
CTRL-E
Move down one line DOWN ARROW
or
CTRL-X
Move right one character RIGHT ARROW
or
CTRL-D
Move left one character LEFT ARROW
or
CTRL-S
574 Using QuickC
CTRL-F
Move left one word
CTRL-LEFT ARROW
CTRL-A
Move to top of screen
CTRL-Q E
Move to bottom of screen
CTRL-Q X
Move to beginning of line
HOME
CTRL-Q S
Move to end of line
END
or
Move to beginning of
CTRL-Q D
file
CTRL-HOME
or
CTRL-Q R
Move to end of file
CTRL-END
or
CTRL-Q C
Move to next error
SHIFT-F3
Move to previous error
SHIFT-F4
Scroll up one line
CTRL-W
Scroll down one line
CTRL-Z
Scroll up one screen, or window PGUP
or
CTRL-R
Scroll down one screen, or window PGDN
or
CTRL-C
Scroll left one window
CTRL-PGUP
Scroll right one window CTRL-PGDN
Find text to be specified
CTRL-Q F
Find next occurrence of text just
found F3
Find selected text
CTRL-\
Find and change text to be
specified CTRL-Q A
QuickC Command Summary 575
Selection Commands
Select preceding character SHIFT- LEFT ARROW
Select current character SHIFT- RIGHT ARROW
Select preceding "word" SHIFT- CTRL-LEFT ARROW
Select current "word" SHIFT- CTRL-RIGHT ARROW
Select preceding line SHIFT UP ARROW
Select current line SHIFT DOWN ARROW
Select to beginning of file SHIFT CTRL-PGUP
Select to end of file SHIFT -CTRL-PGDN
Insertion Commands
Insert text from Clipboard SHlFT-lNS
Deletion Commands
Delete character at current cursor position DEL
or
CTRL-G
Delete preceding character BACKSPACE
or
CTRL-H
Delete to end of current word CTRL-T
Delete current Hne CTRL-Y
Delete to end of current line CTRL-Q Y
576 Using QuickC
™
Delete selected text, saving in
Clipboard shift dpi
Delete control character
.^ ^
CTKL-P<character>
Miscellaneous Commands
Toggle between overtype and
insert modes
INS
or
CTRL-V
Mark section of text
CTRL-K
Jump to marked section of text
I
1
CTRL-Q 1
Find matching left brace
I I
CTRL-1
Find matching left bracket
CTRL-[
Find matching left angle bracket
Find matching
CTRL<
left parenthesis
CTRL-(
Find matching right brace
CTRL-1
Find matching right bracket
CTRL-]
Find matching right angle
bracket CTRL->
Find matching right parenthesis
CTRL-)
Move to next tabstop
TAB
Move to previous tabstop
SHIFT-TAB
Menu Commands
StkCmls'rr""
v<!uickC
^^^^° — -d «P^-ns '^^ available through
menus. These commands are
grouped by menu.
Program List Commands The following commands let you create, mod-
ify,and delete program list files, which contain the names of all the source files
the preceding options has been set. That is, the following assume either
ALT-F L or ALT-F E.
Once QuickC has created or opened the specified program list file,
ALT-E C
Paste text from Clipboard to
Target (requires SHIFT-INS
text in Clipboard)
or
ALT-E P
Make working file read only
ALT-E R
Undo last action on current line
ALT-BACKSPACE
or
ALT-E U
or
ALT-S S
Find function definition
Find next error
ALT-S U
SH1FT-F3
or
statement ALT-R R
Set compile-time options ALT-R C
Compile-time options include the following. All these options
assume ALT-R C has been selected.
or
ALT-I
Build program ^
or
RETURN
582 Using QuickC
Compile file
^
Rebuild all „
Set run-time options
^
The following options all assume alt-r O has been selected:
Specify information to be
passed
to program on command line
for use in programs
just being
compiled memory
in
<command line information>
Specify memory to allocate for
global and static program
data
{near data)
c .. ALT-N
specify amount of memory
to
allocate for local variables
(stack) ^LT-S
?efo7""'^°
The following^-^^'^'^^^^^
options both toggle:
more compact 8
Single character ^
The following keyboard commands will let you step through the program
in the debugger:
value>
Specify name of executable file to be
created (same
name as main source file, .exe
extension) I Fe<file name>
Specify name of map file
to be created (none)
I Fm<file name>
Specify name of object file
to be created (same
name as main source file, .obj
extension)
5>pecify language underlying
I Fo<fi/e name>
function-calling and
naming conventions used by
program (for
mixed-language programming)
I Gc fortran |
pascal
I
cdecl
\
QuickC Command Summary 585
Memory Models
The following commands let you specify the memory models and library iles
The following commands let you use the preprocessor and also save files after
command) /C
586 Using QuickC
The following linker options let you control various values relating to the
program being compiled.
In this appendix we'll briefly mention some of the differences between the
In a certain sense, QuickC and the Microsoft C 5.0 compiler represent two
different philosophies of programming environments. The QuickC environ-
ment makes the programming task easy, by collecting the most useful devel-
opment tools in one program, and by providing fast and convenient compila-
tion. The C 5.0 compiler makes the programming product (the resulting
589
590 Using QuickC
program) as fast and efficient as possible. That is, the emphasis in the C 5
compiler is on the program being
built, rather than on the process of building
Memory Models
The C 5.0 compiler supports Small, Compact, Medium, Large, and Huge
memory models; QuickC supports all but the
Huge model. While both com-
pilers support mixed memory
models, the C 5.0 compiler also lets you
specify a
customized memory model, as described
in the User S Guide for the
compiler
QuickC defaults to a Medium memory
model; C 5.0 defaults to a Small
Features of QuickC and Microsoft C 5.0 591
Libraries
Both compilers include run-time libraries containing the same functions, and
both include a library of graphics functions (graphics.lib). In addition, each
compiler includes a library unique to that compiler. QuickC comes with a
graphics Quick library file (graphics.qlb), which you can tell QuickC to use
if
you want to compile programs that use graphics to memory. The C 5.0
compiler includes an alternate floating point library.
To handle floating point operations in QuickC, you would use a floating
point library (such as mlibfp.lib) and either of two other libraries. One of
these
operations.
In C 5.0, you can use an alternate math library (such as mlibfa.lib) instead
of the mlibfp.lib and em.lib combination. The alternate math library is much
faster, but accurate, than the emulator library. Using the alternate library
is less
library, the C 5.0 compiler also gives you more options for specifying
how
floating point operations are to be handled in the program being created.
QuickC you compile a program to
lets memory and then run the pro-
gram. This compilation process does not include a link phase, so the compiler
does not automatically get access to QuickC's Run-Time library. This means
that only functions buiU into QuickC and those defined in your
program can
be used when compiling to memory.
Ifyou need to use Run-Time library functions but would still like to
compile to memory, you can build a Quick library as we did in Appendix B.
Such a library consists of the Run-Time library functions needed by your
program. A compiler switch lets you tell QuickC to make use of this hbrary
Debugging
As mentioned
Cod"ev'''?-J
" " '"'""
S?cic7eb:;r'"^^^^^^
the QuickC development
'''' '^^^"^^^ ^"'^ —
tools represent a subset of
-'^^ available
their C5
wh the
The CodeView debugger can
evaluate expressions written
in anv of
several languages,
mcluding C, BASIC, FORTRAN,
and Pascal CodeView
can^ven work w.th Hles that
contain both C and as.mblyTouL
code ^
In addition, the expressions
CodeView can handle are more
han h,,e possible extensive
m the Qu.ckC debugger. For example, Co
handle expressions mvolving evlw can
arithmetic and logical operato
s as welUs the
^^ ''- ^"'?" ^^^"^^-- -'-^^^ -'"^^ the iL-
ZToi\::tf:: e^t
even ook
evenfo^ok at thVcT^^":'
the ""'t registers,
contents of machine
"
'^'^"^^ ^"^"^^ "^^"^"^^ '"-»--' and
as the program executes
You
vL Th::r rt''"^' '^^^ ° ^ ^^^^^ ^^^^^ ^^ specific'locat.on:tcode-
rnfTc^Co^vrTr'^''^^"'^'^'^
in fact, CodeView lets
you compare two areas of
memory with a single
Miscellaneous
QuickC
You can use QuickC's program list facility to provide the information
files save time by
needs to produce a .mak file automatically. Such "make"
recompiled.
making it possible to compile only those files that need to be
The format of the .mak file produced by QuickC is the same
as that
Similarities
While there are a number of differences between QuickC and C 5.0, the two
QuickC source and object files are completely compatible with the C 5.0
compiler. That means you won't have to change programs developed with
QuickC, if you decide to optimize or recompile them under C 5.0.
If you tell the QuickC compiler to compile your program for use with a
debugger, the compiler produces a file that is compatible with CodeView. This
means you can debug your QuickC programs with CodeView, if you wish.
All of QuickC's compiler and linker switches are identical to the same
options for the C 5.0 compiler.
Finally, both compilers use the same language definition, as you might
expect, since they use the same Language Reference manual. Both compilers
support most features of the Draft Proposed ANSI Standard language defini-
tion, and both include various language extensions. You can tell either com-
piler not to accept code using language extensions if you need to ensure
dBASE® Ashton-Tate
dBASE III® Ashton-Tate
DOS™ International Business Machines Corporation
Microsoft® Microsoft Corporation
QuickC® Microsoft Corporation
UNIX® AT&T
WordStar® MicroPro International Corporation
I
)
Index
597
598 Using QuickC
i
Index 599
Compound statements, 81, 168, 208 Default keywords and labels, 179-180
commands, 573-575 E
keystrokes, 41-45 echo command, 372
Cut and paste, 46-50 Edit Menu commands, 576
D Editing
floorO, 553
F fopen( ), 392, 507
Program
P character routine, 538-541
Parameters example of structure and features,
R short (integer), 99
randO, 137,214-215,517 short int, 153
Types, continued
Variables
operations on mixed, 10-113
1
array, 290-303
simple data, 88-98
checking values of, 30
variable, 84
declarations and definitions of. 258
defining, 84-88
W
Values, 264-267 Warning Levels. 62
checking intermediate, 359 While loop, 186-191
returned function, 213-217 components of, 188
suppressing, 164 Whitespace, 161
variable, 158-165
Z
Variable types, 84
zero__one_rand( ), 345
OsborRe/McGraw-Hill's
Indispensable
« J
^yv; Osborne ^^Gla wHill
m^muL 2600 Tenth Street
Tradeniar1(s Lotus and 1 -2-3 are registered trademarks of Lotus Development Corp dBASE is a registered trademarlr and
dBASE III PLUS IS a trademartt of Ashton-Tate MS-DOS is a registered trademark of Microsotl Corp
iyi^\^^l X increases your
DOS addressable conventional
memory beyond 640K for
only $195.
Order toll free 1-800-227-0900. MAXIT is just $195 plus $4 shipping, and applicable state sales
tax. Buy MAXIT today and solve your PC's memory crisis. Call Toll free 1-800-227-0900 (In Cali-
fornia 800-772-2531). Outside the U.S.A. call 1-415-548-2805. We accept VISA, MC.
MAXIT IS a trademark of Osborne McGraw-Hill IBM is a registered trademark of International Business Machines Corporation, 1-2-3 and
Symphony are registered trademarks of Lotus Development Corporation, Sidekick is a registered trademark of Borland International. Inc;
PARADOX IS a trademark of ANSA Software, FOXBASE+ is a trademark of Fox Software, Hercules is a trademark of Hercules Computer
Technology, XT and AT are registered trademarks
Inc, of International Business Machines Corporation; Compaq is a registered trademark of
Compaq Computer Corporation
iLl;5
o S J=
be _r ^
c C - D X
•
"^
^ U at
S c
>- Q. _ 8 i
2 E
cr5 c: £
(SJ O
«j a CO6 n D n D
c« o
D a
<U O
X =-1; -^
H
IX 6
3 > 3
2 5 .c
e =
" " r •£
c
o
U 3;
c
2 £
3 'C
a ill
3
HJ
=
T3
i
O ">-
:=
aJCQ Qi
lU
X C & ^ S: X 5 D D
'
Feibel, Werner.
Using Qu.ickC
Numerous examples are used throughout tiP5ook so that you gain a better
understanding of the concepts^^|y
All in all, you'll easily maste^^^oft's impressive compiler with the expert
techniques foun^^WMipMIc^.
Werner Feibel i^^^P^nt and an instructor at Boston University.
Microsoft and^kH^Bngistered trademarks ot Microsoft Corp