PCjs Machines

Home of the original IBM PC emulator for browsers.


MS QuickC Programming

The following document is from the Microsoft Programmer’s Library 1.3 CD-ROM.

Microsoft QuickC Programming


Microsoft(R) QuickC(TM) Programming

The Microsoft(R) Guide to Using the QuickC Compiler

By The Waite Group
    Mitchell Waite, Stephen Prata, Bryan Costales, and Harry Henderson


Microsoft Press
A Division of Microsoft Corporation
16011 NE 36th Way, Box 97017, Redmond, Washington 98073-9717

Copyright (c) 1988 by The Waite Group, Inc.
All rights reserved. No part of the contents of this book may be reproduced
or transmitted in any form or by any means without the written permission of
the publisher.

Library of Congress Cataloging in Publication Data

Microsoft QuickC programming.
Includes index.
1. C (Computer program language)  2. Microsoft QuickC (Computer program)
I. Waite, Mitchell.  II. Title: Microsoft Quick C programming.
QA76.73.C15M53    1988    005.13'3    88-5203
ISBN 1-55615-048-2

Printed and bound in the United States of America.

1 2 3 4 5 6 7 8 9  MLML  3 2 1 0 9 8

Distributed to the book trade in the United States by Harper & Row.

Distributed to the book trade in Canada by General Publishing Company, Ltd.

Distributed to the book trade outside the United States and Canada by
Penguin Books Ltd.

Penguin Books Ltd., Harmondsworth, Middlesex, England
Penguin Books Australia Ltd., Ringwood, Victoria, Australia
Penguin Books N.Z. Ltd., 182-190 Wairau Road, Auckland 10, New Zealand

British Cataloging in Publication Data available

IBM(R) is a registered trademark, and PC/AT(TM) and PC/XT(TM) are trademarks
of International Business Machines Corporation.

Microsoft(R) and MS-DOS(R) are registered trademarks, and QuickC(TM) is a
trademark of Microsoft Corporation.

Acquisitions editor: Claudette Moore
Project editor: Eric Stroo
Copy editor: Gary Masters
Technical reviewer: Doug Henderson





Chapter 1  Introduction
            Why Learn C?
            Why QuickC?
            Hardware Requirements
            Knowledge Requirements
            Conventions and Style

Chapter 2  Starting with QuickC
            Our Book and Their Book
            Directories and Files Used by QuickC
            Running the QuickC SETUP Program
            Setting Up QuickC
            Starting QuickC
            Getting Help
            Fixing Errors
            Preparing for the Next Chapter


Chapter 3  C Fundamentals
            Basic Elements of C Programs
            Punctuation and Spacing in C Programs
            Using Comments in C
            Data Types and Declarations of Variables
            The Power of printf()
            Arithmetic Operators
            Getting Input with scanf()
            Shortcut Assignments, Increments, and Decrements
            Relational Operators
            Logical Operators

Chapter 4  Repetition and Looping
            The for Loop
            The while Loop
            The do Loop
            Debugging and Loops

Chapter 5  Decisions and Branching
            The if Statement
            The Conditional Assignment Statement ?
            Multipath Branching
            The switch Statement
            The break Statement
            The continue Statement
            The goto Statement
            More Complex Conditions for Branching

Chapter 6  Functions and Function Calls
            Functions and Program Design
            Declaring and Defining a Function
            Local and Automatic Variables
            Static Variables
            External Variables
            Register Variables
            Passing Information to a Function
            Functions with Many Parameters
            Functions That Return Information
            Noninteger Functions
            Function Prototypes
            Putting It All Together: A Larger Program


Chapter 7  Arrays
            How Arrays Are Stored in Memory
            How to Declare Arrays
            Referencing and Using Array Items
            Bounds Checking Arrays in Your Code
            How to Initialize Arrays
            Arrays and Functions
            How Array Offsets Advance
            Multidimensional Arrays
            Advanced Topics and Tricks
            The Bitwise Operators, Tiny Arrays

Chapter 8  Addresses and Pointers
            Addresses Reviewed
            What Is a Pointer?
            Accessing Variables with Pointers
            Passing Pointers to Functions
            Pointers and Arrays
            Pointer Arithmetic
            The Interchangeability of *amts and amts[]
            lvalue vs rvalue
            Type Casting Pointers and Addresses
            far Pointers
            Functions That Return Addresses
            Dynamic Arrays
            Advanced Pointer Techniques

Chapter 9  Strings
            Declaring and Initializing Strings
            The String Pool and String Addresses
            Pointers and Initialized Strings
            Formatting Strings with printf()
            String Input and Output
            String Manipulation Routines
            Arrays and Strings
            The Arguments to main()──argv and argc
            Character Classification and Transformation

Chapter 10 Managing Files
            Top-level I/O
            Mid-Level (Unbuffered) File I/O
            The File System
            Advanced Error Handling

Chapter 11 Advanced Data Types
            Structure──An Array of Different Types
            Union──Multiple Types in the Same Space
            Enumerated Data with enum
            Bit Fields
            Advanced typedef

Chapter 12 Large Projects
            Advanced C Preprocessor
            Using QuickC for Large Projects


Chapter 13 Keyboard and Cursor Control
            Keyboard Input Functions
            Reading Non-ASCII Keys
            Console I/O Functions
            Keyboard Control with ANSI.SYS
            Using QuickC to Access the BIOS
            Cursor and Screen Control with BIOS Calls

Chapter 14 Monitors and Text Modes
            Monitors and Controllers
            Text Modes and Portability
            Device-independent Programming
            Direct Memory Access
            The EGA and VGA

Chapter 15 Graphics and QuickC
            The Graphics Modes
            CGA Graphics
            EGA Graphics
            VGA Graphics

Chapter 16 Debugging
            Keyboard-Entry Errors
            Syntax Errors
            Run-Time Errors
            Common Run-Time Errors
            Design Errors

Appendix A  Some Resources for C Programmers

Appendix B  Built-in QuickC Functions



    The Waite Group has written books on all aspects of the C language, from
    primers to advanced texts that mix C with assembly language☼ But when
    Microsoft Press suggested we write a book on Microsoft's then unreleased
    QuickC compiler, we were skeptical. Having spent years trying to teach C
    both in the classroom and through our books, we were painfully aware of
    how difficult a language it is to learn. Despite its power, the Microsoft
    C Compiler has confounded many an eager student. Daunted by the cryptic
    syntax of its command-line interface, they slipped from C's learning curve
    and disappeared beneath its power curve.

    Our first look at the QuickC beta version sparked our attention──this was
    no standard C compiler. QuickC's interface was profoundly different: The
    editor completely integrated into the compiler, optional mouse
    compatibility, drop-down menus, full color display, the list went on and
    on. Finally we had point-and-click compiling. To us, all this meant a
    radical shift in the way we could teach C──we could take a friendly
    approach that would make C accessible to a much larger group of people.

    But was QuickC really a performance program? Were we talking fast object
    code or code more suited to timing traffic signals at snail races? Well,
    after creating hundreds of examples for this book, we can report that
    QuickC lives up to its speedy expectations. A product that can compile
    more than 10,000 lines per minute should satisfy all but the most jaded of

    There was more. QuickC was fully compatible with the libraries of version
    5.0 of the Microsoft C Optimizing Compiler. We could develop our C code in
    QuickC's fast and friendly interface, get the errors out quickly with its
    integrated debugger and tracer, and then optimize our program for
    execution time by compiling it under the standard optimizing compiler.
    With the introduction of QuickC, we felt that C had finally reached the
    point of rivaling BASIC in ease of use. We saw in QuickC a product that
    could vitalize the learning of C and reinforce its dominance in
    professional program development.

    As educators, we saw that QuickC was an opportunity to build a complete
    course in the C language, one that could be pursued without an instructor
    and that would be attractive to beginners and professionals alike. The
    seductive interface made QuickC a breeze to run──we could focus on the
    syntax rather than on complicated command-line options and batch files. On
    top of this, QuickC's large and comprehensive Graphics Library meant that
    we could make our examples visually appealing and challenging. By assuming
    an IBM personal computer running MS-DOS or PC-DOS, we could tackle the
    sensitive issues of the interface between MS-DOS and C while at the same
    time flexing the keyboard, text, and bitmapped displays.

    After all these grandiose thoughts, the next thing we did was very
    natural──we gulped loudly. In the time available, no single author could
    possibly master a product as rich as QuickC with the aim of writing a book
    that examines thoroughly its vast repertoire. The solution was to pool the
    energies of four experienced C authors with over 25 years of combined
    programming and teaching experience, each writer focusing on a different
    section of the book. We think you will find that this book goes beyond
    most C books on the market (including our own). If you have any questions
    or comments, please address them to: The Waite Group, 3220 Sacramento
    Street, San Francisco, California 94115.

    Mitchell Waite
    Stephen Prata
    Bryan Costales
    Harry Henderson


    The authors would like to take this opportunity to thank the people at
    Microsoft Press for helping to make this book a success: Claudette Moore,
    for acquiring this title and putting up with the authors' numerous
    requests throughout the project; Gary Masters, for editing and blending
    the different styles into one clear message; Doug Henderson, for his
    technical review; Eric Stroo, for coordinating the progress of the final
    manuscript through the production process and for being so diligent about
    the final look of the book; Alison Conn, for her review of the book's
    technical coverage; and Greg Lobdell, for providing a continual flow of
    alpha and beta copies of the QuickC compiler. Also, thanks to Reed Koch
    for his many hours of explaining QuickC's internals to the authors.

                                To Bobbie Lee,
                    who touched me in a way no one ever has


Chapter 1  Introduction

Why Learn C?

    If you have experience with C, you are probably familiar with its
    advantages over alternatives such as BASIC or Pascal, and you may want to
    skip to the next section, which discusses the specific advantages of
    QuickC for C programmers. Here we compare C with two other popular
    languages, BASIC and Pascal.

    Although Pascal has its enthusiasts, and our old friend BASIC certainly
    has been improved in many ways (Microsoft's QuickBASIC for example), C has
    quickly become the premier language for professional programming both on
    micros, such as the IBM PC family, and on larger machines, such as those
    running the UNIX/XENIX operating system. Why is C so popular?

Portability and Standards

    One reason is portability. The core of standard C is so designed that the
    same program runs on an IBM PC, a VAX mini, and an IBM mainframe.
    Portability comes about from adhering to standards that guarantee common
    features and functions regardless of the vendor, implementation, or
    hardware environment. The first, informal C standard was proclaimed by the
    famous "white book," Brian W. Kernighan and Dennis M. Ritchie's The C
    Programming Language (New Jersey: Prentice-Hall, 1978). The specifications
    in this book have been widely adopted in the design of C compilers, but
    the definitions are not comprehensive and specific enough to provide a
    true standard. Therefore, the American National Standards Institute (ANSI)
    has proposed a draft standard for the C language. (At the time of this
    writing, the standard has not been officially adopted, but most of its
    features seem stable.) Most current and future C compilers will be written
    to conform with the ANSI standard. QuickC is compatible with the ANSI
    standard. It also permits you to verify that your code uses only
    ANSI-compatible functions and definitions or to identify nonstandard
    features, such as those needed to support functions specific to MS-DOS and
    to IBM hardware.

    Another reason for the popularity of C is its close ties to the UNIX
    operating system. UNIX was written in C, and a variety of standards
    support the use of C in the UNIX environment. QuickC is functionally
    compatible with the UNIX System V standard library specifications.

    But what does all of this mean to you, the QuickC programmer?

    A C program written under QuickC on an IBM PC can, if it uses only
    ANSI-standard features, be moved to an Apple Macintosh, and you can
    compile it with an ANSI-standard Macintosh C compiler and run it in the
    new environment.

    This level of standardization is not common in programming languages.
    Pascal is only partially standardized: A Turbo Pascal program for the IBM
    PC, for example, cannot run under standard IBM Pascal without
    modification. In the IBM PC world, the ubiquitous BASICA program has
    offered a kind of standard, but other models of computers are provided
    with quite different dialects of BASIC, and you must do an extensive
    conversion to get a BASIC program written on one machine to run on another
    manufacturer's hardware.

    Notice that this discussion applies specifically to the "core" of C: the
    control structures, data structures, and basic input/output functions.
    Outside of this standard core, however, a number of areas of a C
    implementation are machine-dependent, such as the size of various kinds of
    numbers, keyboard codes, the video screen, graphics, and features of the
    operating system that handle files. To be worth its salt, a C compiler
    that runs on the IBM PC must include functions that give programs access
    to MS-DOS features, the underlying BIOS, and the hardware. Similarly, a C
    compiler for the Macintosh must include functions that give a program
    access to such elements as the machine's system toolbox. These functions
    are hardware-dependent and implementation-specific──by definition, they
    are not portable, but they are essential to getting the most out of your
    machine. C, as you will discover, provides a way to gather the
    machine-dependent parts in an organized manner, something other languages
    can't do.

    BASIC and, to a lesser extent, Pascal approach hardware dependence by
    customizing the language itself to include commands or functions that take
    care of the machine-dependent features. For example, a BASIC statement to
    control the speaker might be called PLAY. Another version of BASIC might
    call it MUSIC. The problem with this approach is apparent when you try to
    convert a program to run on a different machine; you cannot easily find
    the parts of the program that you must change to manipulate proprietary
    features. Also, such hardware-dependent statements may work differently on
    computers with different hardware configurations.

A Modular Approach

    The programmer's task is more manageable with C. Each C compiler includes
    files of definitions, called include files, and collections of precompiled
    functions, called function libraries, which you can use to supplement the
    core of C to take full advantage of the features of a given machine. Your
    QuickC function library includes a rich collection of definitions and
    functions for MDA, CGA, EGA, MCGA, and VGA graphics (as well as Hercules
    graphics, starting with version 1.01); the whole set of MS-DOS function
    calls; and much more.

    The result is that a C programmer has several choices. If you don't need
    graphics or machine-specific features, you can write an ANSI-standard
    text-only C program and easily move it to other machines and operating
    systems. If you do need machine-dependent features in your program, you
    can use the "no-frills" version of the program and then add graphics and
    other hardware-dependent features in easily identified include files and
    libraries. For a particular hardware environment, you can then merge the
    appropriate include files and libraries into your program. Figure 1-1 on
    the following page illustrates the concept of portability.

    Portability requires many trade-offs. In general, the less portable (in
    other words, the more hardware-dependent) a program is, the faster it
    runs, and the more it takes advantage of graphics and other special
    hardware features. On the other hand, the more portable a program is, the
    easier it is to maintain, modify, or convert it to work with new hardware.

    Throughout this book, we point out portability issues and suggest ways to
    deal with them. For example, we note those features of QuickC that are
    compatible with ANSI and UNIX System V. We also look at portability versus
    performance in the MS-DOS world. For example, we discuss alternative ways
    for dealing with devices such as the keyboard and video display on MS-DOS
    machines (standard I/O, console I/O, and BIOS) and point out the
    portability trade-offs involved with each.

    │                            │  Not
    │    Customized statements   │  portable
    │                            │
    │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
    ├┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┤
    │                            │
    │      MS-DOS and BIOS       │
    │                            │
    │          Hardware          │


    ┌────────────────────────────┐  Can run on
    │     Standard functions     │  IBM PC, VAX,
    └─────────┐        ┌─────────┘  Macintosh,
            │        │            and others.
    ┌─────────┐   ▼    ┌─────────┐  Implementation
    │         │        │         │  of C, such as
    │         │        │         │  Microsoft C or
    │         └────────┘         │  Quick C.
    │ Machine-specific libraries │
    │┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ │
    └┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘
    ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐ ┌─┐
    ┌┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┘ └─┐  Specific
    │                            │  machine-
    │      MS-DOS and BIOS       │  IBM PC,
    │                            │  for example.
    │          Hardware          │


    Figure 1-1.  Portability in C.

C Is Powerful

    Portability is desirable, but you also want to write code that takes full
    advantage of the hardware. In this age of drop-down menus, windows, mice,
    and help screens, users expect a lot more out of software than they did
    only a few years ago. As a programmer you are often pushing the limits of
    the hardware, whether in processing speed, I/O, or graphics.

    When it comes to harnessing the hardware, C really shines. For example,
    other languages try to hide the fact that you are manipulating the
    contents of memory when you write code; with C pointers, you can easily
    manipulate memory directly. With Pascal, you can also directly manipulate
    memory with pointers, but the syntax is not as simple or as powerful as
    that of C. And in BASIC, you can use a PEEK and a POKE to access memory,
    but they lack the flexibility of pointers.

    Another important indicator of the power of a language is its ability to
    use machine resources efficiently. All high-level compiled languages
    translate program statements into machine instructions. With most
    languages you have little control over the efficiency of the resulting
    machine instructions. You are at the mercy of the assumptions the compiler
    or interpreter makes about your program and how it will be used. Suppose,
    for example, that your program uses one or two variables frequently in a
    loop that will be executed many times. In C, you declare register
    variables that are stored, if possible, in internal CPU registers; thus,
    delays in loading or retrieving their values in memory are avoided. The
    result is faster execution speed.

    Another important feature of C is its ability to create a variety of
    memory models. A memory model describes the way RAM is used during
    compilation and the way program code and data are shared in RAM. With most
    older BASICs you can use only 64 KB of memory to hold program code and
    data. Today, most MS-DOS machines have at least 256 KB (and often 640 KB
    of memory or more). Thus, newer compilers for BASIC, Pascal, and other
    languages often allow access to a larger amount of RAM. But C compilers go
    a step further: You──the programmer──decide how the computer will allocate
    memory. Depending on the needs of your program, you can choose to use most
    of the machine's memory for storing compiled instructions, you can use
    most of the memory to store data (such as arrays, structures, or lists),
    or you can allocate varying numbers of 64 KB memory segments to both.
    Figure 1-2 on the following page shows the concepts of register
    variables, pointers, and memory models.

    Pointers, register variables, and memory models are only some of the
    options C gives you for controlling the machine. In addition, most C
    compilers let you improve, or "optimize," the machine code generated from
    your program. You can optimize for program size (a smaller .EXE file) or
    for faster execution or for a combination of these. For example, QuickC
    performs some optimization for you and lets you choose other features as
    appropriate. In addition, you can use QuickC in combination with Microsoft
    C (the professional, industrial-strength C compiler) to provide
    optimization that is truly state of the art.

    Pointers   ┌──────────────────┐              ┌────────────────┐
    for direct │ main ()          │              ├────────────────┤
    access to  │ int * ptr; ──────┼──────┐       ├────────────────┤
    memory     │ ptr ++; ─────────┼───┐  │       ├────────────────┤
                │ ...              │   │  │       ├────────────────┤
                │                  │   │  │       ├────────────────┤
                │                  │   │  │       ├────────────────┤
                └──────────────────┘   │  │       ├────────────────┤
                    Program          │  └──────►├────────────────┤
                                    │          ├────────────────┤

    (A)                         POINTERS

    Fast       ┌──────────────────┐              ┌────────────────┐
    register   │ main ()          │─────► CPU    ├────────────────┤
    access     │ register int i;  │◄─────        ├────────────────┤
                │ ...              │              ├────────────────┤
    Regular    │                  │◄─ ─ ─ ─ ─ ─  ├────────────────┤
    memory     │ int regular_varn │  ─ ─ ─ ─ ─►  ├────────────────┤
    access     │ ...              │              ├────────────────┤
                └──────────────────┘              ├────────────────┤
                    Program                     ├────────────────┤

    (B)                     REGISTER VARIABLES

                                    │      │
                    ┌──────┐         │ Code │          ┌──────┐
                    │ Code │         │      │          │      │
    ┌──────┐        ├──────┤         ├──────┤          │ Code │
    │ Code │        │      │         │      │          │      │
    ├──────┤        │ Data │         │ Data │          ├──────┤
    │ Data │        │      │         │      │          │ Data │
    └──────┘        └──────┘         └──────┘          └──────┘

    (C)                        MEMORY MODELS

    Figure 1-2.  C gives you control of the machine.

C Is Extensible

    C also lets you customize the contents of include files and libraries so
    that they contain only the definitions and functions your program needs.
    These custom files can contain functions for anything from manipulating a
    database to formatting text. After you write and test these definitions
    and functions, your main program can use them as easily as it can use the
    standard include files and libraries provided with your compiler. On large
    real-world programming projects, teams of programmers can receive
    specifications for each set of routines needed, and each team can create
    resources that can be used anywhere in the project. Although most
    languages offer a version of this building-block methodology, the C
    approach is the simplest, the most flexible, and the easiest to use.

    The very popularity of C enhances the value of such language extensions.
    Hundreds of vendors have created C function libraries for almost every
    imaginable task. Figure 1-3 shows conceptually how you can use function
    libraries from both QuickC and other vendors in your programs. You can
    easily integrate vendor libraries into your own code, and because they are
    the products of professional C programmers, they are likely to be fast and
    efficient. You can almost always avoid the age-old problem of reinventing
    the wheel.

                                │  MS-DOS  │
                                │     ┌──────────┐
                                │     │   I/O    │
                                │     │     ┌──────────┐
                                └─────│     │ Graphics │
    ┌─────────────┐                │     │          │
    │             │                └─────│          │
    │ Included  ◄─┼──────────────────────│          │
    │ definitions │                      └──────────┘
    │ ─────────── │
    │ Your code   │                              Third-           Your
    │ main ()     │           Microsoft          party            custom
    │ ...         │           libraries          libraries        library
    └─────────────┘           ┌─┐┌─┐┌─┐          ┌─┐┌─┐           ┌─┐
            │ Compile          │ ││ ││ │          │ ││ │           │ │
    ┌────────▼──────────┐       └─┘└─┘└─┘          └─┘└─┘           └─┘
    │ Compiled Modules  │───────────┘  │               │             │
    ├───────────────────┤              │               │             │
    │ Standard Library  │──────────────┘               │             │
    ├───────────────────┤                              │             │
    │ Graphics          │──────────────────────────────┘             │
    ├───────────────────┤                                            │
    │ Database          │────────────────────────────────────────────┘
    │ Special functions │
            │ Link
    │   Ready-to-run    │
    │      program      │

    Figure 1-3.  Using include files and libraries.

C Is Structured

    The syntax of the C language itself supports structured programming. C
    provides the control structures of a modern structured language, such as
    if/then/else, for, while, while...do, and switch. (The last is like
    Pascal's case statement.) If you are experienced in Pascal or in one of
    the newer BASICs (such as Microsoft QuickBASIC), you will find these
    control structures conceptually familiar. However, you will have to learn
    syntax differences for C, and boxes in the text point these out. If you
    are used to one of the older BASICs, you will be pleasantly surprised at
    how these structures enable you to avoid nearly all goto statements that
    lead to disorganized "spaghetti code."

C Is Concise

    Although C is a well-structured language, it encourages concise rather
    than verbose statements. For example, it uses braces to begin and end
    blocks of code, rather than Pascal's begin and end. C provides shorthand
    operators for assigning values to variables and for incrementing
    variables. To show the flavor of C, the following table presents a few
    comparisons of C, Pascal, and BASIC assignment statements:

    Some Comparisons of BASIC, Pascal, and C
                            BASIC           Pascal           C
    1. Set a, b, and c to 0  a = 0           a := 0;          a = b = c = 0;
                            b = 0           b := 0;
                            c = 0           c := 0;
    2. Set i to i + 1        i = i + 1       i := i + 1;      i++;
    3. Set a to a + 5        a = a + 5       a := a + 5;      a += 5;

    Such conciseness speeds the typing of programs and makes C source files
    more compact and easier to edit. C functions are more accessible than
    their Pascal counterparts and much more efficient than the awkward
    subroutine mechanism of BASIC. With the C preprocessor, you can create
    your own shorthand, or macro, definitions with which you insert
    expressions or whole blocks of code in text by typing the name of the

    This brief overview of the general features of C should suggest why the
    language is so popular. Let's now look more closely at the product with
    which this book is concerned, Microsoft QuickC, and see how its particular
    features and advantages make programming in C even more attractive.

Why QuickC?

    Traditionally, C has had one big drawback compared with interpreted
    languages such as BASIC──a complex compilation and debugging process. You
    probably know that C is a compiled language, and MS-DOS─based compiled
    languages traditionally have required that you go through a lengthy series
    of steps to produce an executable file.

    The steps to compiling a traditional C program are the following:

    1.  Start a text editor or word processor and write a program.

    2.  Save the program to disk and exit the editor.

    3.  Run the compiler program by issuing a command line from the DOS
        prompt, usually with several filenames and options included, that tell
        it, for example, what memory model to use and whether to generate a
        listing file.

    4.  Look at the listing produced by the compiler, and study every error

    5.  Print out this error list for reference.

    6.  Start the editor again, open your C program file, and for each error
        try to find the exact line in which the error occurred and correct the

    7.  Go back to step 3 and try again until the program compiles without
        errors into an object code file.

    8.  Now run the linker, and tell it what libraries to combine with your
        object code file to produce an executable program (an MS-DOS .EXE
        file). If you used an incorrect function name or failed to specify the
        correct libraries, you will now get a new batch of error messages,
        this time from the linker. (They may, for example, report an
        "unresolved external," which probably means the name you used for a
        function in your code did not match the name of the function defined
        in the library.) To fix these errors, you may need to look at listings
        of include files. Or you may have to go back to the editor and correct
        your program. In any case, you must recompile and then try to link

    9.  When the code links without errors, you can finally run the program.
        Did it execute as you expected? No? Do you want to make some changes?
        Well, go back to the editor and try again.

    Just reading through these steps suggests how tedious a traditional
    compiled language can be. With interpreted languages, such as BASIC, LOGO,
    or HyperTalk, you can type a line or two of code, execute it immediately,
    and see the results. If your line of code contains errors or if you want
    to add or change something, the interpreter usually provides a simple text
    editor or line editor you can use immediately.

    But interpreted languages have one critical drawback──they're slow. Each
    line in a program in an interpreted language has to be translated into
    machine-executable instructions each time it is encountered. Therefore,
    only the simplest interpreted-language applications run fast enough for
    use in the real world.

    The philosophy behind QuickC is to provide a programming environment that
    is as easy to use as an interpreter, but with the execution speed
    obtainable only through a compiler. With QuickC, writing and testing
    programs is so easy that C can be a beginning programmer's first language.

The QuickC Programming Environment

    With QuickC, you do all of your program development in and from the same
    place──the QuickC integrated programming environment. (Figure 1-4 shows
    the way your screen looks when you start QuickC.) This environment offers
    many advantages:

    1.  You can open a file for editing by using the Open command on the File
        menu, or you can simply start typing a new program. The QuickC
        full-screen editor is immediately available, with insert/delete,
        cut/paste, indention──all the features you need to type a program as
        easily as you type a letter with a word processor. And you never
        really "leave" this editor. You merely select whatever service you
        need from the menus.

    │ Figure 1-4 can be found on p.12 of the printed version of the book.    │

    Figure 1-4.  The initial QuickC screen.

    2.  To run the program you can click on the mouse or type a command. When
        you work with a program that has not yet been compiled, the compiler
        and linker are called as needed. There are no complex command-line
        options to type. If your program is error free, the program runs in
        seconds on the output screen.

        The main reason your programs compile, link, and run so quickly with
        QuickC is that, unlike traditional C compilers that compile and link
        to disk, QuickC by default compiles to memory. Thus, it can compile
        10,000 lines a minute on a standard IBM PC/AT. Another reason is that
        some of the most commonly needed functions are held in memory. You
        also can create libraries that can be loaded into memory. The result
        is that QuickC uses available memory very efficiently.

    3.  As you view an error message, the cursor follows along through your
        program text; you can instantly correct each error with the built-in
        editor. No printed listings to pore over; no error numbers to look up!

    4.  Suppose your program compiles correctly but doesn't work as you
        expected. Without leaving QuickC, you can turn on the debugging and
        trace features, rerun your program, and then watch the changing values
        of selected variables, follow the flow of execution, and check the
        values being passed to and from functions called by your program.

        What about multiple-module programs──C programs that have several
        separately compiled libraries and code files? Traditionally, you had
        to run a special "make" program and give it a file with a unique
        syntax that told the compiler how to rebuild such a complex program
        after any change was made. With QuickC's program list feature, you
        simply tell QuickC what libraries and source code files you want to
        use. QuickC keeps track of all the other details, such as the
        relationship between modules and the date each module was last

    5.  Do you need access to MS-DOS? Need to make a new directory or back up
        some programs? Maybe you want to run some previously compiled C
        programs from MS-DOS. With a traditional, command-line-driven C
        compiler, you exit the compiler, work in MS-DOS, and then run the
        compiler again and figure out where you left off. With QuickC, you
        never leave the integrated programming environment. Using QuickC's DOS
        Shell feature, you exit to MS-DOS, take care of your business, and
        return to QuickC where you left off.

    You can select many other features from the QuickC programming environment
    in the same easy way. With a command-line compiler, most features require
    that you type obscure flags or option switches on the command line or
    create batch files to simplify complicated compiler commands. With QuickC,
    you select with a mouse click or keystroke such features as the error
    warning level, language extensions, and optimization. But don't let the
    convenience deceive you──underneath the covers, QuickC is constructing the
    proper list of options so that you can use the same linker. (QuickC also
    includes a command-line-driven compiler for those times you have a special
    need, such as compiling under certain memory models, or when you want to
    work outside the QuickC environment.)

QuickC Performs

    QuickC is faster in almost all cases than its nearest competitors, and it
    beats them hands down in floating-point operation.

    QuickC also is fully compatible with its "big brother," the Microsoft C
    Optimizing Compiler, versions 5.0 and later. Any program that compiles
    under QuickC compiles under Microsoft C, version 5.0. Therefore, you can
    develop programs with QuickC and then effortlessly recompile them under
    Microsoft C for fine tuning, using a variety of optimization techniques.

QuickC: Standard and Comprehensive

    Earlier we discussed ANSI and other official standards for C. There are
    also unofficial industry standards that are almost as important. When you
    use QuickC, you have the benefits of using a compiler that has become the
    industry standard for PCs: Microsoft C. QuickC is fully compatible with
    that standard. Thus, dozens of third-party C code libraries work with your
    programs because the programs you write are compatible with the ANSI or
    UNIX System V standards or with the MS-DOS─specific features of
    Microsoft C.

    The extras that come with the QuickC product are also impressive. Each
    standard-model library (small, medium, compact, large) supports the 8087
    coprocessor. There are libraries for every kind of PC graphics from
    monochrome and CGA to the latest VGA graphics for the IBM PS/2. The QuickC
    Graphics Library routines feature easy-to-use routines for drawing points
    and lines and manipulating complete images, including filling and
    animation, all with impressive speed. QuickC also has libraries that allow
    your programs complete access to MS-DOS and BIOS calls. And, because of
    QuickC's UNIX compatibility, you can also use UNIX System V functions for
    writing programs that can be ported to work in the UNIX environment.

Hardware Requirements

    To run QuickC you need an IBM PC/XT, PC/AT, PS/2, or compatible computer
    with at least 448 KB of RAM and at least two floppy-disk drives. We
    suggest, however, that you develop QuickC programs on a hard disk.
    Compiling or linking to disk with floppy disks is time-consuming compared
    with hard disks. Also, fitting all the files you need for developing
    programs onto two disks can be tricky. But because some of you will be
    using floppy-disk-based systems, we will give you some tips later that
    should help you make the best of the situation. (And the situation is
    anything but grim: You can certainly develop programs that run great under
    QuickC on a floppy-based system.)

    We also recommend (but don't presuppose) that you use QuickC with a
    compatible mouse. You can handle all QuickC functions from the keyboard,
    but why get bogged down learning the keystroke combinations? With a mouse
    in hand, you simply point at what you want and select it.

    On the other hand, many people don't have (or choose not to use) a mouse.
    With QuickC, you can use short keystroke combinations. For example,
    Alt-r-s selects the Run menu's Start option to compile and run a program.
    (Even if you have a mouse, typing is sometimes faster.)

    Graphics capability is optional for most of this book. Chapter 15, which
    deals with graphics, requires a CGA, of course; for advanced graphics, you
    need an EGA; and the VGA section requires a PS/2, or a VGA board for older
    PCs. (If you have the new VGA, you also have CGA and EGA capability.) Even
    if you have only the basic monochrome adapter, you can create many
    interesting QuickC programs with the built-in IBM graphics character set.

    Finally, we recommend that you have a printer (although a printer is not
    required for this book).

Knowledge Requirements

    Some programming experience──with BASIC or Pascal, for example──will help.
    But thanks to the ease of use of QuickC, C can be your first programming
    language, although you may have to work a bit harder than more experienced

    Because many of you have programmed in BASIC (such as Microsoft's BASICA
    or QuickBASIC) or Pascal (Borland's Turbo Pascal, for example), we scatter
    "asides" throughout the text for BASIC and Pascal programmers. These point
    out the ways in which C is similar to and different from those languages.
    Familiarity with another language is a two-edged sword when it comes to
    learning C. On the one hand, you already know many programming concepts
    used in C. On the other hand, differences in syntax and usage can trip you
    up if you aren't careful.

    If you are a UNIX programmer, you will feel right at home──as soon as you
    get used to QuickC's much more comfortable living room! The QuickC
    environment is far easier to use than the UNIX cc compiler and ln linker,
    and you won't have to write any make scripts. You probably already know
    the fundamentals of C, but watch out for features that are different in
    the IBM PC/MS-DOS environment, especially graphics and MS-DOS system
    calls. But as we noted, with a few minor exceptions, Microsoft C supports
    the standard I/O and other library functions used on UNIX systems.
    Occasional boxes point out matters of interest to UNIX programmers.

Conventions and Style

    We have chosen the following typographical conventions for the
    descriptions of a C program in the text:

    ■  Names of ordinary (local) variables are lowercase italic. Example:
        count, sum

    ■  Names of external or global variables are also italic, but the first
        letter is capitalized. Example: Model

    ■  Underscores join the words of multiple-word variable names. Example:
        Grand_total (an external variable) or line_count (an ordinary variable)

    ■  Constants created with #define are uppercase italic. Example: PI

    ■  Macro definitions are uppercase italic. Example: PRINT_ERROR(MSG)

    ■  Function names are lowercase italic. Underscores join multiple-word
        function names. Examples: main(), count_lines(), printf()

        You'll also notice that names of Microsoft library functions that are
        non-ANSI-standard (such as the graphics functions) are lowercase italic
        and preceded by an underscore. Example: _getvideoconfig() (a Graphics
        Library function)

    ■  In #include statements, names of header files that we create are in
        double quotation marks. Example: #include "chr_graphics"

        Names of header files provided by Microsoft are in angle brackets.
        Example: #include <graphics.h> (This convention affects the way in
        which the compiler searches for header files on disk.)

    ■  Built-in "keywords," or reserved words, of the C language are lowercase
        italic. Examples: int, do, while

    ■  Program names are uppercase roman. Example: HELLO.C

    ■  Filenames and pathnames are uppercase roman. Examples:

    ■  Names of special keys are spelled as they appear on the standard IBM PC
        extended keyboard. Examples: Enter (not Return), Ctrl-C, the Esc key

Program Listings

    Program listings are set off from the text in a monospace font. Constants,
    variables, and function names are capitalized as indicated in the
    preceding list but are not italicized.

    In many cases, we provide a sample session that demonstrates how a program
    interacts with the user. In these listings, user input is italic.

    guess────────────────────────────────────────────Run the guess.c program
    What number am I thinking of?───────────────────────────Program response
    7─────────────────────────────────────────────────────────────User input
    Wrong! Try Again?
    Right! You win!

    NOTE: The comments at the right in the sample session above are not part
    of actual program dialogue.

Program Style Conventions

    A clear, consistent typographical style makes programs easier to read. No
    single style is universally accepted for C program listings. Ultimately,
    you fashion your own, based on your judgment and the prevailing usage. In
    some cases, more than one kind of syntax can be used. Although C itself
    doesn't care about spacing between the elements of a statement or an
    expression, we use a space between elements unless removing the space is
    clearer. Also, we use a 4-space indention for nested statements and the
    braces that enclose them.

    We always align braces ({ and }) vertically──a major stylistic departure
    from Kernighan and Ritchie. That is, we put

    <body of function>

    rather than

    function_name() {
    <body of function>

    We believe this style enables you to read the listings and identify blocks
    of code more easily. Be warned, however, that you will find lots of C
    listings that contain the second style.

    Finally, because experienced C programmers often make a virtue of saying a
    lot with a little, we point out concise, idiomatic coding styles that you
    are likely to see in program listings from various sources, and we
    sometimes show two or more ways to code a statement.

Chapter 2  Starting with QuickC

    You are now ready to explore the QuickC environment. In this chapter we
    describe the environment, show how to set up QuickC on your computer
    system, present an overview of the QuickC menus and dialog boxes, and help
    you create and run your first QuickC program. We also show how to get help
    from QuickC and how to fix program errors. When you finish this chapter,
    you will be comfortable with QuickC and ready to learn the C language

Our Book and Their Book

    QuickC comes with an excellent user manual that details the mechanics of
    using QuickC. It explains how to configure the system, how to use the
    menus, the meaning of the options in each menu and dialog box, and how to
    use the programs that comprise the QuickC programming tools. The QuickC
    package also includes two reference guides, the Microsoft QuickC Language
    Reference and the Microsoft QuickC Run-Time Library Reference. The first
    guide describes the rudiments of the C language; the second provides
    specific information for using each of the more than 350 C library

    Our book is designed to complement the QuickC user manual by focusing on
    teaching C programming with QuickC rather than rehashing operational
    details from the manual. However, because you need to master QuickC's
    features to write effective C code, we sometimes present a brief overview
    of a procedure or subject and then refer you to the manual for a complete
    discussion. We use this approach particularly when we discuss system setup
    and configuration, for which the manual provides extensive and detailed

    We also do not discuss all the editor commands and keystrokes. You can
    learn these from the QuickC manual and through one of QuickC's excellent
    help screens. However, when we know of some useful tricks that aren't
    covered in the manual, we pass them along immediately.

Directories and Files Used by QuickC

    Programming in C usually involves combining several files to eventually
    form an executable program. These files include definitions of data
    structures and functions (header files), libraries of precompiled
    functions, and your own program code. The QuickC environment uses several
    directories to organize the files into distinct groups, according to
    purpose, such as function libraries, include files, and so on. QuickC also
    uses distinctive filename extensions to identify files that are used or
    created in the compiling and linking process.

Why So Many Files?

    If you use languages such as BASIC or some versions of Pascal, you might
    wonder why QuickC needs such an elaborate system of files and directories.
    With most versions of BASIC, for example, you need only two files: the
    BASIC interpreter program that creates and runs your programs, and the
    file that contains your BASIC program. Although the QuickC environment can
    look quite complicated by comparison, QuickC sets up most of the
    directories and files for you (especially if you have a hard disk) and
    makes it easy for you to move among all the files of a programming
    project. Nevertheless, it is important to understand how QuickC organizes
    files, especially if you need to modify the default organization to avoid
    conflicts with existing directories or for some similar reason.

    To explain the "environment" you work in, we must examine QuickC's
    directories and the files they contain. We use the QuickC default names in
    our discussion, because the actual name and location of the directories
    depend on how you invoke the SETUP program and whether you use a
    floppy-disk or hard-disk system.

Base Directory and Subdirectories

    QuickC installs directories as subdirectories of a "base" directory. If
    you use QuickC by itself, the base directory usually is c:\qc; if you use
    QuickC as part of the Microsoft C 5.0 Optimizing Compiler package, the
    command is usually c:\c5\qc. Thus the actual pathname for the \BIN, or
    base, directory probably is C:\QC\BIN or C:\C5\QC\BIN.

    The most important QuickC directories are \BIN, \INCLUDE, and \LIB. Let's
    look at these and some optional directories that you might find useful.

The \BIN Directory, Compiler, and Linker Programs

    The \BIN directory contains the program QC.EXE, which runs QuickC,
    provides the integrated programming environment, and lets you write,
    compile, link, and execute QuickC programs. (The name "BIN," by the way,
    is short for "binary." The \BIN directory is usually reserved for "binary
    files," or files containing executable programs.)

    The QuickC package actually contains two compiler programs: QC.EXE, which
    comprises the integrated programming environment with its editor, menus,
    and so on; and QCL.EXE, a much shorter program, which generates a
    "command-line" version of the QuickC compiler. (To help you distinguish
    between the programs, think of QCL as "QuickC Line-oriented.") QCL is much
    like the traditional C compiler we described in the Introduction. Rather
    than using menus and dialog boxes, you can compile a program only by going
    to the MS-DOS prompt and typing a command line with options. Another
    program in the \BIN directory, called LINK.EXE, combines your compiled
    programs and stand-alone libraries into a single executable program.
    QuickC usually performs this linking as an invisible process, although
    you can specify linker options when necessary. When you use QCL, you
    control the linker directly with a series of command-line options.

    QC.EXE, with its integrated programming environment, is more convenient to
    use, and we assume in most parts of this book that you will use it to
    compile and run your programs. However, the command-line compiler QCL.EXE
    is very useful for doing what are called batch compilations, for setting
    specific combinations of compile options, for compiling with alternate
    memory models, or for using an alternative program editor with QuickC. QCL
    also lets you use "make" files created with the Microsoft C Optimizing
    Compiler, version 4.0 or 5.0. (Make files are files that keep track of the
    compilation of multiple program modules. QuickC offers an easy-to-use
    alternative called "program lists," which we discuss in Chapter 6.)

The \INCLUDE Directory and Header Files

    The "core" of C is greatly extended by compiler vendors who develop new
    sets of predefined constants, macros, data structures, and functions for
    such areas as graphics, device I/O, and DOS. Some of these are standard
    (proposed ANSI standard or UNIX System V standard) and are found in
    virtually all compilers; others are specific to the IBM PC or to
    Microsoft. The QuickC \INCLUDE directory contains many text files of both
    types. These are known as "include files" because your program can include
    definitions from one or more of these files. (They are also known as
    "header files," because their names must be specified at the beginning, or
    head, of a program.) This is also where you'll put any third-party
    libraries you obtain.

    Include files are not executable files or complete C source programs; they
    are ordinary text files that contain useful function definitions; they
    provide an interface between your program and the compiled code in
    stand-alone libraries. When a program references an include file, the code
    in the include file is inserted into and compiled with the code you
    actually typed in.

    For example, the include file stdio.h contains many of the most commonly
    used input and output functions, and graphics.h contains definitions for
    data structures and functions in the Graphics Library. The following table
    lists the standard QuickC include files. Note that include files have
    filenames with the .h extension. (Don't worry about understanding this
    comprehensive list yet; we will discuss many of them in detail as we use
    them in programs throughout the book.)

    QuickC Include Files
    File       Main Purpose
    assert.h   Debugging expressions
    conio.h    PC-specific console (keyboard) and port (device) I/O
    ctype.h    Character testing and conversion
    direct.h   Creating, removing, and changing MS-DOS directories
    dos.h      Setting and reading 8086 registers for MS-DOS calls
    errno.h    System-wide error numbers
    fcntl.h    Opening MS-DOS files with various modes
    float.h    Implementation-dependent values for advanced floating-point
    File       Main Purpose
    float.h    Implementation-dependent values for advanced floating-point
    graph.h    Microsoft-specific data structures and functions for monochrome
                (MDA), CGA, EGA, MCGA, and VGA graphics
    io.h       Low-level file-handling and I/O routines
    limits.h   Implementation-dependent values for sizes and ranges for data
                types, etc.
    malloc.h   Memory allocation functions
    math.h     Definitions used by math library
    memory.h   Memory manipulation routines (buffer setup, etc.)
    process.h  Used with routines that allow a program to "spawn" (run)
                another program as a "child process"
    search.h   Sorting and searching routines
    setjmp.h   Used for saving and restoring the program state during a "long
                jump" (jump to a different memory segment)
    share.h    Flags controlling sharing of a file among several users (i.e.
                on a network)
    signal.h   Values for "signals" that can be sent to interrupt handlers,
    File       Main Purpose
    stdarg.h   Allows a function to use a variable number of arguments (ANSI
    stddef.h   Miscellaneous constants, types, and variables
    stdio.h    UNIX-compatible standard I/O, such as functions to get and put
                characters to the console or a file
    stdlib.h   Definitions for miscellaneous library functions
    string.h   Definitions for string manipulation functions
    time.h     Data structures used for accessing system time
    varargs.h  Allows a function to use a variable number of arguments

    The \SYS subdirectory of \INCLUDE contains:
    locking.h  Flags for locking files (for networks)
    stat.h     Defines structure used to return status of an MS-DOS file or
    timeb.h    Types used by ftime() (used to get current time)
    types.h    Types used in values returned by functions for time and file
                status information
    File       Main Purpose
                status information
    utime.h    Used by utime() to update access and modification times for
                MS-DOS files

    In QuickC, the \INCLUDE directory also contains a subdirectory called
    \SYS. This subdirectory contains "system specific" include files for IBM
    personal computers and compatibles.

The \LIB Directory and Libraries

    Much of C programming involves writing code that uses standard C functions
    to perform such tasks as getting a character from the keyboard or sending
    a text string to the screen. Microsoft has already compiled these
    functions for you and has placed them in files called "libraries." The
    \LIB directory contains these library files, which have either the
    filename extension .LIB or .QLB. As noted earlier, when QuickC starts, it
    includes in memory the code for a considerable number of commonly used
    functions. In addition, Microsoft provides "Quick Library" versions of
    some libraries, and you can specify that these be loaded as well to
    provide fast, in-memory access. You can also create your own custom Quick
    Libraries. Quick Libraries all have the same extension .QLB.

    If you examine the PACKING.LST file on the QuickC Product disk, you will
    see many libraries with similar names, such as SLIBC.LIB, SLIBFP.LIB, or
    MLIBC.LIB. Why are there so many libraries? The architecture of the Intel
    8086 and 80286 processors used by the IBM PC family requires that memory
    be divided into 64 KB segments. As a result, special instructions are
    needed to access program instructions or data that go beyond a single
    segment. The designers of C compilers address this problem by providing
    programmers with multiple memory models, each containing a different
    allocation of segments for code and data. (QuickC uses compact, small,
    medium, and large memory models. Microsoft C 5.0 adds a "huge" model.)
    Additional libraries handle floating-point (decimal) calculations: Some
    use the 8087 floating-point coprocessor chip, others use software that
    emulates its functions. Also included is an optional graphics library,

    Combined Libraries

    You can use libraries in two ways. When you compile, you can tell the
    linker to include specified libraries (a memory-model library, a
    floating-point library, a graphics library, and so on). Although this is
    most easily done using a "program list," it can involve a bit of
    bookkeeping. The easier way to use libraries is to use the SETUP program
    (discussed later in this chapter), to build one or more combined
    libraries. A combined library is a package that contains one library for
    the floating-point option, one standard library for the specified memory
    model, some general purpose "helper" libraries, and possibly the optional
    GRAPHICS.LIB. The advantage of creating a combined library is that QuickC
    uses it by default, so you don't have to specify library names when you
    compile and link. The \LIB directory contains any combined libraries you
    create with the setup process.

    Note: If you intend to write graphics programs, use SETUP to combine the
    Graphics Library with your standard library. That way, QuickC always
    includes this library in compilations.

The \TMP Directory

    QuickC uses the \TMP directory to store temporary files created during
    compilation. Normally, QuickC removes these files when it finishes with
    them. However, if something "hangs" the system during a compile, you might
    want to check the \TMP library and delete any vestigial files.

The \SAMPLE Directory

    If your computer has a hard disk, the QuickC SETUP program creates a
    \SAMPLE directory and stores in it several example programs. You can use
    these to practice loading, editing, compiling, and running QuickC

The \PROG or \SOURCE Directory

    By default, QuickC stores your programs in the current directory when you
    invoke the compiler. All other files created by the compiling and linking
    process are also stored there. You also can create directories to store
    the source code (the actual program text) for the C programs you write and
    the various files made from your program by QuickC. Although this is
    entirely optional, it makes for a more orderly directory and helps you
    organize and find your programs more easily.

    Whatever your current directory, compiling programs creates the following
    kinds of files, depending on the compiler and linker options you select:

    NAME.C──Source code for the C program name

    NAME.OBJ──Object code produced by the compiler for the C program name

    NAME.MAP──A "map" file showing the addresses used by the linker when it
    linked the program name

    NAME.EXE──The compiled and linked object code for the program name, which
    can be executed by typing name at the MS-DOS prompt

    NAME.MAK──A "make" file containing instructions that QuickC uses to
    recompile or "rebuild" your program if you change it

    Figure 2-1 summarizes our tour of QuickC directories and files. Without
    listing all the QuickC files, the chart shows a typical directory
    structure for QuickC on a hard disk. (The structure of directories on a
    floppy-disk system has several modifications that we will describe in the
    section "Setting Up QuickC for Floppy-Disk Systems" on page 32.)

                    │                     │                     │
                c:\qc\bin             c:\qc\lib           c:\qc\include
            ┌─────────────────┐         ────┬────         ┌────────────────┐
            │                 │             │             │                │
    c:\qc\bin\sample       qc.exe      mlibce.lib     assert.h    c:\qc\include
    ───────┬────────      qcl.exe      slibc7.lib      bios.h     ────────┬────
            │               qc.hlp     graphics.lib    conio.h             │
        cflow.c          link.exe     graphics.qlb     (etc.)         locking.h
    new-conf.sys        lib.exe        (etc.)                        stat.h
    new-vars.bat                                                     (etc.)

    Figure 2-1. Typical directory structure for QuickC.

From Source to Object: An Overview

    Now that we've surveyed the compiler, linker, include files, and
    libraries, let's see how they work together when you run a program with
    QuickC. Let's assume your program uses two include files, stdio.h and
    graph.h. When you "run" or "start" the QuickC compile/link phase, the
    compiler starts by "reading" your source code in the editor buffer. First,
    it sees the instructions to add the include files. The compiler then loads
    the stdio.h file and compiles the code found there. (The code in an
    include file is not already compiled.) Next it loads and compiles graph.h.
    These include files contain, among other things, definitions of functions
    whose compiled code resides in libraries. (The standard library for each
    memory model contains the code corresponding to standard header files such
    as stdio.h; GRAPHICS.LIB contains graph.h.) As it compiles the include
    file, the compiler notes these references to library code and passes them
    to the linker.

    After the compiler generates the object code for the part of the program
    you wrote yourself, the linker "resolves" all library references: It
    extracts the "modules" that contain the necessary code from the
    appropriate libraries and combines them with the rest of the code. The
    result is a compiled object program. QuickC's default creates an object
    program that runs from within the QuickC environment. This enables you to
    run the program immediately after you link it and lets you quickly test
    programs without leaving the QuickC environment. However, you can also
    create a .EXE file, or executable MS-DOS file, that you can run from the
    MS-DOS prompt. Figure 2-2 on the next page summarizes this process

    Program    │ #include stdio.h  │
    references │ #include graph.h  │
    include    │ ────────────      │
    files      │ ────────────      │
                │ ────────────      │
                │ ────────────      │
                        │     Preprocessor     ┌──────────┐
                ┌─────────▼─────────┐            │stdio.h   │
    Included ▒ │ ───────────       │     ┌───── │ ─────    │
    source   ▒ │ ───────────       │     │      │ ─────    │
    code ─── ▒ │ ───────────       │◄────┘      └──────────┘
                │                   │◄────┐      ┌──────────┐
    Your ─── ▒ │ ───────────       │     │      │graph.h   │
    source   ▒ │ ───────────       │     └───── │ ─────    │
    code       └───────────────────┘            │ ─────    │
                        │                      └──────────┘
                        │     Compiler
    Compiled ▒ │ ---------───?     │
    library  ▒ │ ---------───?     │
    references │                   │
                │                   │
    Your ─── ▒ │ ------------      │
    compiled ▒ │ ------------      │
    code       └───────────────────┘
                        │     Linker
                ┌─────────▼─────────┐          ┌─────────────┐
    Final      │ -----------       │          │ Libraries   │
    object     │ -----------       │◄─────────│ (combined   │
    program    │ -----------       │          │     or      │
    (in memory │ -----------       │          │ separate)   │
    or .EXE)   │ -----------       │          └─────────────┘
                │ -----------       │

    Figure 2-2. Compiling and linking with include files and libraries.

Running the QuickC SETUP Program

    Microsoft distributes QuickC on five floppy disks. These disks and their
    hundreds of files contain the two compilers (integrated-environment and
    command-line), a full set of libraries for each memory model with a choice
    of 8087 hardware or emulation, a rich assortment of more than 30 include
    files, several utility programs, and many other goodies. The QuickC SETUP
    program lets you set up a working QuickC environment with directories
    containing only those files that you need and provides automatic access to
    directories as you specify.

    SETUP performs the following operations:

    ■  Sets up variables and commands in the MS-DOS environment that tell the
        operating system where to find all QuickC programs and files

    ■  Sets up a home directory for QuickC, creates the \BIN, \INCLUDE, \LIB,
        and \TMP subdirectories, and moves files from the floppy disks to these

    ■  Creates one or more combined libraries, depending on the memory
        model(s) and form of floating-point support you specify

    Note: SETUP for a floppy-disk system creates only the combined library.
    You must do the rest partly "by hand." See the section "Setting Up QuickC
    for Floppy-Disk Systems" on page 32.

MS-DOS Variables and QuickC

    As we mentioned above, QuickC sets up and uses some MS-DOS commands and
    variables. MS-DOS uses variables (sometimes called MS-DOS "environmental"
    variables) to specify the location of system resources. When you boot an
    MS-DOS disk, the operating system calls on two files to configure the
    system: AUTOEXEC.BAT and CONFIG.SYS. Commands in these files control the
    environment that QuickC uses when you run it.

    When you run the SETUP program for a hard-disk system, QuickC sets
    environmental MS-DOS variables in two files: NEW-VARS.BAT and
    NEW-CONF.SYS. You can use these files as is or insert their contents into
    the AUTOEXEC.BAT and CONFIG.SYS files respectively. We recommend the
    latter procedure unless there are serious conflicts with your existing

    You can use any editor (such as SideKick or EDLIN) to insert NEW-VARS.BAT
    in your AUTOEXEC.BAT file. If you have no AUTOEXEC.BAT, use MS-DOS to
    rename NEW-VARS.BAT as AUTOEXEC.BAT. The resulting file might look like

    setclock────────────────────────────────────────────────Set system clock
    fastopen c:────────────────────────────────────Install file access cache
    sk──────────────────────────────────────────────────────────Run SideKick
    set PATH=c:\;c:\wp;c:\c5\bin;c:\qc\bin;a:\───Combined with your old path
    set INCLUDE=c:\qc\include
    set LIB=c:\qc\lib
    set TMP=c:\qc\tmp

    After you insert the SET commands found in NEW-VARS.BAT, you will probably
    have two PATH= commands in your AUTOEXEC.BAT file. Combine the directories
    in the path provided by SETUP with your existing path, as shown in Figure
    2-3. You can usually use the rest of the SET commands without
    modification. (By default, MS-DOS permits only 128 bytes of space for
    storing MS-DOS variable values.) If this amount proves insufficient,
    modify it as described in the sidebar on the next page.

        AUTOEXEC.BAT               NEW-VARS.BAT
    ┌──────────────────┐     ┌──────────────────────┐
    │ ─────────        │     │set PATH=c:\qc\bin    │
    │ ─────────        │     │set INCLUDE...        │
    │ set PATH=c:\     │     │set LIB...            │
    │                  │     │set TMP...            │
    │                  │     │                      │
    └──────────────────┘     └──────────────────────┘
            │                          │
            │ ───────────                │
            │ ───────────                │
            │ set PATH=c:\; c:qc\bin ◄───┼──── Combined paths from
            │ set INCLUDE=c:\qc\include  │▒    AUTOEXEC.BAT and NEW-VARS
            │ set LIB=c:\qc\lib          │▒─── As is, from
            │ set TMP=c:\qc\tmp          │▒    NEW-VARS.BAT

        CONFIG.SYS               NEW-CONF.SYS
    ┌──────────────────┐     ┌──────────────────┐
    │ ─────────        │     │ FILES=20 ◄───────┼── This is larger, so
    │ ─────────        │     │ BUFFERS=10       │   replace existing FILES
    │ ─────────        │     │                  │   command
    │ FILES=10         │     │                  │
    │ BUFFERS=10       │     │                  │
    └──────────────────┘     └──────────────────┘
            │                          │
            │ ───────────                │
            │ ───────────                │
            │ ───────────                │
            │ ───────────                │
            │ FILES=20                   │
            │ BUFFERS=10                 │

    Figure 2-3. Editing AUTOEXEC.BAT and CONFIG.SYS.

    Here's what the NEW-VARS.BAT commands do. PATH is an MS-DOS command that
    specifies the directories that MS-DOS searches to execute a program.
    Whenever you tell MS-DOS to execute a program on your hard disk (such as
    the QuickC linker or library manager), it first looks in the root
    directory of drive C:, and then checks the specified directories in the
    order they are listed. The next command tells QuickC that include files
    are in the \INCLUDE subdirectory of the main QuickC directory. Similarly,
    the other variables show that libraries are found in \QC\LIB and temporary
    files are in \QC\TMP.

Setting Up QuickC

    Now let's set up the QuickC working environment. The QuickC manual should
    be your source for detailed information about setup procedures and the
    various options involved, but here are "quick start" instructions that can
    simplify the process and probably save you time.

    The basic steps you should follow are:

    ■  Check the PACKING.LST file on the first QuickC distribution disk. Be
        sure you have a complete set of disks and manuals.

    ■  Back up the QuickC disks to floppy disks (use the MS-DOS DISKCOPY
        command to ensure you have an exact copy). Then use the backups during
        the setup process.

    ■  Run the SETUP program.

    Before you run SETUP and before you use QuickC to develop programs, be
    sure that you have at least 448 KB of free memory. QuickC may appear to
    run fine with somewhat less than 448 KB until you try to compile certain

    To verify the amount of free memory, type the CHKDSK command at the MS-DOS
    prompt. To increase the amount of free memory, you might be able to change
    your AUTOEXEC.BAT file so that some memory-resident programs are not
    loaded. Then, reboot to free the memory those programs were reserving.

    Out of Environment Space?
    If your current AUTOEXEC.BAT has many SET commands or a long PATH=
    statement, you might get an MS-DOS "out of environment space" error when
    you add the QuickC variables. If this happens, expand the available
    environment space by putting this command in your CONFIG.SYS file:

    shell=c:\command.com /e:<size>/p

    For MS-DOS versions 3.0 and 3.1, size is the number of 16-byte
    "paragraphs" you want to reserve for the MS-DOS environmental variables;
    for MS-DOS versions 3.2 and later it is the actual number of bytes. The
    default size is 10 paragraphs, or 160 bytes. To set the environment to 256
    bytes, use:

    shell=command.com /e:16/p──────────────────────MS-DOS version 3.0 or 3.1
    shell=command.com /e:256/p───────────────────MS-DOS version 3.2 or later


    If you normally use memory-resident programs or a RAM disk, we recommend
    that you reboot without installing them before running SETUP. The SETUP
    program will fail without at least 385 KB of available RAM. After you set
    up the QuickC environment, experiment with memory-resident programs or RAM
    disks if you wish.

    Setting up a hard-disk system for QuickC differs from setting up a
    floppy-disk system. Therefore, we have developed separate walkthroughs for
    hard-disk and floppy-disk users. If you have a floppy-disk system, skip
    the next section and read "Setting Up QuickC for Floppy-Disk Systems" on
    page 32.

Setting Up QuickC for Hard-Disk Systems

    First, put Libraries Disk #1 in drive A and type the SETUP command. The
    following line is a typical SETUP command:


    The H specifies that your system has a hard disk.

    C:\QC is the pathname of your QuickC "base" directory. By default, QuickC
    creates the following subdirectories under the base directory:

    C:\QC\BIN                     Compiler, linker, and other executable
    C:QC\BIN\SAMPLE               Sample C programs
    C:\QC\INCLUDE                 Include (header) files
    C:\QCINCLUDE\SYS              System-specific include files
    C:\QC\LIB                     Libraries
    C:\QC\TMP                     Temporary files

    Are You Using both QuickC and Microsoft C 5.0?
    If you use both QuickC and the Microsoft C Optimizing Compiler 5.0, you
    can install both compilers on your hard disk without causing any conflict.
    Because both compilers use the same library and include files, and because
    both compilers use the same environment variable names to locate these
    files, you won't have to create separate directories for each compiler's
    library and include files. The only planning and organizational work
    you'll need to do is to organize the compiler files and the source code

    Any program that you can compile with QuickC can be recompiled without
    change by Microsoft C 5.0. QuickC's fast compiler can save time in program
    development, and then the sophisticated optimizations of Microsoft C 5.0
    can speed the execution of your program. Furthermore, QuickC provides
    syntax checking for features unique to Microsoft C 5.0. If a program is
    syntactically correct but uses features of the larger compiler (the huge
    memory model, for instance), QuickC simply ignores those features when you
    run the program.

    Note that SETUP overwrites any file with the same name as a QuickC
    distribution file. If you follow our recommendation to create a new
    directory for QuickC, \QC, in your root directory, you eliminate this

    The M option in the SETUP command lets you use the "medium memory model"
    to compile programs. Note that you can specify other or additional memory
    models when you run SETUP. (See the manual for details.) Although we will
    explain all memory models in later chapters, we use the medium model
    throughout this book because it is the only supported model for programs
    compiled in the QuickC environment. (If you use the command-line compiler,
    which assumes a small model, you might want to create a small model
    combined library that conveniently collects all of the functions you
    normally use with QCL. You can create new combined libraries without
    running SETUP again.)

    The EM in the SETUP command specifies that all floating-point arithmetic
    be performed by software "emulation" of the 8087 math coprocessor chip. If
    you have an 8087/80287/80387 chip in your PC, you might prefer to use the
    87 option which directs floating-point calculations to the coprocessor.
    When QuickC builds your core library, it uses this specification to select
    the appropriate floating-point library. Note, however, that any .EXE file
    you create with the 87 option will not run on machines without a math
    coprocessor. If you are concerned about portability, use EM when you set
    up QuickC. The QuickC environment uses only the emulator; if a coprocessor
    is present the emulator detects that fact and uses it.

    Finally, the GR option specifies that QuickC's Graphics Library functions
    be included in your combined libraries. We recommend that you use this
    option so that you can run the graphics programs in this book without
    specifying the GRAPHICS.LIB every time you link. (Of course, if your
    computer has only a monochrome text display, you should not use this
    option. This installation will proceed, but programs that you subsequently
    create that use graphics will not work.)

    After you enter the initial SETUP command, the program asks you if you
    want to delete the "library subcomponents," or parts of libraries that are
    not needed for the configuration you chose. Unless you plan to use memory
    models or floating-point packages other than those specified in the SETUP
    command, you can save a lot of disk space by typing y at this prompt. The
    SETUP program then prompts you to insert the appropriate distribution
    disks in drive A.

    Without any further input, SETUP creates the QuickC directories, places
    header files in the \INCLUDE subdirectory, creates a "combined library"
    for each specified memory model using the floating-point option you
    selected, and places the combined libraries in the \LIB subdirectory. This
    library is called your "standard library" because it contains compiled
    versions of all the standard C routines (with specified options, such as

    If you have already edited your MS-DOS AUTOEXEC.BAT and CONFIG.SYS files,
    your QuickC environment is now set up and ready to use. Please skip the
    next section, which is for floppy-disk users.

Setting Up QuickC for Floppy-Disk Systems

    Setting up QuickC for a floppy-disk system differs from hard-disk setup in
    two principal ways: First, the floppy-disk setup does not create the
    NEW-VARS.BAT and NEW-CONF.SYS files, so you have to set your own MS-DOS
    variables; second, because you have only 720 KB of disk space on two
    floppy drives, you must be more choosy about which files to install. (If
    you have two 1.4 MB 3.5-inch disk drives, as found in the IBM PS/2 line,
    you need not be so constrained.)

    As explained in the Microsoft QuickC Programmer's Guide, you need to
    format at least two floppy disks: one disk for each memory model and one
    "scratch" disk to hold temporary files created during the setup process.

    Insert your copy of the Libraries Disk #1 in drive A and a blank formatted
    disk in drive B. You are now ready to run SETUP. We recommend that you
    type the following command:

    setup f b: m em gr

    This specifies a floppy-disk setup that places the combined library on
    drive B. The M option lets you use the "medium memory model" to compile
    programs. Although we will explain all memory models in later chapters, we
    use the medium model throughout this book because it is the only model
    supported for programs compiled in the QuickC environment.

    The EM in the setup command specifies that all floating-point arithmetic
    be performed by software "emulation" of the 8087 math coprocessor chip. If
    you have an 8087/80287/80387 chip in your PC, you might want to use the 87
    option instead. When QuickC builds your core library, it uses this
    specification to select the appropriate floating-point library. Note,
    however, that any .EXE file you create with the 87 option will not run on
    machines without a math coprocessor. If you are concerned about
    portability, use EM when you set up QuickC.

    Finally, the GR option specifies that QuickC's Graphics Library functions
    be included in your combined libraries. We recommend that you use this
    option so that you can run the graphics programs in this book without
    specifying the library GRAPHICS.LIB every time you link. (Of course, if
    your computer has only a monochrome text display, you should not use this
    option. The installation will proceed, but programs that you subsequently
    create that use graphics will not work.)

    As it builds the QuickC combined library, SETUP prompts you for the
    necessary disks. The setup process on floppy disks can take as long as 15
    minutes, so don't be alarmed at the seemingly interminable grinding of the
    disk drives. To create library disks for additional memory models or other
    floating-point options, run the SETUP program again.

    Setting Up the MS-DOS Environment

    Because the floppy-disk setup procedure does not create the NEW-VARS.BAT
    and NEW-CONFIG.SYS files, you need to set the MS-DOS variables yourself.
    To do this, add the following two variables to your AUTOEXEC.BAT file:

    set include=a:\include
    set lib=b:

    (If you do not have an AUTOEXEC.BAT, create one and type in the preceding

    This tells QuickC to look for include files in A:\INCLUDE and for
    libraries on drive B.

    Also, edit your CONFIG.SYS so that it assigns values of at least:


    Note that you will have to reboot your system if you are planning on
    running QuickC right away, so the new setting will take effect. Figure
    2-4 summarizes how QuickC is set up and run on floppy disks.

            Drive A                      Drive B
        ┌──────────────────┐          ┌──────────────────┐
        │ QC.EXE           │          │ MLIBCE.LIB       │ ───── Libraries
        │                  │          │                  │
        │                  │          │ QCHELLO.C        │ ▒──── Your source
        │                  │ ◄──────► │ CIRCLE.C         │ ▒     files
        │                  │          │                  │
        │                  │          │ QCHELLO.EXE      │ ▒──── Temporary
    ┌──│                  ◄──┐       │ ────────         │ ▒     and object
    │  └──────────────────┘  │       └──────────────────┘       files
    │                        │
    │  Swapped after startup │
    │  ┌──────────────────┐  │
    └──►                  ├──┘
        │ \INCLUDE         │───── Include files
        │                  │
        │ QC.OVL           │───── Overlay file
        │                  │
        │ QC.HLP           │───── Help screens
        │                  │
        │ LINK.EXE         │───── Linker

    Figure 2-4. Floppy-disk setup for QuickC.

    Differences for Floppy-Disk Users

    The examples in this book assume you have a hard disk with QuickC residing
    in a directory on drive C. Floppy-disk users can use these examples by
    substituting references as follows:

    Hard Disk                            Floppy Disks
    c:\qc\bin                            a:
    c:\qc\include                        a:\include
    c:\qc\lib                            b:

Starting QuickC

    Now we're ready to start using QuickC.

    If you have QuickC on a hard disk and have correctly included \QC\BIN in
    the PATH variable in the AUTOEXEC.BAT file, run QuickC by typing


    at the C> prompt. (If you haven't changed your PATH variable to include
    \QC\BIN, you must change to this directory before you can run QuickC.)

    To use QuickC on a floppy-disk system:

    1.  Boot your system with an MS-DOS disk that contains the new QuickC
        AUTOEXEC.BAT and CONFIG.SYS files

    2.  Put your copy of the Product Disk in drive A

    3.  Start QuickC by typing qc at the A> prompt

    4.  When the QuickC screen appears, replace the disk in drive A with a
        copy of the Work Disk

    Drive A now contains an "overlay" file (this lets QuickC access files
    without further disk swapping), the Help menus, the linker, and the
    \INCLUDE directory.

Improving the QuickC Display

    When you type qc on the MS-DOS command line, QuickC assumes you have a
    color monitor. If you have a monochrome monitor, this default setting can
    reduce the contrast of the characters on your screen and make them hard to
    read. To fix this, exit QuickC by selecting Exit from the File menu, and
    start QuickC in its "black-and-white" mode by typing qc /b.

    If you use a computer that refreshes the screen at a faster rate than
    standard ATs, such as some higher-performance models of COMPAQ computers,
    you can speed screen displays by using the command qc /g to start QuickC.

    If your computer has an EGA card, you can set the screen to display 43
    lines, instead of the normal 25, by starting QuickC with the qc /h
    command. Note that unless you have a high-resolution monitor, text can be
    very hard to read in this mode.

    You can combine these modes by separating them with a space. For example,
    qc /b /g starts QuickC in monochrome mode and accelerates the screen
    refresh rate. You can also put the qc command and options in a batch file
    so you don't have to type them each time you start.

Overview of the QuickC Screen

    If you've used menu-based integrated programming environments such as
    Turbo Pascal and Microsoft QuickBASIC before, the QuickC screen should
    look familiar. (See Figure 2-5.)

    │ Figure 2-5 can be found on p.35 of the printed version of the book.    │

    Figure 2-5. QuickC startup screen.

    Notice the following screen elements:

    ■  The menu bar across the top of the screen lists the following options:
        File, Edit, View, Search, Run, Debug, Calls, and Help.

    ■  The "title bar" displays the name of the program currently loaded into
        the editor. (Because we haven't written a program yet, it now reads

    ■  The main area of the screen, now blank, is the workspace for your

    ■  Two "scroll bars," a vertical one on the right side of the screen and a
        horizontal one near the bottom of the screen, let you use an optional
        mouse to scroll text up and down or side to side.

    ■  The status line at the bottom of the screen keeps track of the name of
        the current program, the status of your program, and the current cursor
        position. Note the Context section of the line. QuickC uses this area
        to remind you of your current stage of program development. Because no
        program is loaded, it reads <Program not compiled>.

Making Selections

    The Microsoft QuickC Programmer's Guide gives exhaustive information on
    how to select menu items, move among parts of a dialog box, accept or
    cancel selections, and so on. Following is a brief and convenient summary
    of this material, explaining both keyboard and mouse commands. The QuickC
    manual also discusses several alternative selection methods you might want
    to explore. To save space and time we show only one method each for mouse
    and keyboard.

    Keyboard Shortcuts ("Hot Keys")

    QuickC lets you select certain frequently used menu items without opening
    the menu first. These "shortcut" or "hot" keys are particularly handy when
    you use the editor. Here are some of the most useful ones:

    Key                      Function
    F2                       Open last file used
    Alt-Backspace            Undo last edit
    Shift-Del                Cut marked text
    Ctrl-Ins                 Copy marked text
    Del                      Clear editor buffer
    F4                       View output screen
    Ctrl-/                   Search for selected text
    F3                       Repeat last search
    Shift-F3                 Find next error
    Shift-F4                 Find previous error
    Shift-F5                 Start program
    F5                       Continue stopped program

    (The Microsoft QuickC Programmer's Guide contains additional

    The Mouse

    Although you can select all QuickC functions from the keyboard, you might
    want to try using a mouse if you have one. With a mouse, you need only to
    point and click to select anything on the screen. Because you don't have
    to learn all the keystroke combinations for making selections or using the
    editor, you can concentrate on learning C right away. Further, the mouse
    makes it easier to select items from a dialog box. You might want to learn
    both the mouse and keyboard methods and see which one best suits you. Or
    you can mix them, using the keyboard for making menu selections and the
    mouse for making selections in dialog boxes, for example.

    You must use a Microsoft mouse or a compatible mouse (such as the IBM PS/2
    mouse or the Logitech serial mouse) with QuickC. Before you can use any
    mouse with QuickC, however, you must install a "mouse driver," either in
    your CONFIG.SYS file or as a .COM file in your AUTOEXEC.BAT. (See your
    mouse documentation for instructions.) The driver is the software that
    lets QuickC recognize the mouse and respond to its movements as though
    they were commands. If you currently use a mouse for other programs, your
    system is probably set up correctly already.

Writing a Program

    Now we're ready to write a simple C program, which we will call QCHELLO.C.
    First, select the File menu. If you have a mouse, move the mouse until the
    pointer on the screen is on the File menu, and click the left button. This
    reveals the menu, as shown in Figure 2-6. Now, move the pointer to the
    Open option, and click the left button again. To reveal the File menu
    using the keyboard, press the Alt key and then press the f key. Notice
    that each menu item has a highlighted letter (often, but not always, the
    first letter in the word or phrase). Type this letter to select the menu
    item. Select the Open option by typing o.

    Note the Exit option in the File menu. Choose this option when you're
    ready to end your QuickC session. If you select Exit after changing your
    current program, QuickC first asks if you want to save the changed
    program. When you exit QuickC, you return to the MS-DOS prompt.

    │ Figure 2-6 can be found on p.37 of the printed version of the book.    │

    Figure 2-6. QuickC File menu.

Selecting a File

    When you select the Open option on the File menu, a dialog box appears.
    (See Figure 2-7.) QuickC uses dialog boxes to obtain the information it
    needs to carry out your request.

    You can select a file from a dialog box in two ways. Notice the long
    rectangle near the top of the dialog box with a cursor blinking in it.
    Typing a filename in this rectangle is the most straightforward method of
    selecting a file. Below is a larger rectangle with some names in it. This
    box lists the contents of the current directory. Names in ALL CAPS are
    directories; names in lowercase are files. (The contents of the current
    directory in your system may vary from those in the example.)

    To make a selection from a dialog box:

    ■  With a mouse, move the pointer to the item you want. Click the left
        button to select the item.

    ■  With the keyboard, use the Tab or back-Tab (shifted tab) key to move
        from one section of the dialog box to another. Press Enter to select
        the item.

    When you select a directory, QuickC lists all files and subdirectories in
    that directory. Each list you display also has a .. entry. Selecting this
    entry moves you back to the parent directory of the directory shown. Thus
    you can easily browse through the file system with only a few keystrokes.

    With the back-Tab or your mouse, move the cursor to the File Name text
    box. Type qchello.c and then press the Enter key. Another small dialog box
    appears to inform you that this file does not exist. Accept the default of
    Yes to create it.

    │ Figure 2-7 can be found on p.38 of the printed version of the book.    │

    Figure 2-7. File "Open" dialog box.

Typing in the Program

    You are now ready to type in a program. QuickC's default mode is in fact
    "edit mode," and the large area of the screen with the cursor in it is the
    Edit window. As you type the listing below, use the arrow keys to move the
    cursor, the Backspace key to make corrections, and press Enter at the end
    of each line. After you enter the text shown in Listing 2-1, your screen
    should look like Figure 2-8.

    /* qchello.c -- a simple C program */

        printf("Hello, and welcome to QuickC!\n");

    Listing 2-1.  The QCHELLO.C program.

What Does It Do?

    Although we won't look at the structure and anatomy of C until the next
    chapter, this program gives you a hint of C style. The first line
    (enclosed by the characters /* and */) is a comment that briefly describes
    the program. It is optional but highly recommended. The word main()
    indicates the beginning of the main function or related group of
    statements in the program. (Most C programs have many functions in
    addition to the main one.) As the name suggests, printf() prints the
    string in the parentheses that follow. The braces, { and }, set off the
    group of statements (only one in this case) that make up the main
    function. So, it's easy to see what this program does: It prints Hello,
    and welcome to QuickC! on the screen. (The \n at the end of the string
    simply moves the cursor to the beginning of a new line.)

    │ Figure 2-8 can be found on p.39 of the printed version of the book.    │

    Figure 2-8. QCHELLO.C as typed into the edit window.


    Running the program is simple. Select the Run menu. As you probably know,
    before we can run our program we must first compile and link it. The Start
    option in the Run menu executes all of these steps for you.

    When you select Start, a dialog box tells you that the program has been
    "modified" and asks if you want to "rebuild" (compile and link) it.
    Whenever you change a program, you must recompile. QuickC treats this new
    program as a changed program, so press Enter or click on Yes to compile

    Before you can blink an eye, QuickC compiles and runs the program. QuickC
    is fast, as you will see when you write longer programs, and because this
    little program doesn't use any include files or libraries, it compiles

    After the program runs, the screen displays the following:

    Hello, and welcome to QuickC!
    Program returned (13). Press any key

    You are now looking at the "output screen." QuickC keeps track of the
    output screen, which always holds the results of your programs, so you can
    switch back and forth between it and the QuickC environment screen. Press
    any key to return to QuickC. For now, don't worry about the return value
    mentioned in the second output line.

Saving the Program

    To save this program to disk for future reference, open the File menu
    again. Notice the Save and Save As options. Select Save to write the
    program to disk. If you want to save the program with a new name, select
    Save As. When the dialog box appears, type the new name and press Enter.
    (You might try QCHELLO2.C.)

Compiling to a .EXE File

    QuickC compiles programs to memory by default. Because it is fast, this is
    often the best way to compile while developing a new program. However, the
    compiled version of a program compiled to memory disappears when you
    compile another program or quit QuickC. Eventually you need a compiled
    version of the program on disk, so you can run it without recompiling.
    Also, you eventually want to create programs that a user can run directly
    from MS-DOS without QuickC available. To produce an MS-DOS-executable
    file, we need to "compile to .EXE."

    Select the Run menu. Now select the Compile item. The dialog box shown in
    Figure 2-9 appears.

    This large dialog box lets you select many options. (We will explain the
    options later as we use them.) Notice the center column, Output Options.
    The small black dot in the parentheses next to the word Memory indicates
    that it is the currently selected output option. We want to change this
    option to Exe. If you have a mouse, move the pointer between the
    parentheses next to Exe and click. From the keyboard, you can move the
    cursor to this position with the Tab and Down Arrow keys and press Enter.
    But there's an even faster way. Note that the letter x in Exe is
    highlighted. To select this item, you need only type the letter x.

    Now you can compile the program. Note the four small rectangles at the
    bottom of the dialog box. The first one, Build Program, has a double
    border, which signifies that it is the default. You can select it in one
    of three ways: tab to it and press Enter, click on it with a mouse, or
    type b.

    The Compile box displays the numbers of the program lines being processed
    as the program is compiled. Because this program is being compiled as a
    stand-alone .EXE file, it must be linked to various disk files.

    Very quickly, the program returns you to the familiar QuickC environment
    screen. Note that the program didn't run and produce output as it did when
    you compiled it earlier. This compile created a .EXE file, and these
    executable files cannot be run directly from QuickC. However, QuickC
    provides an easy way to run it.

    │ Figure 2-9 can be found on p.41 of the printed version of the book.    │

    Figure 2-9. Compile dialog box.

Escaping to MS-DOS

    At the File menu, select the item DOS Shell. This option switches the
    display to the output screen where the MS-DOS sign-on message and prompt
    appear. You can now run any MS-DOS command, as well as most programs and
    batch files.

    To run QCHELLO.EXE, type:


    The screen displays the expected output. (No instruction to press a key
    appears, of course, because QuickC is not running. We are at the MS-DOS

    Now type:


    to return to QuickC exactly where you left off.

Getting Help

    We will not cover every feature of QuickC in this book so that we can
    devote more time to C itself. Although we occasionally refer you to the
    QuickC manual, there's another source of help as near as your keyboard──
    the QuickC Help facility. In fact, you can select from three levels of
    help: general, topic, and keyword.

General Help Screens

    Press the F1 key to select the General help option (or use the mouse to
    make the selection). The first screen you see is shown in Figure 2-10.
    Notice that it displays a summary of some editor commands as well as some
    other frequently used commands. The small rectangles at the bottom of the
    dialog box let you select the Next or Previous help screen. Next, with its
    double border, is the default. Press Enter or click on the box with the
    mouse to display the next screen. Don't try to memorize or even understand
    these screens. Just get an idea of the general information that is
    available for future reference.

Topic Help

    If you select Topic help, you can page through lists of topics until you
    find the information you are looking for. (See Figure 2-11.) For example,
    you could select "preprocessor directives," and then select the particular
    directive for which you want help. To choose Topic help directly from the
    editor window, press Shift-F1.

    │ Figure 2-10 can be found on p.43 of the printed version of the book.   │

    Figure 2-10. A QuickC help box.

    │ Figure 2-11 can be found on p.43 of the printed version of the book.   │

    Figure 2-11. Topic help.

Keyword Help

    Return to the editor window, and move the cursor to the word printf.
    Suppose you are writing a program and you are not sure how this C function
    works. By pressing Shift-F1, you can retrieve information about the C
    keyword or standard function currently marked by the cursor. (See Figure

    │ Figure 2-12 can be found on p.44 of the printed version of the book.   │

    Figure 2-12. Keyword Help screen.

Fixing Errors

    The last section of this chapter discusses how to fix errors in a C
    program. The QCHELLO.C program should still be in the Edit window. Let's
    make some changes in the program so we can practice fixing errors.
    (Normally, we programmers don't have to manufacture errors; we run into
    enough of them on our own!)

    Use the arrow keys or the mouse to move the cursor to the word printf.
    Change it to primtf. Next, go to the end of the line and delete the

    Now select Run and Start to compile and run the program. QuickC soon
    displays a rectangular error window at the bottom of the screen as shown
    in Figure 2-13.

    The error message tells you that a semicolon is missing before the closing
    }. Notice on your screen that the cursor in the edit window is on the
    character immediately following the error. This makes it easy to find and
    correct the error. (In this case, the next character is on the next line,
    however, so you have to move the cursor to the end of the preceding line
    to insert the semicolon after the ).

    │ Figure 2-13 can be found on p.45 of the printed version of the book.   │

    Figure 2-13. Error window.

    Now run the program again. The next error message, `primtf' : unresolved
    external, is less clear than the preceding one. Simply stated, it means
    that primtf is not one of the standard QuickC functions. When you change
    the m back to an n, the program again runs correctly.

Preparing for the Next Chapter

    In the next chapter we begin our study of the elements of the C language.
    Although we discuss additional QuickC features as needed, we will not
    concentrate on using the QuickC environment. So now is a good time to get
    comfortable with your new QuickC environment.

    We recommend that you try the following:

    ■  Save QCHELLO.C under another name, and then use the Open option in the
        File menu to load it into the editor.

    ■  Practice compiling and running the program to memory and to a .EXE

    ■  Use the DOS Shell item of the File menu to exit to MS-DOS, run a .EXE
        program, and then use Exit to return to QuickC.

    ■  Make some errors in QCHELLO.C and try running the program. Observe the
        error messages, fix the errors, and run the program again. What happens
        if the last } is missing? What happens if you change the word "Hello"
        to "Hi"?

    ■  Read Chapter 6 in the Microsoft QuickC Programmer's Guide to learn
        about the advanced features of the editor. We suggest you study them
        when you want a break from reading this book. None of these editor
        features are needed for you to use this book, but they make it easier
        to enter and modify long programs. Remember to use the Help screen to
        remind you of common editing functions.


Chapter 3  C Fundamentals

    Now that you feel comfortable in the QuickC environment, we can turn our
    attention to the fundamentals of C. First, let's look at the basic
    elements of a C program.

Basic Elements of C Programs

    The simplest possible C program, which we call TINY.C, is shown in Listing
    3-1 on the following page. Type this program into the QuickC editor; then
    run it with the Start option from the Run menu. (We recommend that you
    enter and run all sample programs in this book──we believe this will help
    you better understand and remember the concepts we discuss.)

    As you probably suspected, this program doesn't actually do anything when
    you run it. QuickC generated the message Program returned 1. Press any
    key, but the program produced no output at all. The main() function
    returns the value 1, in this sample, to the operating system. (The actual
    value might be different on your machine.) This value is significant only
    if you control it deliberately, as you might want to do when you call a C
    program from another program, for example.

    /* tiny.c -- the smallest possible C */
    /*           program with comments   */

    main() /* function name and argument list */
            /* function definition in braces */

    Listing 3-1.  The TINY.C program.

A Program Consists of Function Definitions

    As simple as it is, however, this program illustrates a basic element of
    C──A C program is essentially a set of function definitions. A function
    contains statements (instructions) that the program "calls" to perform
    specific tasks. A function definition must contain at least the following

    ■  The function name

    ■  An "argument list" enclosed in parentheses

    ■  A group of statements that define the function

    In practice, and especially with programs written in the new ANSI C
    standard, function definitions can be more complicated than this. But this
    simplest definition is all we need until we look at functions in more
    detail in Chapter 6.

    TINY.C has only one function, main(). The argument list, which follows the
    function and is enclosed in parentheses, often contains "parameters," or
    formal descriptions of information, that the function uses when it is
    called (executed). Although an argument list can also be empty, as it is
    in main(), the parentheses are still required. Because main() contains no
    function definition statements, the program does nothing when you run it.

    The QCHELLO.C program we developed in the last chapter is an even better
    example of the elements of a C program. Figure 3-1 identifies the parts
    of QCHELLO.C.

                Function name               Argument list
                    │                         │
                    └──────────┐   ┌──────────┘
                            main ( )
                            │ {
    Function definition  ───┤     printf("Hello, and welcome to QuickC!\n");
    enclosed in braces      │ }   │                                        │
                            └─    └──────────────────┬─────────────────────┘
                                        Statement in function definition

    Figure 3-1. Parts of the QCHELLO.C program.

A Function Definition Consists of a Group of Statements

    In C, a pair of braces ({ and }) encloses a group of statements. Notice
    the part of the program between the braces in Figure 3-1. The statement
    here defines the function main(). All stand-alone C programs begin with
    main(). The statements within braces are sometimes called the "function
    body," to distinguish them from the function name and argument list, which
    together form the "function header."

    The function body can consist of any number of program statements. Note,
    however, that the braces are still required even if the definition
    contains no statements. Think of braces as symbols that delimit
    "paragraphs" of C code.

    A Statement Is like a Sentence

    A statement in C consists of keywords, variable and function names, and
    operators, and, like an English sentence, describes a complete action.
    Statements always end with a semicolon. Below are some example C
    statements and their meanings:

    printf("This is a statement");─────────────────Print This is a statement
    count = 1;───────────────────────────────────Set the variable count to 1
    getche(ch);───────────────────────────Wait for user to type a character,
                                            assign it to the variable ch, and
                                            echo (display) it on the screen

    QCHELLO.C has only one statement, printf("Hello, and welcome to
    QuickC!\n"); this statement translates as "Print the string `Hello, and
    welcome to QuickC!' and then go to the next line." (The \n specifies a
    newline character that moves the cursor to the next line.) This statement
    completely defines the function main() and describes what happens when the
    program executes the function.

    A Statement Can Contain Expressions

    Can an expression, such as count + 2, be a statement? Well, it doesn't end
    with a semicolon. But more importantly, it is not a complete statement.
    The word and number merely express a quantity ("two more than the value of
    the variable count"): They don't do anything with the quantity.

    Although an expression by itself is not a statement, it can be an
    important element of a C statement. For example, count = count + 2; is a
    complete C statement that assigns the quantity of the expression to the
    variable count.

    A Statement Can Call Functions

    Let's look at QCHELLO.C in more detail. (See Figure 3-2 on the following
    page.) What exactly is the printf() function at the start of the statement
    that defines the main() function? If you know BASIC, you might say, "It's
    the command you use to print in C." This isn't really correct, however. In
    BASIC, PRINT is a built-in BASIC command (or keyword) that prints a string
    or number. In C, printf doesn't execute a built-in command; it calls a
    function named printf() and gives ("passes") it an argument (or parameter)
    that tells it what to print.

                Function name               Argument list
                    │                         │
                    └──────────┐   ┌──────────┘
                            main ( )
                            │ {
    Definition of main() ───┤     printf ("Hello, and welcome to QuickC!\n");
    function in braces      │ }     │     │                                 │
                            └─      │     └───────────┬─────────────────────┘
                                    │                 │
                                Name of    Argument list (string to print)
                                being called

    Figure 3-2. Parts of QCHELLO.C revisited.

    Compare the printf() statement with the line containing main(). Both
    consist of a name followed by parentheses: that is, a function name and an
    argument list──the list for main() is empty. (Note that when we show
    function names in text, we use a trailing set of parentheses to
    distinguish them from other C elements.)

    The main() function name with its empty argument list are followed by a
    pair of braces that enclose the function definition. (You'll notice in
    QCHELLO.C that no semicolon follows main() because the line isn't a
    complete statement: It's the header for the function definition that
    follows.) The line with printf(), however, needs no defining group of
    statements because we are not defining printf() here; we're merely using,
    or "calling," the function in a statement. To call a function, simply use
    its name and argument list in a statement. We refer to statements such as
    the printf() line as "function calls."

    Always remember that every function must be defined before you can call
    it, otherwise QuickC would not know what statements to use when it tries
    to compile the function name. So where is the definition of the printf()
    function we called in Figure 3-2? The printf() function is a "core
    library function." Its definition is built into QuickC so that your
    program always has access to it. When you link your program, QuickC
    inserts the appropriate machine code for printing.

    Quick Tip
    If you know Pascal, you recognize the use of the semicolon to end
    statements in C. However, there is one important difference between its
    use in C and in Pascal. In Pascal, the semicolon can be omitted if the
    statement is the last statement in a group (the statement before the word
    END). In C, every statement ends with a semicolon.

    Also notice that the braces in C serve the same function as the Pascal
    keywords BEGIN and END: They delimit a group of statements.

    We stress the difference between C's library functions and the built-in
    commands of some other languages to emphasize the all-important role that
    functions play in C. C makes no distinction in syntax between QuickC
    library functions, such as printf(); functions that you define yourself,
    such as main(); and C header files developed by Microsoft or other

The Flow of Execution Starts with main()

    When you run a C program, execution always begins with the function named
    main(), which must be present. What QuickC executes next depends on the
    functions that main() calls in its definition. In QCHELLO.C, execution
    starts with main(). In the definition of main(), QuickC encounters the
    name printf() and executes that function.

Punctuation and Spacing in C Programs

    Generally speaking, QuickC lets you break lines of code almost anywhere or
    insert many spaces (or none) between program elements. For example, you
    could rewrite the QCHELLO.C program as:

    main(){printf("Hello, and welcome to QuickC!\n");}

    or, at the other extreme, you could add line breaks to produce the
    NARROW.C program shown in Listing 3-2. There are, however, some
    exceptions to C's tolerance of white space and "free-form" syntax. You
    can't split a function name across two lines because the compiler reads
    the newline character at the end of the line as part of the function name.
    Also, you can't break a quoted string, such as the "Hello, and welcome to
    QuickC!" in our printf() statement, between two lines because the compiler
    won't let you use the newline character in a "string constant" (although
    you can specify a newline with the escape sequence \n, as we have seen).

    Because C is a somewhat cryptic language, you should use spacing and
    alignment of code to make it easier for other programmers to read and
    revise your programs. (Remember, after a few weeks you, too, are "another
    programmer" when you look at your code.) You'll also find that aligning
    braces vertically helps you avoid errors: The vertical alignment lets you
    easily match beginning and ending braces.

    /* narrow.c -- a choppy c program */

    ("Hello, and welcome to QuickC!\n");

    Listing 3-2.  The NARROW.C program.

Using Comments in C

    Listing 3-1 on p. 50 contains several lines or parts of lines that begin
    with /* and end with */; for example:

    /* tiny.c ── the smallest possible C program */

    These lines are comments, or nonexecuting remarks, that explain how a
    program works. We strongly encourage you to use comments in your programs;
    they make the program much easier for a reader to understand. Because
    QuickC ignores comments, they can follow a program statement on the same
    line or cover many separate lines. The program examples in this book have
    an introductory comment, and we insert other comments where appropriate.

    Below are several different styles you can use for comments:

    /* Comment line one */
    /* Comment line two */


    /* Comment line one
    comment line two */


    /* Comment line one
    /* Comment line two
    /* Comment line three */

    However, you can't insert a comment within a comment as follows:

    /* Comment line one
    /* Nested comment line two */
    Comment line three */

    The reason you can't "nest" comments is that once the compiler sees the
    beginning of a comment (the /*), it considers everything that follows
    (including another /*) to be part of the comment until it sees */. In the
    nested comment above, the compiler considers the comment ended at the */
    after the word two. It then treats the word Comment on the next line as an
    undefined function or variable name.

    Quick Tip
    Many versions of Pascal use both /*...*/ and {...} to enclose comments. In
    C, you can never use braces for comments: They serve only to begin and end
    groups of statements.

Data Types and Declarations of Variables

    Variables are names for memory storage areas used by a program. Variables
    come in many shapes and sizes. Many BASIC programmers get along reasonably
    well using only two types of variables: numeric (representing a number)
    and string (representing a series of characters). A BASIC programmer might


    to define two variables. The $ at the end of ITEM signifies a string
    variable; its absence in SERIAL specifies a numeric variable. A BASIC
    interpreter sets up these variables "on the fly" as it analyzes the lines
    of code, without storing them in a particularly efficient way.

    With C, the situation is more complicated. In order to use computer memory
    more efficiently, the C compiler reserves a specific location in memory
    for each variable. To do this efficiently, it needs to know exactly how
    many bytes of storage to use and how to store the data in those bytes.
    Therefore, C uses many "data types" to specify such things as the range of
    numbers that a variable can hold, whether negative values should be
    accommodated, whether values can be integers only or include decimal
    fractions, and so on. If you are a BASIC programmer, this constant
    attention to data types takes a little getting used to. However, by the
    end of this chapter, you will know all the available types and when each
    should be used.

    Let's begin our survey of data types by considering some different types
    of data we might store in variables:

    ■  30 (the number of students in a class)

    ■  557,617,814 (number of seconds since a date in 1970)

    ■  22.95 (price of a computer book?)

    ■  1,000,000,000,000.00 (future U.S. budget?)

    ■  a (the letter a)

    As you probably know, data is stored in a computer as patterns of bits: 1s
    and 0s, "ons" and "offs." In the IBM PC family of computers, bits are
    organized in groups of eight (called bytes), or in groups of two bytes
    (called words), or in groups of four bytes (double words), depending on
    the operation involved and the processor used. Figure 3-3 on the
    following page shows how many bytes are needed to store the different
    sizes and kinds of numbers in the above list. The figure also shows the
    name of the data type that describes the storage involved. The addresses
    shown are arbitrary, but they demonstrate how successive items are stored
    with lower addresses.

    The QuickC sizeof operator returns the number of bytes that a given data
    type uses. The program VARSIZE.C (see Listing 3-3 on the following page)
    uses this operator and a series of printf() statements to print out the
    sizes (in bytes) of the following data types: char, int, long, float, and

                                        ADDRESSES   DATA                  TYPE
    Stored          ┌──────────────┐
    "downward"        │              │ 5003
    in memory         ├──────────────┤                                   char
        │      1 ─── ▒│              │ 5002 ▒ ──── a
        │     byte    └──────────────┘
        │             ┌──────────────┐
        │            ▒│              │ 5001 ▒
        │      2 ─── ▒├──────────────┤      ▒ ──── 30                    int
        │    bytes   ▒│              │ 5000 ▒
        ▼             └──────────────┘
                    ▒│              │ 4999 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4998 ▒
                4 ─── ▒├──────────────┤      ▒ ──── 557,617,814           long
            bytes   ▒│              │ 4997 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4996 ▒
                    ▒│              │ 4995 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4994 ▒
                4 ─── ▒├──────────────┤      ▒ ──── 22.95                 float
            bytes   ▒│              │ 4993 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4992 ▒
                    ▒│              │ 4991 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4990 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4989 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4988 ▒
                8 ─── ▒├──────────────┤      ▒ ──── 1,000,000,000,000.00  double
            bytes   ▒│              │ 4987 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4986 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4985 ▒
                    ▒├──────────────┤      ▒
                    ▒│              │ 4984 ▒

    Figure 3-3. Storing information in memory.

    /* varsize.c -- shows amount of memory */
    /*              by various types       */

        printf("Size of a char in bytes is %d\n", sizeof(char));
        printf("Size of an int in bytes is %d\n", sizeof(int));
        printf("Size of a long in bytes is %d\n", sizeof(long));
        printf("Size of a float in bytes is %d\n", sizeof(float));
        printf("Size of a double in bytes is %d\n", sizeof(double));

    Listing 3-3.  The VARSIZE.C program.

    Here's the output of VARSIZE.C:

    Size of a char in bytes is 1
    Size of an int in bytes is 2
    Size of a long in bytes is 4
    Size of a float in bytes is 4
    Size of a double in bytes is 8

Declaring Variables

    To declare a variable, specify the data type and then the variable name.
    Here are some examples:

    int account_no;
    float balance;
    double budget;
    char acct_type;

    The first statement declares account_no as an integer (int) variable. The
    remaining statements declare variables as floating-point decimal (using
    the keyword float), "jumbo" 8-byte floating-point (double), and 1-byte
    character (char) data types.

    When you declare a variable, QuickC sets aside the appropriate number of
    bytes and notes the variable's starting address. The next program,
    VARADDRS.C (Listing 3-4), declares several types of variables and then
    prints out their starting addresses.

    /* varaddrs.c -- uses & operator to get   */
    /*               addresses of variables   */

        char c1, c2;
        int i;
        long l;
        float f;
        double d;

        printf("Address of c1 %d\n", &c1);
        printf("Address of c2 %d\n", &c2);
        printf("Address of i  %d\n", &i);
        printf("Address of l  %d\n", &l);
        printf("Address of f  %d\n", &f);
        printf("Address of d  %d\n", &d);

    Listing 3-4.  The VARADDRS.C program.

    Although the output of this program varies with different system
    configurations, it should look something like this:

    Address of c1 6146
    Address of c2 6144
    Address of i  6142
    Address of l  6138
    Address of f  6134
    Address of d  6126

    VARADDRS.C obtains the addresses of the variables by using an ampersand
    (&) prefix with each variable name. The ampersand is the "address
    operator," and it returns the starting address for each variable
    specified. Compare the output of VARADDRS.C with Figure 3-3 to see how
    variables declared with different data types require different amounts of
    memory. When QuickC allocates the required number of bytes for a declared
    data type, the last byte allocated (moving downward in memory) is the
    variable's starting address. For example, the integer variable i has an
    address of 6142, indicating that it uses two bytes (6144 - 6142 = 2); the
    double type variable d uses eight bytes (6134 - 6126 = 8). Note that the
    compiler allocates two bytes for char values c1 and c2, although each
    value requires only one byte. The extra byte is convenient for
    manipulating (2-byte) words in memory.

Rules for Naming Variables

    In C, the names of variables and functions are called "identifiers." An
    identifier can contain any uppercase or lowercase alphabetic characters
    (A-Z or a-z), digits (0-9), and the underscore character (_). However, the
    name must begin with a letter or underscore. Below are some examples of
    legal and illegal names:

    BigNum───────────────────────────────────Legal, and distinct from bignum
    _video───────────────────────────────Legal, can begin with an underscore
    bal_due─────────────────────────Legal, underscore used to separate words
    player2───────────────────────────────────Legal, number in variable name
    8ball─────────────────────────────────Illegal, can't begin with a number
    tally-ho!─────────────────Illegal, contains hyphen and exclamation point
    int───────────────────Illegal, keyword reserved for name of integer type

    As you can see, you have considerable flexibility in choosing names for
    your variables. Because QuickC can distinguish the first 31 characters of
    a variable name, you can use long, descriptive names that help make the
    program easier to understand and modify. (You might want to use shorter
    names if your program must run a compiler that does not support long
    names.) C distinguishes between uppercase and lowercase characters, so
    that BigNum and bignum are different variables. Note that you can't begin
    a variable name with a number, use punctuation marks such as ! or $, or
    use C-language keywords as variable names. (You can embed a keyword in a
    variable name, however: interest is a legal name even though it contains
    the keyword int.) Fortunately, C has few keywords compared to languages
    such as BASIC: Most specify data types (such as int) or control and
    decision-branching operations (such as while and if).

    We use specific conventions for naming variables and functions. (See
    "Conventions and Style" in Chapter 1.) These are not required by QuickC,
    but are used here to differentiate among types of variables and functions.
    We also begin our variable names with a character other than an
    underscore──Microsoft uses the underscore as the initial character for its
    QuickC library functions.

Assignment Statements

    How do you assign values to variables? In C, the simplest assignment
    statement consists of a variable name followed by an equal sign (=) and
    the value to be assigned. Below are some examples:

    a = 5;
    b = a + 5;
    c = a + b;

    In these assignment statements, the value to the right of the equal sign
    is assigned to the variable on the left. The value can be a number or an
    expression involving variables and/or numbers, such as a + 5 or a + b. If
    the value is an expression, QuickC determines the result and then assigns
    it to the variable.

    You can also assign the same value to several variables at once. Usually,
    you do this to initialize variables by setting them all to 0 or 1:

    line_count = word_count = 0;
    line_no = page_no = 1;

Initializing Variables

    Many languages (including most versions of BASIC) automatically initialize
    numeric variables to 0 and character variables to blank or, perhaps,
    "null." C does not. For example, if your program has the following two

    int length;
    printf("The length is %d\n", length);

    and you do not initialize length, it might produce the following output:

    The length is -25480

    The default value of a C variable is whatever pattern of bits happens to
    be in the memory locations of the variable when the compiler assigns them.
    Therefore, if you want to use a variable called total, for example, in a
    program that keeps track of some quantity, you should assign that variable
    an initial value of 0. You might modify the declaration above as follows:

    int length = 0;

    Because C is a concise language, it lets you combine the declaration and
    assignment of a variable. That is, you can declare the data type, the
    variables, and their values in the same statement:

    int a = 10, b = 50, c = 100;

Type int

    Now that you know how to declare numeric variables and assign values to
    them, let's look at the int, or integer, data type more closely. An
    integer is a whole number, such as 30, -5, or 93,000,000. In QuickC, an
    int variable can hold numbers in the range of -32,768 through 32,767. This
    rather odd-looking range is established because the int type uses two
    bytes (16 bits) of memory. Two bytes can actually hold a range of 0
    through 65,535. But, in the regular int type, the high (leftmost) bit of
    the 2-byte combination stores the integer's sign (positive or negative),
    leaving only 15 bits for the number.

    If your variable will never store negative integers, use the unsigned int
    type. Because the sign bit is not used, you can use the full two bytes to
    store values from 0 through 65,535.

    Now let's look at the INTVARS.C program (Listing 3-5), which declares
    three integer variables, assigns values to them, and then prints out
    values that describe the World War II German battleship Bismarck.

    We declare the variables length and beam as int types because the length
    and beam (width) of the ship are less than 32,767 feet. For the
    displacement variable (the "weight" of the ship), we use the unsigned int
    type because we need a larger number (41,676) than 32,767 (the int limit)
    but a smaller number than 65,535 (the unsigned int type limit).

    The next three lines assign the values to the variables, and the three
    printf() statements print the values out. Notice that the printf()
    statements use two arguments within the parentheses: a string, such as
    "The battleship Bismarck was %d feet long", followed by a comma and the
    variable name whose value is to be printed. The %d in the string is a
    printf() "format specifier," and the value of the variable is printed in
    its place. (The %d specifier denotes a decimal [base 10] integer. C uses a
    variety of specifiers for different types and formats of numbers and
    characters. We'll discuss them when we look at printf().) When you run
    INTVARS.C, it generates the output that appears below the listing (on the
    following page).

    Quick Tip
    ANSI C lets you specify any basic variable type as unsigned. It also lets
    you specify signed types. Therefore, although QuickC considers the int
    type to be signed by default, the C language doesn't guarantee that all C
    compilers do so. To write portable programs, you need to specify all
    variables as either signed or unsigned types.

    /* intvars.c -- declares, defines, and prints */
    /*              some integer variables        */

        /* declare variables */
        int length, beam;
        unsigned int displacement;
        /* assign values to variables */
        length = 824;
        beam = 118;
        displacement = 41676;

        /* print out values */
        printf("The battleship Bismarck was %d feet long",
        printf(" with a beam of %d feet,\n", beam);
        printf("and displaced %u tons.\n", displacement);

    Listing 3-5.  The INTVARS.C program.

    The battleship Bismarck was 824 feet long with a beam of 118 feet,
    and displaced 41676 tons.

Long Integer Type

    We've seen that unsigned int variables can hold values to 65,535. But what
    if you must use larger numbers? Type long uses four bytes (32 bits) of
    memory (1 bit is reserved for the sign), and can store numbers from -2^31
    to +2^31, or -2,147,483,648 to 2,147,483,647 in base 10. Once again, if
    your variable will contain only positive numbers, you can double the high
    end of this range by specifying unsigned long. This lets you assign your
    variable a whole number value in the range 0 through 4,294,967,295.

    The SCORE.C program (Listing 3-6 on the following page) combines the
    declaration and assignment of the int variables home, visitors, inning,
    and attendance. Because total_attendance is a different data type, long,
    you must declare it in a separate statement. Again, the printf()
    statements display the values assigned to the variables and produce the
    following output:

    The score after 7 innings is
    Home team 5, Visitors 2.

    The attendance today is 31300.
    Attendance this year to date is 1135477.

    /* score.c -- defines and prints   */
    /*            int and long vars    */
        /* declare some int variables and assign values */
        /* to them in the same statement                */

        int home = 5, visitors = 2, inning = 7, attendance = 31300;
        long total_attendance = 1135477;  /* long int */

        /* print out the values */

        printf("The score after %d innings is \n", inning);
        printf("Home team %d, Visitors %d.\n\n", home, visitors);
        printf("The attendance today is %d.\n", attendance);
        printf("Attendance this year to date is %ld.",

    Listing 3-6.  The SCORE.C program.

Floating-Point Types

    You should store whole numbers as integers wherever possible──integers use
    the least amount of memory and integer arithmetic is fast. However, many
    numbers (such as dollars-and-cents amounts) require decimal fractions. In
    computers, these types of numbers are stored in "floating-point format."

    Consider the number 22.95. This number can be stored by dividing it into
    two parts: the digits themselves and an exponent showing the magnitude of
    the number in terms of powers of ten. Thus, 22.95 could be represented as
    22.95 * 10^0. For uniformity in performing operations, however, C always
    expresses the digits with only one digit to the left of the decimal point.
    Therefore, the above number is actually stored as 2.295 * 10^1 (the same
    as 22.95 * 10^0). C represents this notation with the expression
    2.295e+001. The first element, 2.295, is the number's digits (the
    "mantissa"), and the e+001 represents "exponent 1," or 10^1.

Type float

    The most commonly used floating-point type in C is float. In QuickC, type
    float uses three bytes to store digits (the mantissa) and one byte to
    store the exponent. Because exponents can be negative (for example,
    1.4e-002 = .014), one bit of the exponent byte stores the sign. Converted
    into decimal terms, this means you can store a mantissa with seven
    significant digits and an exponent ranging from -38 to +38. In fact, with
    QuickC's float type, you can store numbers as large as 3.4e+038, or 34
    with 37 zeros after it.

    The FLOATS.C program (Listing 3-7) displays three float values, each
    printed in both traditional decimal and exponential notation.

    /* floats.c  -- shows floating values in regular */
    /*              and exponential format           */

        float f1 = 2500.125, f2 = 0.0033, f3 = -50.99;

        printf("%f\t %e\n\n", f1, f1);
        printf("%f\t %e\n\n", f2, f2);
        printf("%f\t %e\n", f3, f3);

    Listing 3-7.  The FLOATS.C program.

    The following is the output of the FLOATS.C program:

    2500.125000      2.500125e+003
    0.003300         3.300000e-003
    -50.990002       -5.099000e+001

    Notice that because 0.0033 is less than 1, it has a negative exponent
    (represented by the minus sign after the e). -50.99, on the other hand, is
    a negative number, but because its absolute (unsigned) magnitude is
    greater than 1, it has a positive exponent. FLOATS.C prints each variable
    first in decimal notation and then in exponential notation by varying the
    format specifier in the printf() statement: The %f produces traditional
    decimal format, and the %e produces exponential format.

Type double

    For numbers larger than


    QuickC provides a "jumbo" floating-point type called double (double
    float). It uses eight bytes of storage and has a range of (plus or minus)
    1.7e-308 to 1.7e+308. That's 308 decimal places before or after the
    decimal point, thus accommodating even the most expansive physicist or

    Type Variations on Different Machines
    The C language doesn't define the number of bytes used by the int and
    unsigned int types. Instead, the number of bytes is based on the size of
    number a particular processor can handle in a single operation. This way,
    C compilers can always take advantage of a machine's architecture. Because
    the IBM PC uses the Intel 8086, 8088, or 80286 processor, an int uses two
    bytes, or 16 bits, and this is the implementation QuickC uses. However, on
    larger personal computers, such as those using the Intel 80386 processor,
    and on many minicomputers and mainframes, an int uses four bytes, or 32
    bits. Even if you write your program in "standard" C, you must be aware of
    these differences in implementation and machine architecture when you
    "port" the program to another machine.

Precision for Floating-Point Numbers

    You must consider more than size, however, when storing numbers in a
    computer. We referred to a trillion-dollar budget ($1,000,000,000,000.00)
    earlier in the chapter. If size were the only consideration, we could use
    float to store this number. (A float can handle about 10^38, and a
    trillion is merely 10^12.)

    However, you also must consider the precision available to each data type
    in order to choose the right type for a given variable. Precision refers
    to the number of digits guaranteed to be exactly correct after a
    calculation. The float type has a precision of seven digits. Consider the
    following statements and the resulting output:

    float trillion = 1000000000000.00;
    printf("%f\n", trillion);


    We lost $4,096.00 in this operation. Although we might be happy if the
    government lost only that much of a trillion-dollar budget, we must expect
    full precision in financial calculations and probably an even higher
    precision in most scientific calculations. With its seven-digit precision,
    float can't accurately represent a trillion dollars. We attain the
    required precision by declaring:

    double trillion = 1000000000000.00;

    Because double has 15-digit precision, the result is completely accurate.

Type char

    Let's look at one last data type, char (character). Characters include the
    uppercase and lowercase letters, numerals, punctuation marks, and
    nonprinting control characters. Characters on most computers, including
    the IBM PC, are represented by numbers between 0 and 127, according to the
    ASCII code. The CHARS.C program (Listing 3-8) shows some examples.

    Running CHARS.C produces the following output:

    The character A has ASCII code 65
    If you add ten, you get K
    The character a has ASCII code 97

    The first line of the main() function declares two char type variables,
    ch1 and ch2, and assigns them the values of `A' and `a' respectively. The
    `A' and `a' are called "character constants" or "character literals," and
    you assign them to char variables the same way you assign numeric
    constants. (Note that you must use single quotes around the character

    Consider the first printf() statement in the program:

    printf("The character %c has ASCII code %d\n", ch1, ch1);

    The variable ch1 is specified twice at the end of the argument list. The
    first format specifier, %c, prints the value of ch1 as a character. Then
    the %d specifier prints ch1 as an integer. A character is actually stored
    as a 1-byte version of int, and unless you specify that QuickC treat it as
    a character, it is treated as an integer. This enables us to use the
    expression ch1 + 10 in the second printf() statement. The variable ch1
    contains an integer value (the ASCII code for `A', or 65), so adding 10 to
    it produces 75. When the %c specifier then prints this value, it displays
    the character with the ASCII value of 75, or `K'.

    /* chars.c -- shows some variables of type char */
    /*            as both characters and integers   */

        char ch1 = 'A', ch2 = 'a';

        printf("The character %c has ASCII code %d\n", ch1, ch1);
        printf("If you add ten, you get %c\n", ch1 + 10);
        printf("The character %c has ASCII code %d\n", ch2, ch2);

    Listing 3-8.  The CHARS.C program.

Type unsigned char

    A char value is a signed, 1-byte value that stores values in the range of
    -128 to +127. However, the IBM PC's version of ASCII uses the values 0 to
    255 as character codes. The first half of extended ASCII contains the
    regular ASCII character set. Codes from 128 to 255, however, consist of
    special characters and graphics shapes, which together are called the
    "extended character set." You can use the extended character set by
    declaring variables as the unsigned char type. For example:

    unsigned char box = 178;
    printf("%c\n", box);

    displays a rectangular box, or extended ASCII character number 178. (Note
    that two QuickC general help screens show the complete extended ASCII
    character set.)

Using typedef

    C lets you rename any data type with the typedef statement. For example,
    if you use unsigned char type variables to hold characters from the full
    256-character extended set, you could define an easily remembered

    typedef xchar unsigned char;
    xchar highlight_char, border_char;

    The typedef statement tells QuickC that the word xchar now represents
    unsigned char. Next, we declare two variables as type xchar. Note that you
    can still declare variables as unsigned char at any time. Also note that
    typedef does not create new data types, it merely provides synonyms for
    existing ones.

    The HARDWARE.C program (Listing 3-9) ends our survey of QuickC data

    /* hardware.c -- shows a mixture of int, */
    /*               float, and char types   */

        int threads    = 8;       /* threads per inch */
        float length   = 1.25,    /* length in inches */
            diameter = 0.425,   /* diameter in inches */
            price    = 0.89;    /* price per hundred */
        char bin = 'A';           /* kept in bin A */
        long quantity = 42300;    /* number in bin */

        printf("Screws: %d threads/inch\n %f inches long\n",
                threads, length);
        printf ("%f diameter\n\n", diameter);
        printf("Price per 100: %f\n", price);
        printf("Stored in bin: %c\n Quantity on hand: %ld",
                bin, quantity);

    Listing 3-9.  The HARDWARE.C program.

    Be sure you understand why we declared the different types. The printf()
    statements display the values of the variables and some descriptive text:

    Screws: eight threads/inch
    1.250000 inches long
    0.425000 diameter

    Price per 100: 0.890000
    Stored in bin: A
    Quantity on hand: 42300

    Although the program works correctly, it would look better if the output
    were formatted more neatly. Also, QuickC printed several extra decimal
    places and filled them with zeros. To gain more control over the
    appearance of program output, we need to study printf() in more detail.

Summary of Data Types

    You don't need to memorize the precise numbers associated with each data
    type; one of QuickC's help screens lets you check which data type you
    should use in a given situation. Display this summary of QuickC data types
    by pressing the F1 key and then proceeding to the appropriate screen. As
    you work with various data types in this chapter, you can always consult
    this chart, shown in Figure 3-4, to refresh your memory.

    │ Figure 3-4 can be found on p.67 of the printed version of the book.    │

    Figure 3-4. Data types help screen.

The Power of printf()

    Thus far we've used printf() statements merely to display values. But
    printf() is actually quite versatile for formatting numbers and character

Using Escape Sequences

    Let's look at the parts of the printf() statement from our first program,

    printf ("Hello, and welcome to QuickC!\n");

    This is the simplest printf() statement: It merely prints out a string; no
    variables are involved. Earlier, we briefly discussed the one unusual
    feature of this printf() statement, the \n at the end of the string. This
    combination of backslash and following character is called an "escape
    sequence." Escape sequences tell printf() to print special characters as
    part of the given string. The \n, for example, adds the newline character,
    which moves the cursor or printer head to the beginning of the next line.
    Many languages use two kinds of statements for printing: one to print some
    information, and one to print some information and then start a new line.
    With typical conciseness and versatility, C lets you use one function to
    print any ASCII character, including newline, Tab, and carriage-return
    characters, giving you complete control of the position of the cursor or
    printer head.

    One QuickC help screen, shown in Figure 3-5 on the following page, lists
    all of the escape sequences. The newline \n and tab \t sequences are the
    most frequently used. The \a escape sequence causes an "alert," or beep,
    at the terminal.

    │ Figure 3-5 can be found on p.68 of the printed version of the book.    │

    Figure 3-5. Character escape sequences.

    The ONELINE.C program (Listing 3-10) shows what happens when you don't
    use the newline escape sequence. When you run the program, the output is
    all on one line as follows:

    All displayed onthe same line, with no space unless specified.

    Not only do the strings from all three printf() statements end up on the
    same line, the word "on" at the end of the first string runs into the word
    "the" at the start of the second string. To print two strings on the same
    line with a space between them, you must include the space in the string.
    In the third string of ONELINE.C, we added a space before the word

    /* oneline.c -- shows how printf() continues */
    /*              on the same line             */

    main ()
        printf("All displayed on");
        printf("the same line, with no space");
        printf(" unless specified.");
                /* note added space in line above */

    Listing 3-10.  The ONELINE.C program.

    The program STRINGS.C (Listing 3-11) demonstrates the two basic ways to
    print strings with printf().

    The first printf() statement has only one argument, the string to be
    printed, and the newline escape sequence. The second statement has two
    arguments, the format specifier %s (for "string") and the string to be
    printed. It replaces the specifier with the string and prints it. This is
    the same procedure we used to print numeric variables and literals with
    specifiers such as %d. The STRINGS.C program produces the following

    This uses a string literal by itself
    This plugs the literal into %s

    TABS.C (Listing 3-12) illustrates the use of the tab escape sequence \t.

    /* strings.c -- shows two ways to print */
    /*              a string with printf()  */

        printf("This uses a string literal by itself\n");
        printf("%s", "This plugs the literal into %s\n");

    Listing 3-11.  The STRINGS.C program.

    /* tabs.c -- shows formatting with the \t */
    /*           tab escape sequence          */

        int    q1 = 338, q2 = 57, q3 = 1048, q4 = 778,
                /* quantity in bin */
                t1 = 6, t2 = 8, t3 = 12, t4 = 16;
                /* threads per inch */

        float  s1 = 0.250, s2 = 0.500, s3 = 0.750, s4 = 1.0;
                /* size in inches */

        /* print table header */
        printf("number\t\t size\t\t threads\n");
        printf("in bin\t\t (inches)\t per inch\n\n");

        /* print lines of table */
        printf("%d\t\t %f\t %d\n", q1, s1, t1);
        printf("%d\t\t %f\t %d\n", q2, s2, t2);
        printf("%d\t\t %f\t %d\n", q3, s3, t3);
        printf("%d\t\t %f\t %d\n", q4, s4, t4);

    Listing 3-12.  The TABS.C program.

    This program prints four sets of data in a neat table. The program prints
    the table headers first, using \t to tab to the next field. Using \t to
    position each item at the next tab stop causes the output to be left-
    justified in each field. To make the table easier to read, we added a
    blank line between the header and the data by including an extra \n in the
    second printf() statement. The program then prints the values of the
    variables in the same tab fields as the headers. The result of all this is
    as follows:

    number          size             threads
    in bin          (inches)         per inch

    338             0.250000         6
    57              0.500000         8
    1048            0.750000         12
    778             1.000000         16

Formatting Numbers with printf()

    The printf() function can also print numbers in a variety of formats.
    Let's look at a printf() statement from SCORE.C, which is analyzed in
    Figure 3-6.

    String to print is
    enclosed in quotes         ┌─────────────────────────────┐
            │                  │                             │
            │                ┌─▼──┐            ┌────┐    ┌───▼────┐
    printf ("The score after │ %d │ innings is │ \n │ ", │ inning │ );
                            └────┘            └────┘    └────────┘
                                │                 │           │
                                │                 │           │
                        Format specifier       Newline     Variable whose
                        for an int value       escape      value is to be
                                            sequence    printed

    Figure 3-6. The printf() statement from SCORE.C.

    Return and Newline Are Different
    If you program in other languages on MS-DOS machines, you might expect \r
    (carriage return) to move the cursor to the start of a new line. Change
    the \n in TABS.C to \r and run the program again. What happens? Each line
    prints over the preceding one. Although many languages on MS-DOS machines
    incorporate a line feed in a carriage return, C treats newline and return
    as distinct operations. Return moves the cursor to the beginning of the
    current line but does not advance it to a new line. Newline causes output
    to start on the next line. It commences with output at the beginning of
    the next line (rather than directly below the old position) because MS-DOS
    interprets it as though it contains a carriage return as well.

    Notice the %d in our example, SCORE.C. This, as we have already mentioned,
    is the format specifier for a decimal integer. The string "The score after
    %d innings is" is followed by a comma and the variable inning. Thus, when
    the printf() statement executes, the string is printed with the value of
    inning. You can also print more than one value in the same string. For
    example, if you define int apples = 12, oranges = 9, pears = 3;, then
    execute the following printf() statement:

    printf("I have %d apples, %d oranges, and %d pears. \n", apples, oranges,

    you see the following output:

    I have 12 apples, 9 oranges, and 3 pears.

Specifying Formats with printf()

    Variables are printed according to their type and the format specifiers
    used. One of the QuickC General help screens (Figure 3-7) shows format
    specifiers and additional symbols that can specify formats.

    The program SPECS.C (Listing 3-13 on the following page) prints different
    types of variables with their appropriate specifiers.

    │ Figure 3-7 can be found on p.71 of the printed version of the book.    │

    Figure 3-7. Format specifiers.

    /* specs.c -- shows printf()format   */
    /*            specifiers for numbers */

        int    i = 122;       /* ASCII code for 'z' */
        long   l = 93000000;  /* distance to Sun (miles) */
        float  f = 192450.88; /* someone's bottom line */
        double d = 2.0e+030;  /* mass of Sun (kg.) */

        printf("%d\n", i);  /* integer as decimal */
        printf("%x\n", i);  /* integer as hex */
        printf("%ld\n", l); /* long */
        printf("%f\n", f);  /* float as decimal */
        printf("%e\n", f);  /* float as exponential */
        printf("%f\n", d);  /* double as decimal */
        printf("%e\n", d);  /* double as exponential */

    Listing 3-13.  The SPECS.C program.

    Compare the following output with the printf() statements in the SPECS.C


    The first printf() statement prints the value of the int variable i, 122,
    as an ordinary decimal integer, using the now familiar %d specifier. The
    next statement prints the same value with the %x specifier, which prints
    values in hexadecimal. Next, we print the long integer value 93000000.
    Notice that this specifier, %ld, combines the %l (long) and %d (integer)

    The SPECS.C program then prints the value of the variable f, 192450.88,
    using the %f floating-point specifier. In the next statement, we use %e to
    print the same number in exponential notation. Which is better? If the
    value represents money, the regular decimal format is more appropriate,
    but remember that both representations are slightly inaccurate because the
    original value, 192450.88, has eight places and float has a maximum
    precision of seven places. (If you want absolute accuracy, use the double

    We print the final value, 2,000,000,000,000,000,000,000,000,000,000, two
    ways: as a double (note that you can use %f for double as well as for
    float) and as exponential notation with %e. Clearly, the latter is easier
    to read and understand.

Format Specifiers and Data Types

    Remember, the format specifier merely controls how a value is displayed.
    The data type of the value represents how it is actually stored in the
    computer. The program FORMATS.C (Listing 3-14) displays the comedy of
    errors that can occur if you carelessly use the wrong format specifier
    with a data type. The following is the output of the program; compare it
    with the printf() statements in the program.

    As integer:      5
    As long integer: 8519685
    As exponential:  7.084198e-309
    As float:        0.000000

    The program uses four different specifiers to print the value of the int
    variable i, which we set to 5. Only the first representation, using %d, is
    correct. The other results vary widely (even from one run to another). How
    can the last three methods be so far off the mark? Consider the second
    printf() statement, in which we told QuickC to print the value of i as a
    long integer %ld. A long integer uses four bytes of memory, but this
    variable, as an int type, uses only two. When you specify a long integer,
    QuickC takes four bytes starting at the address of i and converts them
    into a long integer. Two of these bytes, however, have nothing to do with
    the variable i. You can see how similar problems arise when we try to
    interpret an integer variable as a float. All of this demonstrates that
    the format specifier must be compatible with the data type being handled.
    Table 3-1 correlates the most commonly encountered specifiers and data

    /* formats.c -- shows what happens when format */
    /*              doesn't match data type        */

        int i = 5;
        printf("As integer:      %d\n", i);
        printf("As long integer: %ld\n", i);
        printf("As exponential:  %e\n", i);
        printf("As float:        %f\n", i);

    Listing 3-14.  The FORMATS.C program.

    Table 3-1. Compatibility of Specifiers and Data Types
    Specifier          Types
    %d                 int (signed or unsigned); char (ASCII value)
    %ld                long
    %f                 float or double (decimal format)
    %e                 float or double (exponential format)
    %c                 char (as character)

Field Width Specifiers

    We can also improve the appearance of printf() output by controlling how
    many decimal places are printed and how the number is aligned in the
    output field. To do this, C lets us precede the format specifier with a
    "field specifier." The field specifier takes the following form:

    <field width>.<decimal places>

    The "field width" is the total number of character positions that will be
    printed, and "decimal places" is the number of places printed after the
    decimal point. (Use the decimal place specifier only for float and double
    values.) Following are two examples of field specifiers:

    "5.2f"────────────────────float; 5 places, 2 of which are decimal places
    "8d"───────────────────────────────integer; 8 places (no decimal places)

    The program FIELDS.C (Listing 3-15) shows how field width specifiers

    The program prints a single variable with varying field specifiers:

        123.456001────────────────────────────────────12.6f (field specifier)

    In the first printf() statement, the field specifier %12.6f sets up a
    12-character-wide field, 6 characters of which are decimal places. Because
    the variable has only 10 characters to be printed (9 digits and a decimal
    point), printf() indents the number two spaces. By default, numbers are
    right-justified (printed starting in the rightmost position of the
    specified field width). To print numbers that start at the left side of
    the field (left-justified), put a minus sign in front of the field width
    specifier, "%-4.2f".

    /* fields.c -- shows the same number with different */
    /*             field widths and number of decimals  */

        float f = 123.4560;

        printf("%12.6f\n", f);
        printf("%8.4f\n", f);
        printf("%8.3f\n", f);
        printf("%8.2f\n", f);

    Listing 3-15.  The FIELDS.C program.

    Note also in the first printf() statement that we asked for six decimal
    places, even though the variable number contained only the first four
    places. Although printf() prints these extra places, they add nothing to
    the precision of the number, and, in fact, give a misleading impression of
    precision. You should specify decimal places only to the expected
    precision of the value. For example, if you know that a value will range
    between 0 and 9999 with decimal places, you might specify %4.3f because
    the value can have as many as four places to the left of the decimal
    point, and a float has only seven places of precision. Thus, a total of
    seven places (4.3) displays an accurate value. Specifying %8.6 for this
    example would give a false impression of precision.

    In the second statement, the specifier establishes a field width of 8
    (with 4 decimal places). The third statement specifies the same field
    width of 8, but with only 3 decimal places. Notice that the variable's
    fourth decimal place, the zero, is dropped, and that the number is
    indented one space because the variable has only 7 characters. The last
    statement specifies the same field width of 8, but with only 2 decimal
    places. The printf() function not only drops the third decimal place, it
    also rounds up the second decimal place to 6. Also, because the number has
    one fewer digit to fit in the 8-character field, printf() indents the
    number another space.

Arithmetic Operators

    Like most languages, C offers a complete set of arithmetic operators: +
    (addition), - (subtraction), * (multiplication), and / (division). C also
    provides a fifth operator that is not quite as common in other languages──
    %, the remainder operator, sometimes called the "modulus" operator. This
    operator returns only the remainder of a division operation. For example,
    5 % 2 is 1 (5 divided by 2 has a remainder of 1), and 9 % 3 is 0 (9
    divided by 3 has no remainder).

    The modulus operator has many uses: You can use it for creating counters
    that cycle within counters or for resetting variables such as line counts
    by checking for a remainder of zero (if line_cnt % page_length = 0, then
    you know that you must start a new page).

    Operators are used with values to form expressions that yield new values.
    Below are some examples:

    10 * 5─────────────────────────────────────────────Multiply two literals
    a / 5─────────────────────────────────────────────Divide value of a by 5
    count + 1────────────────────────────────────────Add 1 to value of count
    (a * 80) + b──────────────Multiply value of a by 80, then add value of b

    In a program, you combine expressions with other elements to form
    statements. The MATH.C program (Listing 3-16 on the following page)
    contains statements that use expressions involving arithmetic operators.

    /* math.c -- shows arithmetic and       */
    /*           precedence via expressions */

        int a = 10, b = 4, c = 2;

        /* simple arithmetic expressions */
        printf("99 + 2 = %d\n", 99 + 2);  /* ints */
        printf("5 - 12 = %d\n", 5 - 12);
        printf("7.25 + 3.5 = %f\n", 7.25 + 3.5);
                                            /* floats */

        /* compare precedence on these */
        printf("20 * 20 + 40 = %d\n", 20 * 20 + 40);
        printf("20 * (20 + 40) = %d\n", 20 * (20 + 40));
        printf("a * a - c + b =  %d\n", a * a - c + b);
        printf("a * (a - (c + b)) = %d\n",
                a * (a - (c + b)));

        /* compare integer and float division */

        printf("Integers: 5 / 2 = %d\n", 5 / 2);
        printf("Floats: 5.0 / 2.0 = %f\n", 5.0 / 2.0);

    Listing 3-16.  The MATH.C program.

    Each printf() statement prints the expression and then its value, as

    99 + 2 = 101
    5 - 12 = -7
    7.25 + 3.5 = 10.750000
    20 * 20 + 40 = 440
    20 * (20 + 40) = 1200
    a * a - c + b =  102
    a * (a - (c + b)) = 40
    Integers: 5 / 2 = 2
    Floats: 5.0 / 2.0 = 2.500000

    The first three statements simply add and subtract literal numbers and
    print the results. Notice in the third statement that when QuickC sees a
    number with a decimal point, it assumes a float type and prints the answer
    accordingly (10.750000).

Operator Precedence

    The next set of statements in MATH.C illustrates "precedence," or the
    rules that determine the order in which operators are applied. Generally,
    QuickC performs multiplication and division first, then addition and
    subtraction. When operators have equal precedence (such as division and
    multiplication), QuickC performs them from left to right. The following
    QuickC help screen, Figure 3-8, lists all the operators in the language
    (including many covered in later chapters) and arranges them in groups
    from highest to lowest precedence.

    Thus, in Listing 3-16 the first printf() statement in the second group of
    statements multiplies 20 by 20, then adds 40, resulting in 440. However,
    you can use parentheses to impose a different order of precedence, as
    shown in the next statement. To evaluate the expression 20 * (20 + 40),
    QuickC performs the addition first (resulting in 60) and then multiplies
    20 by 60 to produce a value of 1200.

    The next two statements use combinations of variables. As an exercise,
    perform the calculations on paper before you run the program. Remember to
    observe the rules of precedence. Did your answers agree with QuickC's?

    The final two statements in MATH.C illustrate a common problem for the
    unwary beginning C programmer. QuickC divides integer and floating-point
    types differently. When you specify numbers as integers, as in the first
    statement, integer division is performed. Accordingly, 5 divided by 2 is 2
    because this type of division discards any remainder. (A remainder in
    division is a fraction, and int types cannot represent fractions.)
    However, when you specify numbers with decimal points, QuickC treats them
    as float types, resulting in the expected answer of 2.5. Variables of int
    and float types are handled the same way as the literals above.

    The RECEIPTS.C program (Listing 3-17 on the following page) performs
    practical calculations with QuickC's math operators. Notice that we
    declare the units variable as an int type (you can't sell half a unit!)
    and the price and tax rates as float types.

    │ Figure 3-8 can be found on p.77 of the printed version of the book.    │

    Figure 3-8. Operator precedence help screen.

    /* receipts.c -- calculates gross and net */
    /*               receipts on sales        */

        int units = 38;       /* number sold */
        float price = 149.99, /* price per item */
        rate = 0.06;          /* sales tax rate */

        /* variables to hold calculated totals */
        float gross, tax, net;

        /* perform calculations */
        net = units * price;
        tax = net * rate;
        gross = net + tax;

        /* print results */
        printf("\tSales Report\n");
        printf("Net sales: \t%6.2f\n", net);
        printf("Tax:\t\t %5.2f\n", tax);
        printf("Gross sales:\t%6.2f\n", gross);

    Listing 3-17.  The RECEIPTS.C program.

    The "calculations" section uses expressions to generate values for the
    variables net, tax, and gross. The printf() statements combine tab escape
    sequences \t and field width specifiers to align the output. Specify only
    two decimal places for money amounts. (Makes cents, doesn't it?) The
    program produces the following report:

            Sales Report
    Net sales:      5699.62
    Tax:             341.98
    Gross sales:    6041.60

Arithmetic with Mixed Types

    The accuracy of a number generated by QuickC depends on its data type and
    the format in which it is printed. An additional problem arises when you
    perform arithmetic operations on literals (constants) or variables of
    different data types: For example, what happens when you divide an int by
    a float?

    For calculations with mixed data types, C ranks data types roughly
    according to the number of bytes of storage they require. From highest to
    lowest, they are:

    double  8 bytes
    float   4 bytes
    long    4 bytes
    int     2 bytes
    char    1 byte

    Generally, QuickC converts the lower-ranking type to the higher-ranking
    one before it performs the calculation. Thus, when QuickC divides 49 by
    12.5, it first converts 49 to 49.0 (a float), then performs the division.
    (If QuickC chose a lower-ranking type, the calculation would lose
    precision. The above calculation, for example, would be 49 / 12 = 4 in
    integer division.) Although the long and float types both use four bytes,
    a float can contain a fractional part that would be lost when converted
    "down" to a long: Therefore, float is ranked as the "higher" type.
    Finally, QuickC converts float types to double types before it calculates
    the result.

    Although it's convenient that QuickC performs conversions for you, some
    real problems can occur if you assign the results of a calculation to a
    variable of an incorrect data type. The following example illustrates such
    a mistake:

    int sales, units = 50;
    float price = 1.99;
    sales = units * price;
    printf("Total sales are %d\n", sales);

    QuickC calculates price * units correctly by converting units from 50 to
    50.0 (to make it a float), and then multiplying it by the float value of
    price (1.99). The value of the expression is now the float value of 99.50.
    So far, so good. However, we assigned this value to the variable sales,
    which we declared as an int type. As a result, the fractional part of the
    value (.50) is dropped, making the value of sales an incorrect 99.00. The
    solution to this problem is simple──consider all of the potential values
    for a variable before you declare it. In this case, you need to declare
    the variable sales as a float.

    QuickC can help remind you of potential problems with data type
    conversions. When you select the Compile option from the Run menu, the
    left side of the dialog box lists four levels of compiler warning messages
    (levels 0 through 3). If you select level 2 before you compile programs,
    QuickC sends a warning message for each program statement that causes a
    data type conversion. A typical message follows:

    warning C4051: (1 of 4)
    data conversion

    When you see this type of message, note the statement the cursor is on,
    examine the data types involved, and look up the meaning of warning (4051)
    in the Microsoft QuickC Programmer's Guide. There you will note that this
    is an advisory message, and QuickC issues it for perfectly legitimate
    conversions, such as the int to float conversion in our earlier division

    The MIXED.C program (Listing 3-18 on the following page) shows more
    examples of operations with mixed data types.

    /* mixed.c -- shows the effects of mixing */
    /*            data types in an expression */

        int     i = 50, iresult;
        long    l = 1000000, lresult;
        float   f = 10.5, fresult;
        double  d = 1000.005, dresult;

        fresult = i + f;         /* int + float to float */
        printf("%f\n", fresult);

        fresult = i * f;         /* int * float to float */
        printf("%f\n", fresult);

        lresult = l + i;         /* long + int to long */
        printf("%ld\n", lresult);

        printf("%f\n", d * f);   /* double * float */
                                    /* to double */
        fresult = d * f;         /* assigned to a float */
        printf("%f\n", fresult); /* loses some precision */

        /* debugging a division problem */

        iresult = i / l;          /* int / long to int*/
        printf("%d\n", iresult);  /* whoops! loses result */
        printf("%ld\n", iresult); /* this won't fix it */
        fresult = i / l;          /* store in float result */
        printf("%f\n", fresult);  /* doesn't work */
        dresult = i / l;          /* try a double */
        printf("%f\n", dresult);  /* doesn't work */
        fresult = (float) i / l;  /* try type cast */
        printf("%f\n", fresult);  /* correct result */

    Listing 3-18.  The MIXED.C program.

    Compare this output to the program statements:


    The first set of statements adds an int and a float value and prints the
    result, which is 60.5, a float value. This shows QuickC's default type
    conversion at work. The second set of statements shows the same conversion
    with a multiplication operation. The third pair of statements adds a long
    to an int. Note that the result is correct (100,000 + 50 = 100,050), and,
    from its size, you can guess that it must be a long. QuickC converts the
    value 50 to a long before it does the calculation.

    Next, the program works with double and float types. When we specify d * f
    in the printf() statement, QuickC converts the float value f to a double
    and calculates a double result, which we print. (Remember, you can use the
    %f format specifier with either float or double types.) Because the answer
    requires nine places of precision, converting from float to double
    preserves the accuracy of the value.

    Next, we perform the same calculation, but we assign the result to a float
    value, f. Notice that the result, 10,500.052734, becomes inaccurate
    starting at the fourth decimal place. Converting from double to float can
    produce both large and subtle errors, depending on the numbers involved.
    To be safe, use a double variable to hold the result of this type of

    The last lengthy set of statements illustrates various approaches for
    dividing an int value i by a long value l. Only the last method produces
    the correct result.

    Assigning the result of the division to an int variable returns a 0,
    because the result is a very small decimal fraction (50 / 1,000,000), and
    integer division does not recognize remainders.

    In the next statement we assume that the result of the division is a
    decimal fraction and that we can store it in a float. But this doesn't
    work either. When we add more decimal places by using a double variable
    for the result, we still get a result of 0. The problem here is that when
    the two integer variables i and l are divided, the integer portion of the
    result, 0.000050, is 0. At this point, we can't retrieve the decimal
    fraction. Assigning it to a float or a double merely gives you a
    floating-point representation of 0!

Type Casting

    C provides a solution to our division dilemma with a construction called a
    "type cast." A type cast explicitly converts a value to a specified type
    before any operations are done on that value. Consider the following

    int i1 = 10, i2 = 3;
    printf("%d\n", i1 / i2);
    printf("%f\n", (float) i1 / i2);

    In the first printf() statement, we divide the two integers and produce
    the integer result of 3. In the second printf() statement, we add (float)
    before i1. This is the type cast: It converts the value of i1 to a float.
    Because a type cast has a higher precedence than the arithmetic operators,
    it converts i1 to a float before the division operation. Now the division
    operation contains a float and an int! QuickC's default type conversion
    then converts i2 to a float as well, and the result is the float value
    3.33333. If you look at the last two statements of the MIXED.C program,
    you can see we used a type cast in the same way, with the correct result
    of 0.000050.

    Type casts are useful for handling variables of lower-ranking data types
    (int, for example) that must occasionally be used in calculations to
    produce a result of a "higher" type (such as float). It is more efficient
    in terms of both storage and processing time to declare such variables as
    the lower type and to use type casts when necessary. Later, you will find
    type casts valuable when you must convert values to a specific type.

Getting Input with scanf()

    In order to write programs that have real-world utility, we must first
    understand how a C program gets input from the user. The all-purpose C
    function for getting input and storing it in a variable is called scanf().
    (Like printf(), scanf() is a "built-in" QuickC core library function.)
    Figure 3-9 shows how it works.

    Let's assume we have a program with a declared integer variable named
    acct_no. When the scanf() statement executes, the program waits for input
    from the user. After the user types the number and a carriage return, the
    input is stored in the variable acct_no, as if it had been assigned by an
    assignment statement. Notice that the acct_no variable in the scanf()
    argument list is preceded by an ampersand (&). Do you remember when we
    placed ampersands in front of variable names in the VARADDRS.C program
    (Listing 3-4 on p. 57) to retrieve the storage addresses of the
    variables? The scanf() function requires as its second argument an address
    at which it can store the input. The & returns the address of the
    following variable. If you omit the address operator from the front of the
    variable name, the value of the variable is interpreted as though it were
    an address, and the input is stored at that address. This can produce
    frightful results if it overwrites information that your program needs!

    Format specifier for
    type of value wanted
    scanf ("%d", &acct_no);
                │     │
        "address of"   Variable name
        Variable to store input in

    Figure 3-9. Parts of a scanf() statement.

    The first argument in the scanf() statement in Figure 3-9 is "%d". This
    looks and works like the format specifiers we used with printf()──it
    specifies the type of the value that the program expects. As with
    printf(), the "%d" specifies an integer. You can also use most of the
    other specifiers you used with printf(). For example:

    scanf("%f", &deposit);

    gets a value for the float variable deposit.

    Notice that scanf(), by itself, does not print a prompt for the user; it
    merely presents the user with a blinking cursor. Therefore, you should
    precede a scanf() statement with a printf() statement that tells the user
    what information to supply. In the example above, we might precede the
    scanf() statement with:

    printf("How much is your deposit?");

    The cursor now appears following a space after the prompt. You don't need
    to include a newline character: The cursor will move to the next line when
    the user presses Enter after typing the input.

    You can also use scanf() to get values for more than one variable at a

    printf("What is your age and weight?");
    scanf("%d %d", &age &weight);

    In this example, the user types an age, a space (to separate the values),
    and then a weight. Note that the user can substitute an Enter or a Tab for
    the space.

    The CONVERT.C program (Listing 3-19) uses scanf() to prompt a user for a
    temperature in Fahrenheit and then converts the temperature to Centigrade.

    /* convert.c  -- converts Fahrenheit temperature     */
    /*               to Centigrade; gets value from user */

        float ftemp, ctemp;

        printf("What is the temperature in Fahrenheit? ");
        scanf("%f", &ftemp);
        ctemp = (ftemp - 32.0) * 5 / 9.0;

        printf("The temperature in Centigrade is %5.2f", ctemp);

    Listing 3-19.  The CONVERT.C program.

    A sample user dialog with CONVERT.C follows:

    What is the temperature in Fahrenheit? 87
    The temperature in Centigrade is 30.56

    We print the prompt with a printf() statement, then use a scanf()
    statement with a floating-point specifier %f to get the input value for
    the float variable ftemp.

    The AVGTEMP.C program (Listing 3-20) averages the daily high temperatures
    for a week. When you run the program, it prompts for the high temperature
    for each day of the week, beginning with Monday.

    /* avgtemp.c -- finds average temperature */
    /*              for the week              */

        int t1, t2, t3, t4, t5, t6, t7;
        float avg;

        printf("Enter the high temperature for:\n");
        printf("Monday: ");
        scanf("%d", &t1);
        printf("Tuesday: ");
        scanf("%d", &t2);
        printf("Wednesday: ");
        scanf("%d", &t3);
        printf("Thursday: ");
        scanf("%d", &t4);
        printf("Friday: ");
        scanf("%d", &t5);
        printf("Saturday: ");
        scanf("%d", &t6);
        printf("Sunday: ");
        scanf("%d", &t7);

        /* calculate and display average */
        avg = (t1 + t2 + t3 + t4 + t5 + t6 + t7) / 7.0;
            /* divide by 7.0 to ensure float result */
        printf("The average high temperature for");
        printf(" this week was %5.2f degrees.\n", avg);

    Listing 3-20.  The AVGTEMP.C program.

    The int variables t1 through t7 store the daily high temperatures, which
    are obtained by a series of scanf() statements. The program then
    calculates an average and prints it out. A sample dialog with this program
    might look as follows:

    Enter the high temperature for:
    Monday: 82
    Tuesday: 91
    Wednesday: 97
    Thursday: 104
    Friday: 95
    Saturday: 88
    Sunday: 78
    The average high temperature for this week was 90.71 degrees.

    Note: It is important to note that scanf() does not check to make certain
    that the input is compatible with the data type of the variable in which
    it is stored.

Shortcut Assignments, Increments, and Decrements

    Now that you know how to assign a value to a variable with the assignment
    operator = and how to use arithmetic operators to calculate new values, we
    can show you a few tricks and shortcuts. In the course of a program, it is
    often useful to add a value to a variable repeatedly or to subtract a
    value from a variable repeatedly. For example, a program that counts lines
    needs to add one to a variable (such as total_lines) each time it counts a
    new line. We could do this as follows:

    total_lines = total_lines + 1;

    That's the way most languages do it. However, because changing the value
    of a variable is such a common occurrence in programming, C provides
    special, concise "arithmetic assignment operators" for the purpose.

Arithmetic Assignment Operators

    The arithmetic operators are the +, -, *, /, and %, and the assignment
    operator is the =. The arithmetic assignment operator, as the name
    suggests, is a combination of an arithmetic operator and the assignment
    operator: for example, +=. When a statement executes, QuickC performs the
    specified arithmetic on the variable's value and then assigns the result
    of the calculation to the variable as its new value. Using an arithmetic
    assignment operator, we can write a shorter version of the statement that
    increases the value of total_lines by one:

    total_lines += 1;

    Below are more examples that use arithmetic assignment operators:

    count -= 1;───────────────────────────Subtract 1 from the value of count
    fare += 0.75;──────────────────────────────────Add 0.75 to value of fare
    value *= 10;────────────────────────────────────────Multiply value by 10

    You can use any arithmetic operator in an arithmetic assignment operation.
    Table 3-2 lists the five possible arithmetic assignment operators. The
    addition and subtraction assignment operators are the most commonly used.

    The OPEQUAL.C program (Listing 3-21) demonstrates the use of arithmetic
    assignment statements. The printf() statements print several arithmetic
    assignment expressions and their results.

    Be sure that when you read the printf() statements in the program you can
    correctly predict the following output:

    Starting values: m = 10 n = 5
    m += 2 makes m 12
    m -= n makes m 7
    m *= 2 makes m 14
    m = m + 1 makes m 15
    m += 1 makes m 16

    Table 3-2. Arithmetic Assignment Operators
    Operator           Meaning
    +=                 Add to value and assign
    -=                 Subtract from value and assign
    *=                 Multiply by value and assign
    /=                 Divide by value and assign
    %=                 Get remainder from division and assign

    /* opequal.c -- shows combination math/assignment */
    /*              operators and increment operators */
        int m = 10, n = 5;
        printf("Starting values: m = %d n = %d\n",
                m, n);

        /* combination of arithmetic and assignment */
        printf("m += 2 makes m %d\n", m += 2);
        printf("m -= n makes m %d\n", m -= n);
        printf("m *= 2 makes m %d\n", m *= 2);

        /* two ways to increment m */
        printf("m = m + 1 makes m %d\n",
                m = m + 1);
        printf("m += 1 makes m %d\n",
                m += 1);

    Listing 3-21.  The OPEQUAL.C program.

Increment and Decrement Operators

    As the last program demonstrated, both m = m + 1 and m += 1 added one to
    the value of m. If you've done any programming, you know how frequently
    the value of a variable must be increased or decreased by one. This is
    especially true when you create a "counter" variable that keeps track of
    the number of times a statement in a loop executes. C provides an
    ultra-concise operator, the increment operator ++, to add one to the value
    of a variable. Similarly, --, the decrement operator, subtracts one from
    the value of a variable. Consider the following examples:

    count++ ;────────────────────────────────────────Add 1 to value of count
    index-- ;─────────────────────────────────Subtract 1 from value of index

    Note that these increment and decrement operators are really arithmetic
    assignment statements. They add (or subtract) one and assign the resulting
    value to the variable.

    count++;──────────────────is equivalent to───────────────────count += 1;
    index--;──────────────────is equivalent to───────────────────index -= 1;

    (Most programmers do not use a space between the increment [or decrement]
    operator and the variable name. However, in C it is perfectly legal to use
    intervening spaces, as in count + +.) INCDEC.C (Listing 3-22) shows how
    the increment and decrement operators change the value of a variable.
    Compare the program statements to the following output:

    a is 10
    ++a is 11
    --a sets a back to 10

    /* incdec.c -- shows effect of            */
    /*             increments and decrements  */

        int a = 10;

        printf("a is %d\n", a);
        printf("++a is %d\n", ++a);
        printf("--a sets a back to %d\n", --a);

    Listing 3-22.  The INCDEC.C program.

Pre-increment vs Post-increment

    In the INCDEC.C program we put the increment or decrement operator in
    front of the variable name. However, you also can use it after the
    variable name. In either case the variable is incremented or decremented;
    but there is one important difference. When you use the operator in front
    of a variable name, the incrementing or decrementing is done immediately.
    When you use the operator after the variable name, the incrementing or
    decrementing is not done until the next use of the variable. The PREPOST.C
    program (Listing 3-23) shows how this works. The output of the program
    illustrates how incrementing is delayed:

    b is 100
    b++ is still 100
    but after it's used, b is incremented to 101

    ++b, on the other hand, is immediately 102

    Notice what happens to b when we use the increment operator after it,
    rather than before it. The first printf() statement with the value b++
    prints the original value of 100, showing that it has not yet been
    incremented. The next printf() statement, however, prints 101.

    As a practical matter, the distinction between pre-increments and
    post-increments (or decrements) is usually important only when the
    variable is incremented or decremented while it is being used with other
    operators in a single expression. For example, suppose you want to
    increment counter and also assign it to total in the same statement.
    Assuming counter is currently 10:

    total = counter++;

    assigns 10 to total, because counter is assigned to total but not
    incremented until the next time it is used. On the other hand:

    total = ++counter;

    assigns 11 to total, because counter is incremented immediately and then

    /* prepost.c -- shows effect of pre- */
    /*              and post-increments  */
    /*              and decrements       */

        int b = 100;

        printf("b is %d\n", b);
        printf("b++ is still %d\n", b++);
        printf("but after it's used, ");
        printf("b is incremented to %d\n\n", b);

        printf("++b, on the other hand, ");
        printf("is immediately %d\n", ++b);

    Listing 3-23.  The PREPOST.C program.

Relational Operators

    If you have some programming experience, you know that most programs must
    make decisions based on the values of certain variables. Variables are
    tested or compared, and the result of the test determines which program
    statement will execute next. The next two chapters cover the variety of
    "control structures" that C provides for this purpose. Let's build the
    foundation for those discussions by looking at the operators that C uses
    for testing or comparing values.

    A relational operator compares two values, which can be variables, literal
    numbers, or whole expressions. A combination of relational operators and
    values is called a relational expression. An example is count > 10, which
    translates as "is the value of count greater than 10?" The > in this
    expression is the "greater than" relational operator. The expression is
    true or false depending on the current value of the variable count. If
    count is 8, for example, the expression is false.

    Table 3-3 illustrates the ways we can compare two values, a and b. In
    reality, the values can be constants, variables, or expressions──anything
    that expresses a numeric value. (Remember from our discussion of ASCII
    that characters, too, are essentially numeric values.)

    We described the value of a relational expression as being "true" or
    "false." These terms are useful ways for us to follow the logic of a
    program, but the actual value of a relational statement, like everything
    else in the computer, is numeric. When a statement is true, its value is
    1; when a statement is false, its value is 0. On the following page, the
    RELATION.C program (Listing 3-24) uses printf() statements to show the
    values of some statements that use relational operators.

    The program generates the following output:

    a = 5    b = 3   c = 4
    Expression a > b has a value of 1
    Expression a == c has a value of 0
    Expression a > (b + c) has a value of 0

    Because a is 5 and b is 3, the expression a > b has a value of 1, or true.
    Because c is 4, a == c has a value of 0, or false. The third expression
    combines relational and arithmetic operators: It first calculates the
    quantity (b + c), and then it compares the value to a.

    Table 3-3. Relational Operators
    Expression               Meaning
    a < b                    Is a less than b?
    a > b                    Is a greater than b?
    a == b                   Is a equal to b?
    a != b                   Is a not equal to b?
    a <= b                   Is a less than or equal to b?
    a >= b                   Is a greater than or equal to b?

    /* relation.c  -- shows effect of      */
    /*                relational operators */

        int a = 5, b = 3, c = 4;
        printf("a = %d\t b = %d\t c = %d\n", a, b, c);

        printf("Expression a > b has a value of %d\n",
                a > b);
        printf("Expression a == c has a value of %d\n",
                a == c);
        printf("Expression a > (b + c) has a value of %d\n",
                a > (b + c));
        printf("Expression a = b has a value of %d\n",
                a = b); /* what happened here? */

    Listing 3-24.   The RELATION.C program.

Relational == vs Assignment =

    Here's a pitfall to watch out for: In C, a single equal sign = is the
    assignment operator, but a double equal sign == is the relational "equals"
    operator. In some languages (such as BASIC), a single operator, =, serves
    both purposes. So, if you are familiar with the BASIC usage, you might
    make errors with these operators until you get used to the difference. A
    common symptom of this error is a test that always appears to be either
    true or false. For example, if you type the assignment count = 10 instead
    of the relational count == 10 and then use the result in a control
    structure (such as a loop or if statement), QuickC always sees the result
    of the test as "true." Why? Because although relational expressions return
    a value of 1 for "true," QuickC considers any nonzero value to be "true"
    in this type of test. Because the sample statement with = is actually an
    assignment, its value is 10 (the number assigned), which QuickC interprets
    as "true" during a relational test.

    Assignment and "Equals" Relation
    The following table lets you compare the assignment and relational
    "equals" operators in C to those in other common languages:

    Language               Assignment              Relation
    BASIC                  =                       =
    Pascal                 :=                      =
    FORTRAN                =                       .EQ.
    Logo                   make                    =
    COBOL                  MOVE                    EQUAL TO


Precedence of Relational Operators

    In RELATION.C, we used parentheses in the expression a > (b + c). If you
    check QuickC's operator precedence help screen (Figure 3-8 on p. 77), you
    will see that relational operators have a lower precedence than arithmetic
    operators. Therefore, even if you don't use parentheses, b + c is
    calculated first, and only then is the result compared to a. Nevertheless,
    it is a good programming practice to use parentheses to visually clarify
    an expression.

Logical Operators

    Sometimes it is necessary or useful to test for more than one thing in the
    same expression or statement. For example, you might want to test to see
    if either the temperature or pressure in a boiler has exceeded the safety
    limit. Let's assume the test for temperature is (temp < 900) and the test
    for pressure is (pressure < 5000). We can combine the two tests as

    (temp < 900) && (pressure < 5000)

    The && is called the AND logical operator. It compares the results of two
    relational values and returns a value of true (1) only if both are true.
    QuickC first makes the temp test, then it makes the pressure test (testing
    is from left to right). Then the && operator checks to see if both tests
    were true.

    The OR logical operator, ||, works like the AND operator, except that it
    returns a value of true (1) if either or both of the tests are true. Thus,
    the statement

    (ch == 'q') || (turn > last_turn)

    is true if either the current value of ch is `q' or the current value of
    turn is greater than that of last_turn, or both. You could use this
    statement to check if a game is over.

    Using two relational operators, && and ||, and two possible results of a
    test (true and false), there are four possible results for a relational
    statement involving two tests. The TRUTH.C program (Listing 3-25 on the
    following page) prints these out by making comparisons using ones and
    zeros that represent the result of already completed relational tests.
    Recall that QuickC regards a value of 1 to be "true" and a value of 0 to
    be "false." Thus, 1 AND 1 is 1 means "True and true is true."

    1 AND 1 is 1
    1 AND 0 is 0
    0 AND 0 is 0
    1 OR 1 is 1
    1 OR 0 is 1
    0 OR 0 is 0

    /* truth.c -- shows logical operators */
        printf("1 AND 1 is %d\n", 1 && 1);
        printf("1 AND 0 is %d\n", 1 && 0);
        printf("0 AND 0 is %d\n", 0 && 0);
        printf("1 OR 1 is %d\n",  1 || 1);
        printf("1 OR 0 is %d\n",  1 || 0);
        printf("0 OR 0 is %d\n",  0 || 0);

    Listing 3-25.  The TRUTH.C program.

    Once again, if you check the QuickC operator precedence help screen, you
    will notice that logical operators, such as && and ||, have a lower
    precedence than the relational operators, such as < or ==. Therefore, we
    didn't need parentheses around the relational expressions in our examples
    because QuickC evaluated them before it checked the logical operators.
    Again, we used parentheses because they make these complex expressions
    easier to read.

    The last operator we need to discuss is the !, or "not" operator. Its
    function is simple enough──it reverses the truth value of a relational
    expression. For example, if a is 10, a > 5 is true, but !(a > 5) is false.

Chapter 4  Repetition and Looping

    In all of our programs so far, QuickC has executed the program statements
    sequentially, from the first statement to the end of the program. However,
    most of a program's important work involves controlled repetition, in
    which a group of statements repeatedly does a particular job until the
    work is done. For example, consider the data-entry routine of a database
    program. This group of statements (used to receive, validate, and store
    data) must be repeated as long as the user wants to enter new data
    records. This set of repeating statements is called a loop because it is
    executed as though the statements were arranged in a circle. However, when
    the user wants to stop entering data, the program must be able to
    recognize a "quit" command and stop repeating the data-entry statements.

    As you study C, you will find many other examples of the need for
    controlled repetition. For example, a program that retrieves data from a
    file must repeatedly read and process data items until it reaches the end
    of the file. If you program in another language, you probably use loops
    regularly to initialize and access elements of an array or a set of

    C uses three types of loops: the for loop, the while loop, and the do
    loop. Although these loops are fundamentally similar, they let you control
    the looping action in different ways to suit different needs. This chapter
    focuses on how to use these three types of loops and some of their common

The for Loop

    The for loop repeats a group of program statements as long as a specified
    condition is true. Generally, you use it to specify a fixed number of
    repetitions: for example, processing the accounts for each month of the

    The anatomy of a for loop is as follows:

    for (start; condition; update)

    In this generalized for loop, start is one or more statements that
    initialize the variables used by the loop; condition is a relational
    expression that is tested to see whether the loop should continue to run;
    and update is one or more statements that change the values of variables
    in the loop. The group of statements between the braces that follow the
    for line is called the "body" of the loop. These statements execute as
    long as the condition in the parentheses is true. (The body can also
    consist of only one statement, in which case the braces are optional. We
    tend to use braces for even a single statement, however, because they make
    the body of the loop easier to distinguish.)

    The FORLOOP.C program (Listing 4-1) uses a for loop to count from 1 to
    10. After we declare the variable i, we begin the loop structure with the
    keyword for. The parentheses that follow the for contain the control
    statements for the loop. Note that semicolons separate the control

    /* forloop.c -- a simple for loop that   */
    /*              counts to ten            */

        int i;
        for (i = 1; i <= 10; i++)
            printf("%d\n", i); /* body of loop */
        printf("All done!\n");  /* executed when i > 10 */

    Listing 4-1.  The FORLOOP.C program.

    The start statement establishes the variable i as the loop's control
    variable. This is the variable whose value is tested to determine when the
    loop will stop running. (Many people use i, j, or k for loop control
    variables. This tradition owes its roots to FORTRAN. However, any legal
    name will do.)

    The next statement, i <= 10, is the loop's test, or condition. It
    specifies that the body of the loop execute repeatedly as long as the
    value of i is less than or equal to 10. The test condition is a relational
    statement that compares the loop control variable to an assigned value and
    returns a value of 1 (true) or 0 (false).

    The last statement in the for loop parentheses is i++. This update
    statement changes the value of the loop control variable each time the
    loop body executes. Here we use the ++ increment operator to increase i by
    one each time it executes, and, in fact, most for loops use update
    statements that either increment or decrement the control value by one.
    Using values other than one, however, is almost as easy: The statement
    value += 10, for example, adds 10 to value each time it executes. You can
    also use multiplication or division rather than addition or subtraction.

    Let's step through FORLOOP.C one statement at a time to see how it works:

    ■  Set i to 1.

    ■  Check i to see if it is less than or equal to 10.

    ■  Because the result of this test is true, execute the body of the loop.
        (The body consists of a printf() statement that prints the value of i.)

    ■  Execute the update statement, i++. (Set i to i + 1, or 2.)

    ■  Check the test statement again to see if i is still less than or equal
        to 10. If it is, execute the body of the loop again. Continue the cycle
        until the test condition is false (when the value of i increases to

    Figure 4-1 on the following page shows this program as a flowchart. You
    can follow the arrows to trace the flow of execution.

    Choosing a Control Variable
    If you are used to writing loops in BASIC, remember that with C, you must
    declare the loop control variable before you use it in the loop. Select a
    data type for the control variable that can accommodate the full range of
    values the variable will hold when the loop is run.

    For example, a loop that will run 50,000 times requires a control variable
    of type unsigned int because a signed int value cannot exceed 32,767.

        │  Initialize    │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
        │     i = 1      │                          ┌─────────▒─────────────────
        └───────┼────────┘                          │° for (i = 1; i <= 10; i++°
                │                                   │°               ▒       ▒ °
                ▼           ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒{▒▒▒▒▒▒▒▒▒▒       ▒ °
                /\          ▒         ▬  ▬  ▬       │°     printf("%d\n", i);▒ °
            /    \ ◄▒▒▒▒▒▒▒      ▬           ▬    │°     }        ▒        ▒ °
            /  TEST  \    No     ▬               ▬  │°              ▒        ▒ °
    ┌───►/  i <= 10   \ ──────► ▬      END      ▬  │°              ▒        ▒ °
    │    \     ▬      /         ▬               ▬  │°              ▒        ▒ °
    │      \ ▬ ? ▬  /             ▬           ▬    │°              ▒        ▒ °
    │        \ ▬  /                  ▬  ▬  ▬       │°              ▒        ▒ °
    │          \/                                  │°              ▒        ▒ °
    │          │ Yes                               └───────────────▒────────▒──
    │  ┌───────▼────────┐                                          ▒        ▒
    │Do body of loop │                                          ▒        ▒
    │  │  print f...    │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒        ▒
    │  └───────┬────────┘                                                   ▒
    │          │                                                            ▒
    │  ┌───────▼────────┐                                                   ▒
    │  │  Add 1 to i    │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
    │  └───────┬────────┘
    │          │

    Figure 4-1. The for loop.

    Why does the loop stop running? Let's look at the situation when i = 10:
    The printf() statement in the body of the loop prints the number 10. The
    update statement then increments the loop by 1 and the test statement
    executes. Because the value of i is now 11, the test fails (returns a
    value of "false"). This causes the program to skip the loop body and
    execute the next statement, which prints the message All done!

for Loop Style

    As with other C statements, the statements within the parentheses of a for
    loop can extend to more than one line if necessary. As noted in our
    discussion of conventions, we align the braces vertically for the loop
    body, as shown in FORLOOP.C. An older style aligns the braces as follows:

    for (i = 1; i <= 10; i++) {

    With this style, the braces can get lost in a long listing, making it
    difficult to find where the body of the loop begins and ends. Aligning the
    braces vertically makes them easier to spot and highlights the body of the

    Also note that we indent the body of a C loop to the right of the line
    that specifies it. To indent text in the QuickC editor, simply press the
    Tab key. The default indention in QuickC is eight characters, but you can
    change this value at the Options box of the View menu. We use a tab of
    four characters in our listings.

    Pitfalls to Avoid in for Loops

    An easy mistake to make when writing for loops is to put a semicolon after
    the closing parenthesis:

    for (i = 1; i <= 10; i++);───────────────────────────────Semicolon added

    This does not cause a compiler error: In C, a semicolon by itself is a
    "null statement." Such a statement does nothing, but it counts as a legal
    statement. Using a semicolon after the parenthesis makes the null
    statement the body of the loop. Adding the semicolon to FORLOOP.C causes
    the loop to do "nothing" 10 times; the program then prints the value of i
    (which is 11 after the loop exits) and All Done!

    Also, always remember to put braces before and after a loop body that
    consists of more than one statement. If you do not use braces, only the
    first statement following the parentheses executes as the body of the
    loop. The remaining statements will execute only once, after the loop
    terminates. (This is another reason for adopting the practice of always
    putting braces around the statements in a loop body, even when the body
    has only one statement.)

Multistatement for Loops

    FORLOOP.C has only one statement in the body of the loop, but most
    programs are much more complex. Let's develop a program that will print a
    table of square roots, squares, and cubes for the integers from 1 through
    9. Because this program must calculate and print three values for each
    number, it needs several statements in the body of the for loop.

    Using QuickC Library Functions

    To write such a program, we need a means of producing the square root of a
    number. Although C does not have operators for calculating squares or
    cubes directly, we can get these values simply by multiplying a variable
    by itself two and three times respectively. To get the square root,
    however, we must call on QuickC's sqrt() function. This function returns
    the square root of any value you pass to it. For example, if i = 4, then
    sqrt(i) = 2.

    The square root function, sqrt(), is an example of a QuickC library
    function (sometimes called a "library routine"). We've already used
    several QuickC "core" functions, such as printf() and scanf(). Because
    these functions are part of the QuickC environment, you can use them
    without any special commands. (Appendix B lists all the built-in core

    Quick Tip
    Sometimes it is convenient to break out of a loop during its execution.
    Perhaps you recognize a problem with its output, or perhaps you find
    yourself in a runaway loop──one whose test will not or cannot fail. To
    break out of a loop, press Ctrl-Break.

    However, sqrt() is not on this list. It is one of many library routines
    that are defined in the header files (often called "include files") of the
    \INCLUDE directory. One of the early tasks in learning QuickC is becoming
    familiar with its external library functions. Fortunately, QuickC makes it
    easy to explore the function library.

    QuickC's extensive on-line help screens let you call up a summary of any
    function to find out whether it is a core function or an external library
    function. To find out about sqrt(), select Topic from the Help menu. Next,
    select the appropriate topic, math; this produces a list of library
    functions that include the sqrt() function. When you select this function,
    a small help window appears at the top of the QuickC screen. (See Figure
    4-2.) The first entry in this window informs you that sqrt() resides in
    both the float.h and math.h include files.

    QuickC also lets you browse through include files while you are working on
    a program. Simply select Include from the View menu, select the \INCLUDE
    directory from the window (if necessary), and then select the include file
    you want to view. When you finish, select Open Last File from the File
    menu, and QuickC returns you to the program you were working on.

    Of course, the preferred reference for all QuickC library functions is the
    Microsoft QuickC Run-Time Library Reference, one of the manuals that come
    with QuickC. It introduces the library functions by category.

    │ Figure 4-2 can be found on p.98 of the printed version of the book.    │

    Figure 4-2. Library function help window.

    Using an Include File in a Program

    To use functions or other definitions from an include file in your
    program, you must specify the name of the file you want to call before the
    start of main(). For example:

    #include <graph.h>

    includes the file that contains graphics functions and definitions in your
    program. (The angle brackets that enclose the filename tell QuickC to look
    for the file in the default \INCLUDE directory, whose pathname the setup
    procedure stored in the environmental variable INCLUDE.) This statement is
    actually a "directive" to the QuickC preprocessor, a program that examines
    your C program code and looks for special commands that tell it to make
    changes in the program text before compilation begins. In this case, the
    #include preprocessor directive reads the contents of the specified
    include file into the program and compiles it as though you had typed it
    in. Only after it reads and compiles all the include files does QuickC
    compile your program statements. Note that preprocessor statements such as
    #include are not actually C language statements and do not end with a

    Creating a Program List

    We have seen that we need to use a #include statement if we want to refer
    to the sqrt() function in the program we want to run, TABLE.C (Listing
    4-2). In addition, before we compile the program, we need to tell QuickC
    where to find the compiled library code that corresponds to the definition
    of sqrt() in the include file math.h. We can do this by creating what is
    called a "program list." To do this, first select Set Program List from
    the File menu. In the dialog window (Figure 4-3 on the following page),
    type in the name of your program followed by the extension .mak. (Thus,
    for TABLE.C you type table.mak.) Next, select Edit Program List from the
    File menu.

    /* table.c -- prints square root, square, and cube */
    /*            for the numbers 1 thru 9             */

    #include <math.h> /* include math functions so we  */
                        /*  can do square root       */

        int i;
        printf("i\t sqrt(i)\tsquare(i)\tcube(i)\n\n");
        for (i = 1; i < 10; i++)
            /* beginning of body of loop */
            printf("%d\t", i);
            printf("%f\t", sqrt(i));
            printf("%d\t\t", i * i);
            printf("%d\n", i * i * i);
            /* end of body of loop */

    Listing 4-2.  The TABLE.C program.

    The dialog window, shown in Figure 4-4, lists all the C programs in the
    current directory. Select TABLE.C from the window with the mouse or
    keyboard, and then select the Add/Remove button. Finally, select the Save
    button to save the edited program list. You are now ready to run TABLE.C.

    │ Figure 4-3 can be found on p.100 of the printed version of the book.   │

    Figure 4-3. Set Program List dialog window.

    │ Figure 4-4 can be found on p.100 of the printed version of the book.   │

    Figure 4-4. Edit Program List dialog window.

    We use the #include <math.h> statement before the definition of the main()
    function to tell QuickC to use the math.h include file in the program. A
    printf() statement then prints the table header. Because we only want to
    print the header once, we don't place this statement inside the loop!

    Next comes the for loop. The loop specifications establish the test
    condition as i < 10 and the update as increments of 1.

    The body of the loop consists of four printf() statements: The first
    prints the value of i; the next three print, in order, the square root,
    the square, and the cube for each value. The program results in the
    following neatly formatted table:

    i        sqrt(i)        square(i)        cube(i)
    1        1.000000       1                1
    2        1.414214       4                8
    3        1.732051       9                27
    4        2.000000       16               64
    5        2.236068       25               125
    6        2.449490       36               216
    7        2.645751       49               343
    8        2.828427       64               512
    9        3.000000       81               729

Multiple Initializations and Calculations in for Loops

    An almost universal rule in C states that anywhere you can put a single C
    statement, you can put multiple statements. For example, in a for loop,
    you can initialize two variables in the first part of the loop
    specification, as follows:

    for (count = 1, total = 0; count < values; count++)
        total += count;

    Here we initialize the for loop by setting the loop control variable count
    to 1. At the same time, we set the variable total to zero. This loop adds
    all the integers between 1 and the number specified in values. Note that a
    comma separates the two statements in the initialization: Semicolons
    separate the three parts of the loop specification (start or
    initialization, test, and update); however, commas separate multiple
    statements within each part.

    Quick Tip
    If you want QuickC to search for a file in the current directory instead
    of the default include directory, enclose the filename in quotes: #include
    "graph.h". If you want QuickC to search another directory, specify the
    full pathname: #include "c:\qc\mydefs\defs.h".

    You can also use multiple calculations in the update portion of the loop
    specification. For example, we can rewrite the above loop as follows:

    for (count = 1, total = 0; count < values;
        total += count, count++)

    Here we moved the statement that added each new value of count to total
    out of the loop body and put it in the update part of the loop
    specification. However, a loop must have a body to be legal, so we added a
    single semicolon (a null statement) as the loop body. (The null statement
    is somewhat dangerous because you might accidentally delete the stray
    semicolon. We try to avoid this by indenting the semicolon to the loop
    body position. We also enclose the semicolon in braces. The braces are
    unnecessary, but they help to emphasize the importance of the semicolon in
    the loop structure.)

    The use of multiple statements and null bodies in loops is a matter of
    programming style. Many C programmers try to be as concise as possible, so
    you will often encounter these usages in C code. We present these variants
    to acquaint you with common C programming practices; you gain no
    performance advantage by doing all initializations and calculations within
    the loop specification.

    The INFLATE.C program (Listing 4-3) is another example that uses multiple
    initializations and calculations. At first glance, you might think that
    the braces in the for loop have been forgotten or misplaced.

    /* inflate.c -- shows multiple initialization */
    /*              and calculations in for loop  */

        int year;
        float value, rate;
        printf("What do you think the inflation rate will be?");
        scanf("%f", &rate);
        printf("If the dollar is worth 100 cents in 1987\n");
        printf("and the inflation rate is %2.2f, then:\n", rate);

        for (year = 1988, value = 1.0; year <= 1999;
            value *= (1.0 - rate),
            printf("in %d the dollar will be worth", year),
            printf(" %2.0f cents\n", value * 100), ++ year)

    Listing 4-3.  The INFLATE.C program.

    The program asks you to estimate the average inflation rate for the next
    decade or so. (We're sure your guess is as good as ours!) If you enter
    .06, the program generates the following:

    What do you think the inflation rate will be? .06
    If the dollar is worth 100 cents in 1987
    and the inflation rate is 0.06, then:
    in 1988 the dollar will be worth 94 cents
    in 1989 the dollar will be worth 88 cents
    in 1990 the dollar will be worth 83 cents
    in 1991 the dollar will be worth 78 cents
    in 1992 the dollar will be worth 73 cents
    in 1993 the dollar will be worth 69 cents
    in 1994 the dollar will be worth 65 cents
    in 1995 the dollar will be worth 61 cents
    in 1996 the dollar will be worth 57 cents
    in 1997 the dollar will be worth 54 cents
    in 1998 the dollar will be worth 51 cents
    in 1999 the dollar will be worth 48 cents

    The program uses scanf() to obtain the estimated inflation rate. Then it
    prints the introduction to the table and enters a for loop. Because the
    table prints yearly values, we call the loop control variable year. (Note,
    by the way, that control variables need not start at 0 or 1.) The
    initialization part of the loop also sets value to 1.0. (In other words,
    the dollar starts at its full value.) The test part of the loop causes the
    printing of values for the years 1988 through 1999.

    The update part of the loop specification does the work of this loop──the
    loop has a null body. Each year the current value is multiplied by 1.0 -
    rate to show the effects of inflation. The arithmetic assignment operator,
    *=, causes this new amount to become the new value. The printf()
    statements print the amount in cents by first multiplying value by 100;
    the format specifier %2.0f rounds it off to whole cents. Finally, ++year
    increments year, and the loop is ready for another pass.

    A for Loop Using Characters
    Because the PC's ASCII character set is merely a set of integer values
    from 0 to 255, a for loop can process characters as easily as it does
    ordinary integers. For example, the int control variable can be
    initialized by setting it to the character value `a'. There is no problem
    with this, because `a' is simply the integer value 97. Then, by specifying
    a loop test condition such as i <= `m', you determine that the body of the
    loop will be repeated 13 times, once for each letter in the first half of
    the alphabet.

Nesting for Loops

    Sometimes it is useful to have one of the statements in the body of a loop
    be another loop. This is called nesting loops. For example, you might
    design a program to read a disk data file that is arranged so that each
    line contains four data fields. An "outer" loop could process each line,
    and an "inner" loop could process each field. The outline for this program
    might be:

    for (line = 1; line <= last_line; line++)
        for (field = 1; field <= 4; field++)

    The first, or outer, loop uses the control variable line and the test line
    <= last_line to read each line of the file in turn. (In this example, we
    assume that the number of lines in the file has been previously
    determined.) The inner for loop uses the control variable field to step
    through the four fields of each line. The body of this nested loop calls
    the function process_field to do the actual reading of data. When the last
    field in the line is processed, the inner loop exits. Because we are still
    in the body of the outer loop, the outer loop continues by moving to the
    next line; then the inner loop runs again. Only when the inner loop runs
    for the last line do the nested loop structure and the save_file()
    statement execute.

    Our next sample program, GRAPHBOX.C (Listing 4-4 on page 106), uses
    several for loops, including a pair of nested ones, to draw a box on the
    screen using PC graphics characters.

    As we mentioned earlier, the IBM PC uses the ASCII values from 128 to 255
    to represent the "extended character set," which includes many shapes that
    you can use to create effective graphics. To find the appropriate
    characters for drawing a box, select the help screens for ASCII characters
    from the Help menu (or press the F1 key). The second of the two screens
    contains the extended characters. For example, with character number 201
    you can draw the upper-left corner of the box.

    To display these characters, you must use the QuickC core library function
    called putch(). Call the function by specifying the ASCII code of the
    desired character in parentheses. For example, to draw the corner
    character mentioned above, specify:


    Using #define

    Our box-drawing program uses many different characters to represent the
    corners and sides of the box, plus the newline, return, and blank
    characters. Remembering the ASCII codes for all these characters is a
    difficult task, and relying on memory could lead to coding mistakes. But C
    has a feature that helps eliminate this problem.

    C provides a mechanism for assigning symbolic names to frequently used
    values in a program. The preprocessor directive #define lets you specify a
    name and assign a value to it, as in the following example:

    #define UPLEFT 201

    Before QuickC compiles your program, the preprocessor finds each
    occurrence of the name UPLEFT and replaces it with the number 201. You
    remember the name; QuickC remembers the number. You can also use #define
    with characters. If you use the definition #define color "green" and you
    use the statement printf(color); in your program, the preprocessor
    translates the statement into printf("green"); before QuickC compiles it.

    Always place #define statements before the definition of main(), and do
    not end them with a semicolon. (If you use a semicolon, the preprocessor
    will treat it as part of the value to be substituted. This often leads to
    a bug that causes a compiler error.)

    You can use #define to make your code more readable by substituting
    easy-to-remember names for numbers. For example, you could use NL for the
    newline character instead of 10, the newline ASCII value. Also, #define
    makes it easy to change many values in a program without having to change
    many individual statements.

    #define vs Variables
    You might ask why you should use #define when you could do the same thing
    more easily with ordinary variables. After all, you could declare int nl =
    10; and then use putch(nl); (to simplify punctuation) and thereby avoid
    the preprocessor step. But there are two reasons why this isn't a good

    First, using #define produces more efficient code than does using a
    variable. When your program uses variables, QuickC must compile extra
    machine instructions to store, change, or fetch the needed values. With
    #define, on the other hand, the preprocessor compiles the values directly
    into the compiled code: The program doesn't need any extra instructions.
    As a result, your compiled code is faster and more compact.

    Second, a variable should represent a quantity that is subject to change
    by the program. The ASCII value 10 for a newline character, however, is a
    constant. Using #define guarantees that the value you define cannot
    accidentally be changed while the program is running.

    Incidentally, the new ANSI C standard creates the keyword const to let you
    avoid #define directives. Using the new keyword, you might declare const
    int nl = 10; to define the constant nl.

    /* graphbox.c -- defined to use PC-specific graphics characters */

    #define NL 10
    #define CR 13
    #define BLANK 32
    #define UPLEFT 201
    #define UPRIGHT 187
    #define LOWLEFT 200
    #define LOWRIGHT 188
    #define LINE 205
    #define SIDE 186

        int i, j, height, width;

        /* get height and width from user */
        printf("How high a box do you want? ");
        scanf("%d", &height);
        printf("How wide do you want it to be? ");
        scanf("%d", &width);

        /* draw top of box */
        for (i = 0; i < (width - 2); i++)
        putch(CR); /* go to next line */

        /* draw sides of box */
        for (i = 0; i < (height - 2); i++)  /* outer loop */
            putch(SIDE); /* left side */
            for (j = 0; j < (width - 2); j++) /* inner loop */
            putch(SIDE); /* right side */
            putch(CR); /* move to next line */

        /* draw bottom of box */
        for (i = 0; i < (width - 2); i++)
        putch(CR); /* box is done, move cursor to new line */

    Listing 4-4.  The GRAPHBOX.C program.

    To continue with the previous example, if you convert your program to run
    on a mainframe that uses a non-ASCII character set, you need only to
    change the value of NL in the #define statement to reflect the new
    character value throughout the program.

    When you run the GRAPHBOX.C program, it asks:

    How high a box do you want? 8──────────────────────Enter height in lines
    How wide do you want it to be? 20──────────────Enter width in characters

    Figure 4-5 shows the graphics box that this program generates on your

    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║

    Figure 4-5. Character graphics box produced by GRAPHBOX.C.

    The program begins with nine #define statements that name the needed
    characters and their values. The first section of main() prompts the user
    for a height and width. Then a putch() displays the character for the
    upper-left corner of the box. Next, a for loop prints a graphics
    double-line character width - 2 times. (We print two less than width
    characters to leave room for the upper-left and upper-right corner

    The third section of main() draws the sides of the box. After subtracting
    the top and bottom lines, we want to print height - 2 lines: This is
    provided for by the test statement in the next for loop. For each line,
    the program prints the SIDE character (the double bar) and then uses a
    nested for loop to print width - 2 blank characters to position the cursor
    at the right side of the box. Another SIDE character completes the line;
    then an NL and a CR move the cursor to the next line.

    The statements that print the bottom line are the same as those that
    printed the top line, except that they use the special characters for the
    lower-left and lower-right corners of the box.

The while Loop

    C contains another loop structure, called the while loop, which takes the
    following general form:

    while (test)

    Structurally, the while loop is a for loop with only the test part of the
    specification, its condition, in parentheses. You initialize loop
    variables in a statement before the while, and you update or increment the
    loop with a statement in the loop body. Thus, although the for loop
    features compactness and holds the entire loop specification in the
    parentheses, the while loop is easier to read because the parentheses
    contain only the test expression. The WHILE.C program (Listing 4-5 at the
    bottom of the page) shows a simple example.

    The program produces the following output:


    The statement int count = 1; declares and initializes the loop control
    variable. At the while statement, the condition count < 11 is tested.
    Because it is true, the body of the loop executes. The body consists of a
    printf() statement that prints the current value of count, and the
    statement count++; which increments count. The test condition is then
    checked again, and the loop continues printing numbers until count reaches
    11. At this point the test fails, the loop terminates, and the statement
    printf("Done!\n"); executes. Figure 4-6 shows a flowchart of this while

    /* while.c -- a simple while loop */

        int count = 1;

        while (count < 11)  /* loop condition */
            /* body of loop */
            printf("%d\n", count);

    Listing 4-5.  The WHILE.C program.

    At this point you might ask why you need while statements if they are
    merely variant forms of for loops. The answer is simple. A for loop is
    designed to work with a specific series of values (such as numbers from 1
    to 10 or 1 to total_lines), and it usually counts up or down. A while
    loop, however, is designed to run indefinitely as long as some condition
    remains true. It also can test many kinds of conditions.

    For example, suppose you want to write a program that draws endlessly
    changing graphic patterns until the user presses a key to stop it. A while
    loop is ideal for this purpose when used with the QuickC library function
    kbhit(), which returns a 1 (true) if a key is pressed and a 0 (false) if
    no key is pressed. (A loop that waits for some external event to take
    place is called a "polling loop.") The main loop of your graphics program
    might appear as follows:

    while (!kbhit())
        draw statements;

    The draw statements create the graphics while the test part of the while
    loop specification polls the keyboard. As long as the user does not press
    a key, the kbhit() function returns a 0, or false. Notice that we use an
    !, which is the "logical not" operator, in front of kbhit(). "Not false"
    is the same as "true," so the test for the while loop is satisfied and the
    body of the loop executes as long as the user doesn't press a key. If you
    find this reverse logic difficult to understand, try translating the loop
    specification into words:

    while───────────────────────────────────────────────────────"As long as"
    kbhit()─────────────────────────────────────────────────"key is pressed"

        ┌────────────────┐                           ┌─────────────────────────┐
        │  Initialize    │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒int count = 1         °│
        │   count = 1    │                           │°                       °│
        └────────────────┘  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒while (count < 11)    °│
                ▼           ▒                        │° {                     °│
                /\          ▒         ▬  ▬  ▬       ▒▒▒▒printf("%d\n", count);°│
            /    \ ◄▒▒▒▒▒▒▒      ▬           ▬    ▒│°                       °│
            /  TEST  \    No     ▬               ▬  ▒▒▒▒count++;              °│
    ┌───►/count < 11  \ ──────► ▬      END      ▬  ▒│° }                     °│
    │    \     ▬      /         ▬               ▬  ▒│°                       °│
    │      \ ▬ ? ▬  /             ▬           ▬    ▒│°                       °│
    │        \ ▬  /                  ▬  ▬  ▬       ▒└─────────────────────────┘
    │          \/                                  ▒
    │          │ Yes                               ▒
    │  ┌───────▼────────┐                          ▒
    │Do body of loop │                          ▒
    │  │  print f...    │◄▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
    │  │  count++;      │
    │  └───────┬────────┘
    │          │

    Figure 4-6. The while loop.

    When the user presses a key, kbhit() returns "true," but the logical not
    operator reverses the result into "not true," or 0, and the loop exits.

    Note that a polling while loop needs no counter variable or incrementing.
    The while loop needs only to test a condition that will eventually change.
    (If the condition never changes, the program never stops.)

Using while to Animate a Character

    We can nest while loops much as we nested for loops. The ANIMATE.C program
    (Listing 4-6) uses a set of nested while loops to produce simple
    animation──making a character appear to move back and forth across the

    When you run ANIMATE.C, a "double arrow" graphics character (» or «) and
    the flashing cursor move back and forth across the screen until you press
    a key.

    ANIMATE.C starts with #define statements that specify the right arrow and
    left arrow PC graphics characters as well as the backspace and blank
    characters, which we use for moving the cursor back and for erasing the
    previously drawn arrow.

    The outer loop uses while (!kbhit()), which keeps the program running
    until you press a key. The inner while() loop moves the arrow to the right
    by repeatedly

    1.  displaying the right arrow character,

    2.  backing up the cursor,

    3.  erasing the previously displayed arrow by overprinting it with a blank
        space character, and

    4.  incrementing pos to display an arrow in the next space.

    (Remember, the blank moves the cursor to the space with the displayed
    arrow.) A while loop then tests pos to stop the arrow when it reaches the
    right side of the screen (position 79).

    Notice the two nested while loops that we use to slow the display so the
    eye can follow it. This loop simply counts to 1000. We often use delay
    loops such as this to slow a program to accommodate human perception or
    peripheral devices that cannot keep up with the CPU. (Sophisticated delay
    loops read and use the system clock.)

    Another set of while loops moves the arrow from the right side of the
    screen to the left side. You should have little trouble figuring out how
    it works. Note that the arrow must move backward, so we decrement (rather
    than increment) pos and test for pos > 1 to see when the arrow reaches the
    left side of the screen.

    What happens when the arrow reaches the left side of the screen? The body
    of the outer loop finishes, and control returns to the test statement in
    the outer loop. Assuming no key has been pressed, the body of the loop
    then executes again.

    /* animate.c -- animates a graphics character */
    /*              until a key is pressed        */

    /* Special characters */
    #define RTARROW 175
    #define LFTARROW 174
    #define BLANK 32
    #define BACKSPACE 8

        int pos, i, j = 1;
        while (!kbhit())
            pos = 1;
            while (pos < 79)
                i = 1;
                while (i < 1000)
                    j = i + 10;
            while (pos > 1)
                i = 1;
                while (i < 1000)
                    j = i + 10;

    Listing 4-6.  The ANIMATE.C program.

Combining while and for Loops

    The following program, MIXLOOPS.C (Listing 4-7), accepts a character from
    the user and counts through the alphabet until it reaches the character,
    beeping once for each count. The loop continues to accept characters until
    the user enters a blank. (Note: As written, the program works only with
    lowercase alphabetic characters; you can extend it to accept others by
    changing the starting value of the variable i.)

    A session with MIXLOOPS.C might run as follows:

    c──────────────────────────────────────────────────User enters character
    In FOR loop!─────────────────────────────Beeps each time line is printed
    In FOR loop!
    In FOR loop!
        ───────────────────────────────────────User enters blank to end program

    The outer loop, a while loop, contains the test:

    while ((ch = getche()) != ' ')

    This introduces another noncore QuickC library function called getche(),
    which stands for "get character with echo." This function accepts a
    character from the user and echoes (displays) it on the screen. Because
    this function is located in the conio.h include file, you must add the
    appropriate #include line before the definition of main().

    An important feature of this loop is its use of a function call whose
    value is both assigned to a variable and tested in the loop condition.
    We'll learn more about function calls in the next chapter.

    /* mixloops.c -- reads characters,       */
    /*               beeps for ASCII count,  */
    /*               uses a while and a for  */
    #include <conio.h>

        char ch;
        int  i;

        while ((ch = getche()) != ' ') /* get a char. */
            for (i = 'a'; i <= ch; ++i) /* count up to alphabet pos.*/
                    printf("In FOR loop!\n");
                    printf("\a");  /* sound beep each time */

    Listing 4-7.  The MIXLOOPS.C program.

    When the user enters a character, the assignment ch = getche() assigns the
    ASCII value of the character to the variable ch. The not equals operator
    != then compares the character value to the ASCII value for the blank
    character, specified as '  '. This results in a true or false value that
    the while loop tests.

    If the user does not enter a space, the for loop, which makes up the body
    of the while loop, executes. The for loop tests for i <= ch. Thus, if the
    user enters the character f, the loop counts from the ASCII value of `a'
    to that of `f': The body of the for loop executes one time each for the
    values `a' through `f', and you hear six beeps.

    MIXLOOPS.C is a good example of the appropriate use of while and for
    loops. The outer, while loop waits indefinitely for a condition to change
    (the user enters a space); the inner loop, the for loop, counts to a
    definite value (the ASCII value of the character entered in the while

The do Loop

    The third (and final) C looping structure is the do loop, which takes the
    following general form:

    while (test);

    The do loop is very similar to the while loop, with one major exception──
    the while loop performs the test and then executes the body of the loop;
    the do loop executes the body of the loop and then performs the test.
    Thus, the body of a do loop always executes at least once, even if the
    result of the first test is false.

    The DO.C program (Listing 4-8) demonstrates a simple do loop that
    performs the now-familiar task of counting from 1 to 10.

    /* do.c -- a simple do-while loop */

        int i = 1;
            printf("%d\n", i);
        while (i < 11);

    Listing 4-8.  The DO.C program.

    Of the three C looping structures, the do loop is by far the least used.
    Usually when you test for a change in condition, a while loop is more
    appropriate because you want the program to react immediately to user
    input, especially a "quit" command.

    Use the do loop to repeat an action until some condition changes only when
    the test need not be made immediately. A good example is the TIMER.C
    program (Listing 4-9).

    /* timer.c -- uses do loop to    */
    /*            check elapsed time */
    #include <time.h>

        long start, end, /* starting and ending times */
                        /* measured in seconds since */
                        /* Jan. 1, 1970 */
            ltime;      /* used to get val from time function */
        int seconds;     /* elapsed time to be set */

        printf("QuickC Egg Timer\n");
        printf("Enter time to set in seconds: ");
        scanf("%d", &seconds);
        start = time(&ltime);  /* get system elapsed seconds */
                                /* since 1-1-70 */
        end = start + seconds; /* calculate alarm time */

            {;}                       /* null statement for loop body */
        while (time(&ltime) < end);   /* wait for alarm time  */

        printf("Time's Up!\a\a\a\n");

    Listing 4-9.  The TIMER.C program.

    Quick Tip
    Pascal programmers should note the similarity of C's do and Pascal's
    repeat until loops. The difference is that the C do loop repeats the body
    until the specified condition is false, whereas the Pascal loop repeats
    the body until the condition is true.

    This program lets you specify a time in seconds, after which the program
    beeps three times and prints Time's Up! The program uses the noncore
    library function time(), which, when given the address of a long type
    variable, stores in that variable the number of seconds that have elapsed
    since Jan. 1, 1970, as measured by your PC's clock. As you would expect,
    the function also returns this value for use in the calling statement.

    After the initial messages are printed and the user enters a number, the
    program calls the time() function. Because this function, like scanf(),
    requires an address as its parameter, you must call the function as
    time(&ltime), using the address operator & to specify the address of the
    long variable ltime. The returned value, the elapsed seconds from Jan. 1,
    1970 to the second the user enters a number, is assigned to the variable
    start. We add the number of seconds specified by the user to this variable
    and store the result in the variable end. This variable thus contains the
    number of elapsed seconds at which the program will terminate.

    The do loop then begins. Because this is a timer program and the user
    wants to wait some period of time, the test does not need to be performed
    before the body of the loop executes, so the do loop is appropriate. The
    body of the loop is a null statement──all we want to do is wait. The test
    while (time(&ltime) < end) repeatedly calls the time() function and checks
    the returned value until the elapsed time exceeds the value in end.

Debugging and Loops

    It's rare for a program to work correctly the first time you run it.
    Debugging is the art of knowing what to look for in a program that has
    errors and of correctly interpreting what you see. Some common errors in C
    programs that involve elements we have already discussed include:

    ■  Syntax errors

    ■  Uninitialized variables

    ■  Wrong or incompatible data types

    ■  Incorrectly specified loops

    Throughout this book we point out common programming errors. Syntax errors
    are the easiest to fix: The compiler enforces the rules of C syntax and
    informs you when and where you have erred. (Sometimes, though, you must
    sort out the real problems from the syntax errors that occur as a result
    of an earlier error!)

    True bugs are much harder to detect and fix because they cannot be
    detected by QuickC. We'll define a "logic bug" as an error that does not
    violate the rules of C but generates program results that are either
    completely or partially incorrect. For example, C contains no rule that a
    variable must be initialized. There is no rule preventing you from
    assigning the result of a double calculation to an int variable. And
    loops, with their sometimes complex conditions and specifications, offer
    plenty of opportunity for bugs, such as the problem that arises when you
    use a semicolon after a for loop specification.

    Until recently, debugging a C program was a tedious process that involved
    putting printf() statements in strategic parts of a program to reveal the
    values of key variables or the order in which program statements executed
    (or both). Then came programs called "debuggers" that could run and report
    on a C program. (If you bought QuickC with Microsoft C 5.0, you also
    received CodeView, a sophisticated debugger.) QuickC represents the next
    step in the evolution of debugging: The debugging features are built into
    the QuickC environment itself. (QuickC's debugger only works in the medium

    The BUGS.C program (Listing 4-10) is a bug-ridden program that we will
    fix using the QuickC Debug menu and facilities. It features a while loop
    and is supposed to let the user enter as many as five numbers and get
    their total and average.

    Type this program exactly as shown and run it. (If you spot some bugs
    along the way, give yourself a star. But please type the program as shown
    so you can step through the debugging exercise properly.) When you run the
    program, this is what happens:

    Continue (y/n)? y───────────────────────────────────────────────Type `y'
    Enter a number:
    Enter a number:
    Enter a number:
    Enter a number:
    Enter a number:

    /* bugs.c -- for practice with debugger */

        char response;
        int number, max_numbers = 5, count = 0, total = 0;
        float average;

        printf("Continue (y/n)? ");
        response = getche();
        while ((response != 'n') && (count < max_numbers))
            printf("\nEnter a number: ");
            scanf("%d", &number);
            total += number;
            printf("Continue (y/n)? ");
            response = getche();
        average = total / count;
        printf("\nTotal is %d\n", total);
        printf("Average is %f\n", average);

    Listing 4-10.  The BUGS.C program.

    The program is running out of control. To break out of it and return to
    the QuickC environment, press Ctrl-Break. Now the screen displays the
    following messages:

    Enter a number:
    Enter a number:

    run-time error R6014 : (1 of 1)
    - control-BREAK encountered

    Program returned (255).  Press any key

    Press a key to return to QuickC. (You can ignore this error message.)

    Now you need to figure out which bug or bugs caused the program to fail.
    Because the program uses a while loop, it seems likely that something is
    causing one statement of the loop to be repeated endlessly. To debug the
    program, you must first select the Compile menu and then select the Debug
    option. (The Debug menu is shown in Figure 4-7.) This tells QuickC to
    gather debugging information as it compiles the program. Now select Build
    Program from the bottom of the Compile menu to recompile the program with
    debug information. Then go to the Debug menu and select Trace On. (This is
    a toggle setting──select it to turn it on, denoted by a check mark to the
    left of Trace On; select it again to turn it off.) Trace On highlights the
    statement currently being executed (in color if you have a color display).
    This lets you easily follow the flow of the program as it executes.

    │ Figure 4-7 can be found on p.117 of the printed version of the book.   │

    Figure 4-7. The Debug menu.

    Another option in this menu is Screen Swapping. When you turn on Screen
    Swapping, QuickC alternates between the output screen and the QuickC
    environment screen as every statement executes. This causes a flickering
    effect that can be annoying. Even with Screen Swapping off, QuickC shows
    the output screen whenever any statement produces output or requests
    input. We suggest you leave Screen Swapping off. Finally, select Start
    from the Run menu to run the program.

    As the program runs, notice that the statement currently being executed is
    highlighted. (If you have a color monitor, you can change colors by
    selecting Options from the View menu.) The first printf() statement
    executes and is followed by the scanf() statement that solicits a
    response. Type y to continue.

    After that, the program runs away. Notice that the loop specification line
    and the next printf() line execute continually. Do you see why? We didn't
    use braces to mark the beginning and end of the loop body. Therefore, the
    printf() line is executed as the body of the loop. Because this statement
    doesn't get or change values for either of the loop's two control
    variables, response and count, the loop test never becomes false, and the
    loop never terminates. Now you can stop the program (with Ctrl-Break) and
    insert the braces before and after the indented lines.

    This example illustrates how easy it is to use the QuickC debugger. You
    simply turn on the debugging features, observe the problem, and go back to
    the program to fix it. Because everything is done in the same QuickC
    environment, you don't have to save or reload any files.

    Controlling the Debugger from the Keyboard
    It is often easier to use keyboard commands rather than menu selections to
    debug a running program. You can use the following QuickC keyboard
    commands while debugging a program:

    Function Key      Result
    F8                Execute next statement, trace through function
    F10               Execute next statement, trace around function
    F7                Execute until current cursor position is reached
    F4                Display the output screen

    (When a statement calls a function you have defined elsewhere, F8 traces
    through the definition of the function. F10, on the other hand, does not
    detour to trace a called function.)

    Now run the program again. As the highlight moves on the screen, notice
    that the whole body of the loop executes. That's an improvement. You now
    can run BUGS.C and enter a series of numbers to be totaled and averaged.
    Let's say you enter three numbers──8, 12, and 10. This is what happens:

    Continue (y/n)? y
    Enter a number: 8
    Continue (y/n)? y
    Enter a number: 12
    Continue (y/n)? y
    Enter a number: 10
    Continue (y/n)? n
    run-time error R6003
    - integer divide by 0

    Program returned (255).  Press any key

    Clearly the program still doesn't work right. A look at the listing shows
    that the program is supposed to add each new number to total and, after
    the last number is entered, divide total by count to get average.
    Apparently count is still zero when the loop exits, thus triggering the
    divide by zero error. Why? To find out, let's use another feature of the
    QuickC debugger, "watch variables."

    Move the cursor in the text area to the variable name count. Select the
    Debug menu again, and then select Add Watch. The window shown in Figure
    4-8 appears. The Watch window is a device that lets you designate program
    variables for QuickC to monitor. When the value of one of these variables
    changes, QuickC displays its new value in a window at the top of the
    screen. This eliminates the need to put extra printf() statements in your
    program to monitor variables.

    │ Figure 4-8 can be found on p.119 of the printed version of the book.   │

    Figure 4-8. Adding a watch variable.

    Because you already selected it with the cursor, the word count appears in
    the window. Simply select OK to add count to the list of watch variables.
    (To remove watch variables later, select Delete Last Watch or Delete All
    Watch from the Debug menu.)

    You can also specify another display format for the value of the watch
    variable by adding a comma and a format specifier to the variable──for
    example, count,d──which specifies an integer format display for count. The
    format specifiers are similar to those you used with printf() and scanf().

    Next, select Add Watch twice and add the variables number and total to the
    watch list. (Either position the cursor on the names or type them in the
    dialog box.)

    Finally, let's set a breakpoint. This is a place in the program at which
    execution will stop. This lets you examine the status of the watch
    variables. Move the cursor to the last line in the body of the while
    loop──response = getche();──and choose Toggle Breakpoint (or press F9)
    from the Debug Menu. (Select the toggle again with the cursor on the same
    line to remove the breakpoint, or choose Clear All Breakpoints. You can
    set any number of breakpoints.)

    Now you're ready to run the program again, so choose Start from the Run
    menu. Again, the program prompts you for numbers, and you can watch the
    statements in the while loop as they execute. The program stops at the
    breakpoint at the end of the body of the loop. At the top of the screen, a
    small window lists the watch variables and their current values. After you
    inspect them, select Continue from the Run menu (or press F5) to resume
    program execution. (Figure 4-9 shows the screen display with the current
    statement and breakpoint highlighted; the watch variable information
    window is at the top.)

    │ Figure 4-9 can be found on p.120 of the printed version of the book.   │

    Figure 4-9. Debugging in progress.

    As the loop cycles, notice that number accepts the value of the number you
    entered, and total grows as you add new numbers. But count always remains
    zero. Have you figured out why? We forgot to put a statement in the body
    of the loop that increments count. Adding count++; after total += number;
    completes our debugging of the program.

    You can do more complex things with the debugger, so be sure to read
    Chapter 8 of the Microsoft QuickC Programmer's Guide for more information.
    For example, when you learn about arrays and structures, you can use watch
    variables to display them, too. Meanwhile, you also can use the debugger
    as a learning tool for tracing the flow of programs in this book, the
    sample programs provided by Microsoft, or other C programs.

Chapter 5  Decisions and Branching

    All programming languages must be able to perform controlled "branching."
    Branching uses the result of a test or condition to determine which
    statement (or group of statements) will execute next. In this chapter we
    discuss the variations of branching in C and learn how to use them with
    looping statements.

The if Statement

    In C, as in most languages, the if keyword introduces a branching
    statement. The following structure is the simplest form of branching:

    if (condition)

    An if statement, like a while loop, evaluates a condition first. The
    condition can be any combination of values and relational or logical
    operators that yields a true (nonzero) or false (zero) value──answer ==
    `y', for example. If the condition is true, the following statement (or
    group of statements in braces) executes. (As with loops, the statement or
    statements controlled by the condition are called the "body" of the
    statement.) If the condition is false, the following statement or group of
    statements does not execute, and execution continues with the next
    statement or group of statements. A simple example follows:

    if (balance < 0)
        printf("Your account is overdrawn!\n");
    printf("Your current balance is %8.2f\n", balance);

    If the customer's balance is less than zero, the first printf() statement
    executes, telling the customer the account is overdrawn; then the second
    printf() statement, which prints the current balance, executes. If the
    customer's balance is zero or more, the condition is false, and the first
    printf() statement does not execute──the program skips it. Only the second
    printf() statement executes.

    In the above example, we indent the first printf() statement to show that
    the if controls it──it is the body of the if statement. (In C, we indent
    statements for our benefit only: The compiler doesn't require indention.)
    Always enclose the condition in parentheses, and do not use a semicolon
    directly after the parentheses because the complete if statement includes
    the if, the condition, and the statement body.

    The IF.C program (Listing 5-1) features the if statement.

    /* if.c -- simple IF statement */

    char ch;
        printf("Do you want to continue y/n? "); /* prompt */
        if (ch = getche() == 'y')
            printf("\nLet's continue ...\n");    /* if true */
        printf("\nAll done.\n");                 /* always executed */

    Listing 5-1.  The IF.C program.

    Quick Tip
    If you know BASIC or Pascal, note that C does not use the then keyword
    before the body of the if statement. Most other languages use the
    following form for the if statement:

    if (condition)
        then statement(s)

    If you mistakenly use then with if, the QuickC compiler will catch the
    error, of course, and you will soon stop making it.

    IF.C asks the user if he or she wants to continue. The test expression ch
    = getche() == `y' gets the response character, assigns it to ch, and tests
    it. The program generates one of two responses:

    Do you want to continue y/n? y──────────────────────────────User types y
    Let's continue ...
    All done.

    Do you want to continue y/n? n─────User types any character other than y
    All done.

    Note that the program prints Let's continue ... only if the user types y;
    however, it always prints All done.

    The if statement represents a fork in the road: One of two possible
    courses is followed, depending on the result of the test. The flowchart in
    Figure 5-1 depicts such a branch, with the test shown in a diamond-shaped

                            No │                                 │
        ┌───────────┐         /\          ┌───────────┐    ┌─────▼─────┐
        │ printf    │       /    \        │ printf    │    │ printf    │
        │ ("Do you  │     /   if   \      │ ("\nLet's │    │ ("\nAll   │
        │ want to   ├──►/ch=getche() \───►│ continue  ├───►│ done.\n");│
        │ continue  │   \   =='y')   /    │ ...\n");  │    │           │
        │ y/n");    │     \        /      │           │    │           │
        │           │       \    /        │           │    │           │
        └───────────┘         \/          └───────────┘    └───────────┘

    Figure 5-1. Flowchart for the if statement.

Comparing if and while

    Notice the structural similarity of the following two statements:

    if (score > 90)


    while (question <= total_questions)

    Both statements test a condition and (if the condition is true) execute
    the following statement. The important difference between the two
    constructions is that the if statement executes the body of the statement
    only once; but the while statement executes the body repeatedly (as long
    as the test continues to be true).

    If you think about the two statements, you can see why each is appropriate
    for its assigned task. A statement that prints the final score of a quiz
    needs to be executed only once. On the other hand, a statement that calls
    a function that asks the next question in the quiz must be executed

Using a Group of Statements with an if

    The body of an if statement can contain any number of statements. Consider
    the following example:

    if (choice == 'd')
        printf("How much do you want to deposit? ");
        scanf("%f", &deposit);
        balance += deposit;
        printf("Thank you. Your new balance is ");
        printf("%8.2f", balance);

    When choice is `d', the program executes all five statements between the
    braces. Notice that we use the same indention for the braces and the

Nested if Statements

    Just as the body of a loop can contain another loop, the body of an if
    statement can contain another if statement. For example, a simple text
    formatter might use the following code fragment:

    if (pos == line_length)
        if (++line_count > lines_page)

    If the first if statement is true (the character position equals the line
    length), the program executes the body of the statement, which is itself
    an if statement. This statement increments line_count by one, and if the
    result is greater than lines_page, the body of the inner if statement
    executes. These statements print a footer, output a "form feed" character,
    add one to the page number, and print a header for the next page. (Because
    we must repeatedly test for the end of line and the end of page, we would
    actually place these if statements inside a while loop. As you might
    expect, you will often use if statements inside loops, and we will show
    you examples of these later in this chapter.)

Providing Alternatives with else

    The if statement has an adjunct──else──that is useful for executing a
    statement or group of statements only if the given condition is false. The
    general form of the if-else statement is simply an extension of the simple
    if statement:

    if (condition)

    Consider the following example:

    if (age >= 18)
        printf("To vote, enter number of candidate: ");
        scanf("%d", &candidate);
        printf("Sorry, you must be at least 18 to vote.\n");

    The first group of statements executes only if age is greater than or
    equal to 18. The statement following else executes only if that condition
    is false. If-else statements let you provide appropriate responses for
    both true and false results.

    Note that we align the else with its corresponding if because together
    they form one if statement of two parts. Correspondingly, we also indent
    the statement(s) controlled by the else to match the statements under the

    The IFELSE.C program (Listing 5-2) uses an if statement with an else to
    simulate the logon sequence for a bulletin board system.

    /* ifelse.c -- IF with ELSE */

    char ch;
    int num;
        printf("Are you a new user? y/n? ");
        if (ch = getche() == 'y')
            /* executed if IF is true */
            printf("\n\nYou must register to use this\n");
            printf("bulletin board. Please read\n");
            printf("Bulletin #1 first. Thank You.\n");
            /* executed if IF is false */
            printf("\n\nEnter your secret number: ");
            scanf("d", &num);

    Listing 5-2.  The IFELSE.C program.

    If the user replies y to the question Are you a new user?, the statements
    following the if execute. If the user types n (or anything else), the
    statements following the else execute instead.

    Here is a sample dialogue:

    Are you a new user? y/n? y

    You must register to use this
    bulletin board. Please read
    Bulletin #1 first. Thank You.

    Are you a new user? y/n? n

    Enter your secret number: 31415

Matching an else to an if

    As you write more advanced programs, you will need to use more complex if
    statements, such as:

    if (temp < 900)
        if (temp > 750)
            printf("Warning! Boiler overheating!\n");
        printf("Start emergency shutdown!\n");

    This program is meant to check the temperature and print a warning if the
    temperature is between 750 degrees and 900 degrees, or print an emergency
    warning if the temperature is greater than 900 degrees. It might look
    correct, but it's not.

    When this if statement actually executes, it prints nothing if the
    temperature exceeds 900, and it prints the emergency warning if the
    temperature is less than 750! We actually want the else to go with the
    outer if to print the emergency warning only if the temperature exceeds
    900. However, although we physically aligned the else so that it appears
    to go with the outer if, the compiler reads the statement differently. It
    considers the else to belong to the inner (nested) if.

    Always remember that QuickC matches a given else with the preceding
    unenclosed if that doesn't already have an else. Now that we understand
    this rule, we can fix the program by enclosing the inner if in braces so
    that the else is not attached to it:

    if (temp < 900)
        if (temp > 750)
            printf("Warning! Boiler overheating!\n");
        printf("Start emergency shutdown!\n");

The Conditional Assignment Statement ?

    Compared to BASIC or even Pascal, C might seem to be a sparse language
    that provides the essential tools for programming but few frills. However,
    we've already seen several elements (such as the special increment and
    decrement operators) by which C provides "shorthand" expressions to
    simplify commonly encountered programming chores. Another such common
    programming task is assigning one of two values to a variable, depending
    on the result of a test. For example, suppose we want to set the variable
    max to the larger of the values of the variables n1 and n2. Of course, we
    can use an if and else, as follows:

    if (n1 > n2)
        max = n1;
        max = n2;

    But we can also use C's conditional assignment statement to do the job──
    and in one line of code. The general form for the conditional assignment
    statement is:

    variable = (expression) ? value1 : value2;

    QuickC evaluates the expression in parentheses first. In this form of
    assignment statement, if the expression is true (nonzero), value1 is
    assigned to variable; if the expression is false (zero), value2 is
    assigned to variable. Note that a question mark follows (expression) and a
    colon (:) separates the two values.

    We can now rewrite our earlier statement for assigning a value to max as

    max = (n1 > n2) ? nl : n2;
        │        │        │    └─────────────────────────────── Assign if false
        │        │        └───────────────────────────────────── Assign if true
        │        └────────────────────────────────────────── Expression to test
        └──────────────────────────────────────────── Variable to receive value

    This translates as "If n1 > n2, then assign the value of n1 to max;
    otherwise, assign the value of n2 to max." Although this statement might
    look odd, it's easy to use and quite handy.

Assigning Truth Values

    If the two possible values for a variable are actually "true" and "false,"
    you don't need to use the conditional assignment statement. Simply assign
    the result of the expression to the variable. For example,

    frozen = (temp <= 32)

    sets the value of frozen to true (nonzero) if the temperature is less than
    or equal to 32 and sets it to false otherwise.

    The SHORTIF.C program (Listing 5-3 on the following page) illustrates
    some shorthand and conditional assignments.

    /* shortif.c -- shows 'shorthand' IF / ELSE   */
    /*           -- gets absolute value of number */

        int num, pos, abs;
        printf("Enter a whole number: ");
        scanf("%d", &num);

        pos = (num >= 0); /* is number positive? */

        abs = (pos) ? num : -num;  /* assigns negative of */
                        /* number if number is negative */
        if (pos)
            printf ("The number is positive.\n");
            printf("The number is negative.\n");
        printf("Absolute value of number is: %d\n", abs);

    Listing 5-3.  The SHORTIF.C program.

    First, the program gets a number from the user. Then it tests the number
    to see if it is positive. Notice we do this by assigning the result of the
    expression (num >= 0) to the variable pos. This value now contains "true"
    if the number is positive, or "false" if it is not. (Remember that these
    are actually numeric values, 1 and 0, that have the logical effects of
    "true" and "false" when used in tests.)

    Next, the program uses a conditional assignment statement to calculate the
    absolute value of the number. (The absolute value of a number is its value
    disregarding its sign. Thus, both 5 and -5 have an absolute value of 5.)
    Recall that a conditional assignment statement assigns one of two values
    to a variable based on the truth result of an expression. However, you can
    also use a single variable that has a truth value instead of an
    expression. Because the variable pos was assigned a truth value earlier,
    we can use it here as the test for the conditional assignment.

    Now let's look at the assignment statement and the if-else branches. If
    the entered number (num) is positive, pos contains "true," and the
    statement assigns num to the absolute value abs. In other words, the
    absolute value of a positive number is simply the number itself.

    If num is negative, however, then pos contains "false," and the statement
    assigns the second value, -num, to abs. The negative of a negative number
    is a positive number and, therefore, the absolute value. The examples on
    the following page demonstrate the output when the program is run twice──
    first with a positive number and then with a negative number.

    Enter a whole number: 23
    The number is positive.
    Absolute value of number is: 23
    Enter a whole number: -58
    The number is negative.
    Absolute value of number is: 58

Multipath Branching

    Thus far, we've discussed simple branches (the single if) and two-way
    branches (the if and else). Simple branches are most useful for testing a
    condition that can have only one of two values──typically "true" and
    "false." But what about those situations in which you must test for one of
    several values? This commonly occurs in a menu from which a user must
    choose one of several items.

    Consider, for example, a program that offers the user a choice of readings
    from a home weather station. Let's say the user can choose among
    temperature, humidity, pressure, and wind velocity. Here's one way we
    could set up the menu:

    printf("Enter reading wanted: t = temp  h = humidity\n");
    printf("p = pressure  w = wind velocity ");
    ch = getche();
    if (ch == 't')
        printf("Current temperature is %5.2\n", temp)
        if (ch == 'h')
            printf("Humidity is %4.2f\n", humidity);
            if (ch == 'p')
                printf("Air pressure is %5.2f\n", pressure);
                if (ch == 'w')
                    printf("Wind velocity is %d\n", wind);
                    {/* default */
                    printf("Invalid choice. Choose ");
                    printf(" t, h, p, or w.\n");

    This chain of if statements, each hooked to the preceding statement's
    else, will work, but it has many disadvantages. Its many levels of nesting
    are difficult to read. Also, the many indentions run the code off the edge
    of the screen, requiring awkward line breaks.

    This type of code also creates a conceptual problem. The structure of
    these statements suggests that each if statement is dependent on all of
    the preceding if statements. This suggests that we are checking for some
    kind of special case that is true only if all the ifs in the series are
    true. But nothing is further from the truth──we merely want to compare ch
    to four possible values and provide a branch for each value. (We also need
    a default branch to handle invalid user-entry values.)

    We can improve the visual organization of our branches in the following

    if (ch == 't')
        printf("Current temperature is %5.2\n", temp)
    else if (ch == 'h')
        printf("Humidity is %4.2f\n", humidity);
    else if (ch == 'p')
        printf("Air pressure is %5.2f\n", pressure);
    else if (ch == 'w')
        printf("Wind velocity is %d\n", wind);
    else { /* default */
            printf("Invalid choice. Choose ");
            printf("t, h, p, or w.\n");

    This arrangement is clearer and more compact. All of the branches now have
    the same level of indention, showing that they are co-equal and not
    dependent on each other. However, it is important to note that else if is
    not a distinct command: Changing indention doesn't change the way the
    compiler handles this code.

The switch Statement

    C offers a special switch statement that makes writing multiple branches
    much easier. The general form of switch follows:

    switch (variable)
        case 'constant1':
        case 'constant2':
        case 'constant_n':

    Specify the name of the variable to be tested in parentheses after the
    word switch. As with the other loops and branches, don't use a semicolon
    at the end of the first line: The entire structure comprises one

    The body of the switch statement (enclosed in braces) is a list of
    possible branches. Each branch consists of the word case followed by a
    constant value (a number or character) in parentheses. During execution,
    this constant is compared with the switch variable: If they are equal, the
    statements for that case execute. Note that single quotes enclose each
    constant, and the line ends in a colon.

    One or more statements follow each case line. (Do not enclose a group of
    statements in braces──the compiler handles all statements under a given
    case as a single unit.) The last statement in each branch is the keyword
    break. The break statement immediately ends execution of the switch
    statement; program execution resumes at the statement that follows the
    body of the switch statement. Usually you will conclude each case in a
    switch statement with the keyword break. If a switch statement behaves
    erratically, look for missing break statements in the individual cases.
    Also, include a default: to handle invalid values.

    Sometimes, however, you will want to execute a set of statements if the
    switch variable has any one of several values. You can do this by placing
    the set of statements after a series of switch values, as in the

    switch (ch)
        case 'q':
        case 'Q':
        case ...

    No statement is associated with 'q', so execution falls through to the
    code for 'Q', the show_score() and end_game() functions execute, and break
    is encountered.

    A switch statement can contain any number of branches, which is why the
    last branch in our format description uses the notation constant_n. A
    special case, default:, is an optional branch that is usually placed after
    the last explicit case in the switch statement. It specifies the branch
    that executes if none of the conditions for the other cases match the
    value of variable. Although default: is optional, programmers frequently
    use it to respond to erroneous values, such as an invalid choice.

    Let's use the switch statement to rewrite our weather station menu:

    switch (ch)
        case 't':
            printf("Current temperature is %5.2\n", temp)
        case 'h':
            printf("Humidity is %4.2f\n", humidity);
        case 'p':
            printf("Air pressure is %5.2f\n", pressure);
        case 'w':
            printf("Wind velocity is %d\n", wind);
            printf("Invalid choice. Choose ");
            printf("t, h, p, or w.\n");

    Figure 5-2 on the following page illustrates this switch statement. The
    multiple branches suggest tracks in a railroad switching yard, the
    probable origin of the name.

                                                ┌───────┐    ┌──────────┐
                                ┌───────────►│  't'  │───►│printf... │
                                │            │       │    │break;    │
                                │            └───────┘    └──────────┘
                                │            ┌───────┐    ┌──────────┐
                                │    ┌──────►│  'h'  │───►│printf... │
                                │    │       │       │    │break;    │
            Value of   ▬  ▬ ──────┘    │       └───────┘    └──────────┘
            ┌────────┐       ▬ ────────┘       ┌───────┐    ┌──────────┐
            │        │    │     ▬ ─────────────►│  'p'  │───►│printf... │
    switch │ ( ch ) │    *     ▬               │       │    │break;    │
            │        │          ▬               └───────┘    └──────────┘
            └────────┘        ▬ ────────┐       ┌───────┐    ┌──────────┐
                        ▬  ▬ ──────┐    └──────►│  'w'  │───►│printf... │
                                │            │       │    │break;    │
                                │            └───────┘    └──────────┘
                                │            ┌───────┐    ┌──────────┐
                                └───────────►│default│───►│printf... │
                                                │       │    │break;    │
                                                └───────┘    └──────────┘

    Figure 5-2. The switch statement.

The break Statement

    The break statement has other uses than as the last statement in each case
    of a switch statement. A break statement can also be used with the three
    looping statements: (for, while, and do). In all cases, however, break has
    the same effect──it immediately "breaks out of" the enclosing structure
    and causes execution to resume after the end of the switch or loop
    structure. The BREAK.C program (Listing 5-4) uses break to exit from a
    while loop:

    This program uses the rand() library function to generate a series of
    random numbers. On each pass through the while loop, the program generates
    and displays one random number in the range 0 through 32,767. The

    if (number < 32000)

    terminates the while loop if the program generates a random number greater
    than 32,000. The output might look something like the following:

    Broken out of WHILE loop.

    /* break.c -- shows how to get out of loop with BREAK */

    #include <stdio.h>
    #define TRUE 1

        int number;
        while (TRUE) /* endless loop */
            /* get a random number between 0 and 32767 */
            number = rand();
            printf("%d\n", number);

            /* break out of loop if random number */
            /* is greater than 32000              */
            if (number > 32000)
                break; /* exit WHILE loop */
        printf("Broken out of WHILE loop.\n");

    Listing 5-4.  The BREAK.C program.

    The last value, 32391, triggered the break statement. The while loop
    terminates, and the printf() statement following the body of the while
    loop executes.

    The SWITCH.C program (Listing 5-5 on the following page) shows you how to
    create a simple menu using a while loop containing a switch statement. The
    program asks the user to select one of four math routines (octal
    representation, hex representation, square, or square root), prompts for a
    number to be converted, and prints the result. The user types q to exit
    the program.

    Following is a sample dialogue with SWITCH.C:

    Select a math routine:
    o = octal  h = hex   s = square
    r = square root  q = quit: o
    Enter a whole number: 30
    Result: 36

    Select a math routine:
    o = octal  h = hex   s = square
    r = square root  q = quit: r
    Enter a whole number: 10
    Result: 3.162278

    Select a math routine:
    o = octal  h = hex   s = square
    r = square root  q = quit: q

    Program returned (113).  Press any key

    /* switch.c -- demonstrates switch statement */
    /*             prints values according       */
    /*             to user's choice              */
    #include <math.h> /* for sqrt() */
    #define TRUE 1
        char choice;   /* routine wanted by user   */
        int number;    /* number entered by user   */

        while (TRUE)   /* endless loop */
            printf("\nSelect a math routine:\n");
            printf("o = octal  h = hex   s = square\n");
            printf("r = square root  q = quit: ");
            choice = getche(); printf("\n");

            if (choice == 'q')
                break; /* exits WHILE loop; ends program */

            /* rest of program executed if choice <> 'q' */
                printf("Enter a whole number: ");
                scanf("%d", &number);

            switch (choice) /* print according to */
                            /* choice requested   */
                case 'o':   /* print octal */
                    printf("Result: %o\n", number);
                    break;  /* break here in each case    */
                            /* exits the switch statement */

                case 'h':   /* print hex */
                    printf("Result: %x\n", number);

                case 's':   /* square */
                    printf("Result: %d\n", number * number);

                case 'r':   /* square root */
                    printf("Result: %f\n", sqrt(number));

                    printf("Choice must be o, h, s, r, or q\n");

    Listing 5-5.  The SWITCH.C program.

    We enclose the menu in an endless while loop because the user will be
    making choices indefinitely. Notice that we use while (TRUE) instead of
    while (1). Both have the same effect, but the code is clearer when we use
    a #define statement to make TRUE equal to 1 and use the descriptive name
    in the program.

    After the program displays the menu and getche() gets the user's choice,
    an if with a break statement tests for the possibility that the user wants
    to quit. If the user quits, the while loop terminates and the program

    If the user did not enter q, the program obtains the number to be
    processed. A switch statement then processes the number. The constants in
    the various cases correspond to the menu options so the switch statement
    can match the user's choice with the appropriate case. If the choice is
    `o' or `h', a format specifier returns the appropriate value; if the
    choice is `s' or `r', the value is calculated.

    The default: case handles any value not specified in the menu by printing
    a list of valid values. Note that the default: case needs no break because
    there are no further statements in the switch statement that can be

switch vs if-else

    Using switch gives you a structure that is at least as clear as the series
    of else ifs shown earlier, because each case is clearly distinct. The
    switch has the additional advantage that the variable to be compared is
    stated clearly once, at the beginning of the structure, rather than being
    buried inside the individual tests.

    Whether you decide to use switch or the if-else form is a matter of style.
    A good rule of thumb is to use switch whenever four or more possible
    values (including a default) are involved. Some programmers argue that
    switch is clearer for even three possible branches.

    There is, however, one situation in which you cannot use switch──even when
    many branches must be used. A switch statement can be used only to test
    simple constant values. You cannot, for example, do the following:

    switch (expenditure)
        case < 10.00:───────────────────────Expression is illegal for switch
            printf("petty cash");
        case < 100.00:
            printf("see office manager");
        case < 500.00:
            printf("see district manager");
            printf("see head office");

    Unlike many versions of the Pascal case statement and similar structures
    in other languages, the C switch statement cannot compare a value against
    ranges of values. It also cannot be used with relational expressions. For
    these programming tasks, you must use multiple if-else structures.

The continue Statement

    Under some conditions we might need to skip some of the statements in the
    body of a loop and return to the loop's test condition. For example, if a
    program offers a menu operation that has potentially irrevocable
    consequences (such as overwriting the contents of a file), you might want
    to ask Do you really want to overwrite this file? If the user answers no,
    the program must skip the remaining statements and return to the menu. The
    continue statement lets you do this.

    A continue statement takes the following general form. We illustrate it
    here with a while loop, although you can use it in any kind of loop (but
    not a switch).

    while (condition)
        some statements;
        if (condition)
        rest of statements;

    The if statement tests a condition as usual: If its condition is true, the
    continue executes. This restarts the loop before the rest of the
    statements in the body of the loop execute, and the while loop condition
    is tested again.

    The CONTINUE.C program (Listing 5-6) uses a simple example of a continue
    statement. The program also illustrates a "toggle switch" variable, sw. A
    toggle switch changes to the opposite of its current value each time you
    use it. If it's "on," the next time you use it you turn it off.

    The body of the endless while loop first prints out the current status of
    the sw switch. Next, the program uses a break statement to give the user
    an opportunity to quit. The program then asks the user whether the switch
    should be toggled. If the answer is not `y,' a continue statement skips
    the last statement in the loop body and the switch is not toggled. If the
    answer is `y,' the continue doesn't execute, and the last statement sw =
    !sw toggles the switch. (Recall that the ! operator reverses the truth
    value of the associated variable.)

    The next program, M.C (Listing 5-7), demonstrates various combinations of
    for loops and if statements and includes a continue statement. The program
    draws a letter M within the dimensions specified in the #define statements
    at the beginning of the program.

    /* continue.c -- shows CONTINUE in a loop */
        int sw = 0;
        char ch;
        while (1) /* endless loop */
            /* print current status */
            if (sw)
                printf("\nSwitch is ON\n");
                printf("\nSwitch is OFF\n");

            printf("Do you want to quit? ");
            if (ch = getche() == 'y')
                break;    /* exit loop on yes */

            printf("\nDo you want to toggle the switch? ");
            if (ch = getche() != 'y')
                continue; /* restart loop on no */

            sw = !sw;     /* toggle switch */

    Listing 5-6.  The CONTINUE.C program.

    /* m.c -- draws a letter M        */
    /*        using IF and CONTINUE  */

    /* define characters */
    #define CH 'M'    /* character to "draw" with */
    #define BLANK ' '
    #define NL 10
    #define CR 13
    #define LEFT 20   /* left side of M   */
    #define RIGHT 46  /* right side of M  */
    #define BOTTOM 22 /* last line to use */
        int pos, line;
        /* space to left side */
        for (line = 1; line <= BOTTOM; line++)
            for (pos = 1; pos < LEFT; pos++)
            putch(CH); /* draw left side */
            /* are we past midpoint? */
            if (line > ((RIGHT - LEFT) / 2))
                /* yes, so just draw right side */
                for (pos = LEFT; pos < RIGHT; pos++)
                continue; /* start loop over, do next line */
                /* not past midpoint, check for interior */
            for (pos = LEFT; pos < RIGHT; pos++)
                if ((pos == (LEFT + line)) ||
                    (pos == (RIGHT - line)))
            putch(CR); /* could also use printf("\n"); */

    Listing 5-7.  The M.C program.

    The M.C program generates the following output:

    M M                       MM
    M  M                     M M
    M   M                   M  M
    M    M                 M   M
    M     M               M    M
    M      M             M     M
    M       M           M      M
    M        M         M       M
    M         M       M        M
    M          M     M         M
    M           M   M          M
    M            M M           M
    M             M            M
    M                          M
    M                          M
    M                          M
    M                          M
    M                          M
    M                          M
    M                          M
    M                          M
    M                          M

    Drawing the letter M involves drawing two distinct sections──the V-shaped
    inner part and the straight-sided outer part. The overall control for
    drawing all the individual lines resides in the outermost for loop. Each
    line is started by a small for loop that moves to the left side of the M
    and draws the character "M" there.

    What happens next depends on whether the current line number is in the top
    or bottom part of the M. We determine this by testing to see if the
    distance down the screen in lines is greater than half the distance across
    the M in characters. (This test is arbitrary. Feel free to try other
    formulas and to vary the size of the M by changing the #define

    If we are not below the bottom of the V part of the M, the for statement
    in the body of the if moves to the right side, which is then drawn. The
    continue (which doesn't require an if here) then skips the rest of the
    statements, which aren't needed.

    If the if statement is false, we are still in the upper portion of the M:
    The body of the if is skipped, and the rest of the statements in the body
    of the outer loop execute. The if statement works on the principle that
    the inner lines of the M are drawn one space further to the right and to
    the left from the sides of the M for each line further down the screen.
    Thus, the appropriate positioning for the inner lines is found by adding
    and subtracting the current line number.

The goto Statement

    C's goto statement transfers control to the line containing the specified
    label. For example, you might use goto with an if statement, as follows:

    printf("Do you want to continue? \n");
    if (ch = getche() == 'y')
        goto yes;
        goto end;
    yes: printf("Let's continue ...\n");

    Here, if the user enters y, the goto immediately causes execution to skip
    to the printf() statement that follows the label yes: (which must end with
    a colon). If the user does not enter y, the second goto skips the "yes"

    The Appropriate Use of continue
    The continue statement is rarely used in C programs. Often (in Microsoft
    C, for example), continue's function is handled by an else branch for the
    relevant if statement or by a new if statement. However, if you have a
    complicated, multiple-nested set of if-else statements, using a continue
    statement might simplify things.

    If this looks confusing, that's because it is. You can do the operation
    much more clearly with an if and an else:

    printf("Do you want to continue? \n");
    if (ch = getche() == 'y')
        printf("Let's continue ...\n");

    Nearly all contemporary computer scientists discourage the use of goto
    because it obscures program logic and makes code difficult to decipher, as
    anyone who has ever tried to debug an old-style BASIC program knows. If
    your programming background is in the older versions of BASIC or FORTRAN,
    resist the impulse to use goto statements. Examine the logic of your
    program: You probably will see, as in the above example, that an if-else
    with appropriate conditions (or a switch, break, or continue) lets you
    express the operation more clearly. That's why you can go for months
    without encountering a goto in C programs. (An occasional exception is the
    goto that breaks out of a multiple-nested loop. You can't use break in
    this situation because it only breaks out of the current loop. But even in
    this case, you can avoid a goto by redesigning the program structure to
    use "flag" variables. We will discuss flag variables later.)

More Complex Conditions for Branching

    Because we have been concentrating on the mechanics of branching, we have
    used only simple test conditions in our branching statements. In the last
    chapter, we showed you how to use logical operators (&& and ||) to create
    multiple conditions for controlling loops. You can also use these compound
    conditions to control the execution of if statements.

    The next program, PIXELS.C (Listing 5-8), introduces the QuickC Graphics
    Library. It generates random positions for pixels (points of light on the
    screen) and uses a compound condition to display only selected pixels.

Running the Program

    We've already included header files in several of our programs. The
    machine code represented by these header files was in the standard library
    (such as MLIBCE.LIB, the medium memory model with floating-point
    emulation), so all you needed to specify was #include and the appropriate
    header file. QuickC knew where to find the default library.

    To use graphics, however, you must specify the graph.h header file. The
    definitions it contains reside in a separate Graphics Library,
    GRAPHICS.LIB. You might need to tell QuickC where to find this library. As
    with the TABLE.C program in the last chapter, you need a program list.
    Create a program list called PIXELS.MAK and add PIXELS.C to it. (If you
    are unsure how to proceed, reread "Creating a Program List" in Chapter

    /* pixels.c -- creates shapes       */
    /*             from random pixels   */
    #include <graph.h> /* for graphics  */

        int pixels, xpos, ypos;
        /* window coordinates */
        int xmin = 100, xmax = 540;
        int ymin = 50,  ymax = 150;

        srand(0);               /* init random nums */
        _setvideomode(_HRESBW); /* CGA 640 x 200    */
        _setcolor(1);           /* white foreground */

        /* generate random pixel locations      */
        for (pixels = 1; pixels < 10000; pixels++)
            xpos = rand() % 639;
            ypos = rand() % 199;

            /* set pixel if within window */
            if ((xpos > xmin && xpos < xmax) &&
                (ypos > ymin && ypos < ymax))
                _setpixel(xpos, ypos);
        getch(); /* freeze screen until key pressed */
                /* restore original video mode */

    Listing 5-8.  The PIXELS.C program.

    You will need to add the Graphics Library (GRAPHICS.LIB) to the program
    list as well unless you specified that the Graphics Library was to be
    included in your standard library when you ran the SETUP program. (See
    Chapter 2.)

    For future programs in this book, we will not remind you to create a
    program list. In general, if the program uses any standard functions that
    are not part of the core library (listed in Appendix :ARB), you must
    create a program list with the name of the program in it before you can
    compile the program to memory. If you wish, you can simply try to compile
    the program and create the program list if you get the error unresolved

    PIXELS.C starts by including the graph.h header file, which contains
    definitions for graphics at every resolution and color supported by the
    IBM graphics adapters (CGA, EGA, VGA, and so on, each with several modes).
    We discuss graphics modes and graphics routines in Chapter 15. Here,
    simply note that the _setvideomode() statement in PIXELS.C sets the video
    mode to the constant _HRESBW, which represents the two-color
    high-resolution CGA mode having a resolution of 640 pixels by 200 pixels.
    The _setcolor statement sets the foreground color (the color of the
    displayed pixels) to white.

    The values xmin, xmax, ymin, and ymax contain the coordinate positions of
    the screen "window" in which the program plots pixels. Figure 5-3 shows
    the screen and the coordinates of the selected window, as well as some
    sample output for the program.

    The heart of the program is the for loop that plots 10,000 random pixel
    positions. Notice that we use the % (modulus) operator to select values in
    the range 0 through 639 for X, and 0 to 199 for Y. (This corresponds to
    the 640-by-200 resolution for the specified CGA high-resolution mode.)

    The if statement checks for an X and a Y position within the window
    specified by xmin, xmax, ymin, and ymax. Notice that the && logical AND
    operator ensures that each value is greater than or equal to the minimum
    and less than or equal to the maximum. The && between the two expressions
    in parentheses tests the random value to see if it fits in both the X and
    Y ranges.

    │ Figure 5-3 can be found on p.144 of the printed version of the book.   │

    Figure 5-3. Screen coordinates and output for PIXELS.C.

Variations of PIXELS.C

    You can create many interesting shapes by substituting different
    conditions in the if statement. The GALAX.C program (Listing 5-9)
    establishes a center point (center_x, center_y) and a radius. The if
    statement in the for loop uses a formula that determines if a point is
    within the circle──if it is, the program plots the pixel.

    The result of running GALAX.C (Figure 5-4 on the following page) looks
    more like an ellipse than a circle because in the 640-by-200 mode, pixels
    are spaced together more closely horizontally than they are vertically.
    Because the program must calculate many square roots, it runs a little
    slowly. We use an if (kbhit()) to let you stop the program whenever you
    press a key.

    /* galax.c -- creates an ellipse by selecting */
    /*              from random pixels            */
    #include <graph.h> /* for graphics */
    #include <math.h>  /* for sqrt() */
    #include <conio.h> /* for kbhit() */
        int pixels, radius = 50;
        double center_x = 320, center_y = 100,
            xpos, ypos;
        for (pixels = 1; pixels < 25000; pixels++)
            /* draws filled ellipse, due */
            /* to dimensions of hi-res screen */
            /* generate random location */
            xpos = rand() % 639;
            ypos = rand() % 199;
            if (sqrt /* is distance within radius? */
                ((xpos - center_x) * (xpos - center_x)
                + (ypos - center_y) * (ypos - center_y))
                < radius)
                _setpixel(xpos, ypos);
            if (kbhit())
                break; /* exit if key pressed */
        getch(); /* freeze screen until key pressed */

    Listing 5-9.  The GALAX.C program.

    │ Figure 5-4 can be found on p.146 of the printed version of the book.   │

    Figure 5-4. Output of GALAX.C.

Chapter 6  Functions and Function Calls

    One of the great advantages that C offers a programmer is its huge variety
    of library functions, which cover everything from manipulating text to
    controlling memory allocation. The seasoned C programmer soon learns that
    the C library contains most of the tools needed to perform a given task.
    However, the real power of C derives from the ease with which you can
    design customized C functions that perform the specific and unique
    operations your program requires. In this chapter, we will show you how to
    create and use these functions.

Functions and Program Design

    Every C program must have at least one user-defined function, namely
    main(). Most real-world C programs, however, consist of many user-written
    functions, because the procedure for processing data usually involves many
    different steps. A program that calculates statistics, for example, might
    have to ask the user for data, check the data for validity, store the data
    in memory or on disk, process the data (often according to several
    criteria), and report the results, possibly in a variety of formats. If
    all of this program code were in the main() function, the resulting jumble
    would hamper a programmer trying to visualize where one step ends and the
    next one begins. Debugging the program would be nearly impossible because
    you would have difficulty figuring out which of the intertwined parts
    worked correctly and which ones did not. And if you decided to revise the
    program to add new capabilities, you could not easily find the appropriate
    place to add new code.

    Most experienced programmers design programs using a "top-down" approach.
    This method resembles writing an outline for a report. First, list the
    principal ideas or steps. Then divide those ideas or steps into subtopics,
    and continue subdividing until you feel ready to write the actual
    sentences. Using a similar approach, the main() function for our
    statistics program might read:

    while (more_data)

    Each of the names followed by parentheses in the definition of main() is a
    call to a user-defined function. Notice how this "outline" clearly shows
    the overall flow of the program. First, the program offers the user
    choices in a data-entry menu; then it enters a loop that receives,
    validates, and stores data as long as more data is entered. The data is
    then processed. Another menu lets the user generate a specific type of
    report, and finally the program prints that report.

    Note that using separate functions does more than merely keep the parts of
    a program conceptually separate; it also provides an orderly way of
    communicating information between different sections of the program. Each
    function receives information, such as the values of certain variables,
    and after it finishes executing, returns the transformed information to
    another section of the program.

    In a C program, any function can call any other function. This means that
    your user-defined functions can call other user-defined functions as well
    as C library functions. For example, the definition of do_report(), shown
    on the opposite page, might call other user-defined functions, each of
    which prints a different kind of report, corresponding to the choices
    offered in the report_menu() part of the program.

        switch (choice)
            case 'b' :
            case 'p' :
            case 'l' :
            case 't' :

    Normally, you declare each user-defined function in main() before the
    program calls it. The definitions of these user-defined functions usually
    follow the end of the definition of main(). (You also can define groups of
    functions in separate files: We will discuss this in Chapter 12.) Figure
    6-1 proposes a general outline for a program that declares and uses three
    user-defined functions.

    Dividing a program into logically organized user-defined functions also
    lets you develop the program one piece at a time. You can start by putting
    "stub" definitions in the functions, such as:

        printf("executing pie()\n");

    ┌────────────────┐                Function definitions
    │ main ( )       │    ┌──────────────────────────────────────────┐
    │ {              │  ┌─┼─────────────────────────┐                │
    │   func1 ( );   │ ─┼─┼───────┐                 │                │
    │   func2 ( );   │ ─┘ │┌──────▼──────┐   ┌──────▼──────┐  ┌──────▼──────┐
    │   func3 ( );   │ ───┘│  func1 ( )  │   │  func2 ( )  │  │  func3 ( )  │
    │   ─────────    │     │  {          │   │  {          │  │  {          │
    │   ─────────    │     │  ───────    │   │  ───────    │  │  ───────    │
    │   ─────────    │     │  ───────    │   │  ───────    │  │  ───────    │
    │   ─────────    │     │  ───────    │   │  ───────    │  │  ───────    │
    │ }              │     │  }          │   │  }          │  │  }          │
    └────────────────┘     └─────────────┘   └─────────────┘  └─────────────┘

    Figure 6-1. Outline for a C program with functions.

    These let you test the overall control structures of the program (the
    loops, branches, and switches) before you write the actual routines that
    perform the various tasks. Then you can replace functions one at a time
    with their real definitions. After you are certain that a function works
    properly, you can move to the next function. The basic philosophy of this
    type of programming is "divide and conquer."

Declaring and Defining a Function

    Now that we've discussed the advantages of user-written functions in
    program development, let's look at the mechanics of declaring, defining,
    and using your own functions. Always remember that you must declare your
    functions before you use them. In earlier chapters, when you used the
    #include directive to allow calling C library functions in your program,
    QuickC inserted function declarations and definitions into the program.
    For functions you create yourself, however, you must provide the
    declaration and a definition.

Declaring a Function

    Let's declare and define a function that prints an error message. The
    general format of a simple function declaration is as follows:

    return_type name();

    In the declaration, return_type refers to the data type (int, float, etc.)
    of the value that the function returns. Because our first example is a
    function that does not return a value, we must use the special word void
    as the return type. (The word void, in this instance, doesn't mean
    "invalid," but rather "empty.")

    The function name──error, in our example──must be followed by parentheses,
    which hold the parameters the function is designed to use. In the case of
    an error message function, parameters might include a string to print and
    values that would indicate that the message be printed in high-intensity
    text, accompanied by a beep, and so on. You must always include
    parentheses──even if, as in the case of our error() example, you use no
    parameters. (The compiler uses the parentheses to distinguish a function
    from a variable.) Note also that you must use a semicolon at the end of
    the line. Thus, we declare our error message─printing function as follows:

    void error();

    As with variable declarations, you can declare several functions of the
    same type on one line, as in the following declaration:

    void error(), greeting(), warning();

    Usually, programmers place the declarations for user-defined functions in
    the definition of main(), before any other statements (except possibly

        void error(); /* function declaration */
        other function declarations;
        other statements;

    However, if a program has many user-defined functions, you might want to
    put the declarations before main(). This enables you to see the
    declarations more easily and separates them from the code of main()

Defining a Function

    After you declare a function, you must define it with statements that will
    execute when the program calls the function. The first line of the
    function definition essentially repeats the original function declaration:

    void error()

    However, remember that, with a definition, you use no semicolon at the end
    of the line. The next line should contain an opening brace ({ ); then come
    the statements that define the function; and finally, the definition ends
    with a closing brace (} ). Thus, we write our error() function definition
    as follows:

    void error()

    When a statement calls this function, it prints the word Error! and sounds
    a beep. (Notice the \a [alert] escape sequence, which is listed in Figure
    3-5 on p. 68.)

    ANSI and Function Declarations
    QuickC and other current versions of C do not require you to declare
    functions that return no value or an int value. The new ANSI standard and
    modern programming practice encourage you to declare all functions,
    however. This is to help the reader see how the functions work and to
    allow the compiler to check for inconsistencies between the definition of
    a function and the way it is called. For these reasons, which we explain
    in greater detail later, we declare all functions in our example programs.

Calling the User-defined Function

    We call our user-defined error() function the same way we call a library
    function like printf()──by naming it in a statement in main() or in the
    body of another function. Consider the following example:

        if ((number < 1) || (number > 9))
            error();  /* function call */

    The if statement calls the error() function only if number is either less
    than 1 or greater than 9. Again, note that the function call must include
    the parentheses with the function name.

    The DBLBAR.C program (Listing 6-1) calls the user-defined function line()
    to print a double bar before and after a program title. Note that we
    declare line() before the statements in main(), that we call line() twice
    from within main(), and that we define line() following the end of main().
    Figure 6-2 shows the flow of control in this program.

    /* dblbar.c -- prints header using */
    /*             line() function     */

    #define DOUBLE_BAR 205

        void line();       /* declare line() function */

        line();            /* call line() function    */
        printf("dblbar.c -- prints header using\n");
        printf("line() function\n");
        line();            /* call line() again        */

    void line()            /* function definition      */
        int pos;
        for (pos = 1; pos <= 40; pos++)

    Listing 6-1.  The DBLBAR.C program.

                                ┌──First call
        ┌───────────────────┐ │  and return
        │ main ( )          │ │           ┌ - - - - - - - - - - - - -
        │ {                 │ │           |                           |
        │ void line ( );    │ │           |   ┌───────────────────┐   |
        ┌─┼►line ( ); ────────┼─────────────|───┼►void line ( )◄- - - -
        │ │ printf ("...);    │             |   │ {                 │
        │ │ printf ("...);    │             |   │ int pos;          │
    - -│►line ( );- - - - -│- - - - - - -    │ for (     )       │
    |  │ │ }                 │ │               │ printf ("...);    │
    |  │ └───────────────────┘ │           ┌───┼─}- - -            │
    |  │                       │           │   │       |           │
    |  │           Second call─┘           │   │       |           │
    |  │           and return              │   └───────|───────────┘
    |  └───────────────────────────────────┘           |
    |                                                  |
    └ - - - - - - - - - - - - - - - - - - - - - - - -  ┘

    Figure 6-2. Flow of control in DBLBAR.C.

    You can also follow the flow of this program by using QuickC's debugger.
    Select the Debug option in the Compile dialog box, and then select Trace
    On from the Debug window. When you recompile the program, you see the
    highlighted statement move through main() until it reaches the first call
    to line(). The debugger then highlights the statements in line().
    Highlighting returns to main() with the statement following the call to
    line(). Control shifts in the same manner when the program encounters the
    second call to line().

A Disadvantage in Using Functions

    The double-bar program demonstrates the advantage of using functions──they
    reduce the size of the program. Any time we need to draw a line, we simply
    call line() rather than repeat the whole for loop. However, using
    functions has a potential disadvantage. At the machine-code level, each
    time the program calls a function, the current status of the calling
    program (including the contents of CPU registers) has to be saved,
    information must be passed to the function via a memory area called the
    "stack," and various other housekeeping operations must be performed to
    return control to the calling statement. Therefore, calling a function
    involves a lot more "overhead" than merely using a copy of the desired
    code wherever you need it in your program. (The performance difference is
    not noticeable with only a few function calls, but you might notice it
    when you execute a function call thousands of times within a loop.) The
    loss of efficiency is not critical in most cases and usually is far
    outweighed by the benefits of using functions. But keep in mind that the
    more function calls you use, the more important the overhead factor

Local and Automatic Variables

    A defined function, such as line(), can contain its own variable
    declarations within its definition. Variables declared within a function
    definition are called local variables, and they are accessible only within
    the function in which they are declared. Outside the function braces, the
    variables don't exist. To be accessible to all the functions in your
    program, a variable must be declared globally──outside of any function.
    The extent of a variable's accessibility, or "visibility," is called the
    "scope" of a variable.

    In Figure 6-3, note that variables defined in func1() cannot be accessed
    from main(). They are "invisible" to main(), func2(), or anywhere else
    outside of the definition of func1(). For example, the definition of
    line() in DBLBAR.C declares an int variable pos, which is used in the for
    loop. (See Listing 6-1 on p. 152.) Suppose the main() function in
    DBLBAR.C contained the following line:

    printf("The variable pos in line() has a value of %d\n", pos);

    This line would produce a compiler error, because main() doesn't "know"
    that a variable called pos exists──pos is the private property of the
    line() function.

    ┌─────────────┐                        ADDRESS
    │  main  ( )  │
    │  {          │
    │   int n;◄- - - - - -Visible only     8706
    │   func1 ( );│       in main ()       (remains
    │   func2 ( );│                        until end
    │  }          │                        of program)
    │  func1 ( )  │
    │  {          │
    │   int n;◄- - - - - -Visible only     8694
    │   ───────   │       in func1 ()      (reused)
    │   ───────   │
    │  }          │
    │  func2 ( )  │
    │  {          │
    │   int n;◄- - - - - -Visible only     8694
    │   ───────   │       in func2 ()      (reused)
    │   ───────   │
    │  }          │

    Figure 6-3. Local variables.

    Similarly, if you declare a variable called pos within main(), it is
    private to main() and not accessible from within a function called by

    Because variables defined inside functions are local in C, you can use
    variables with the same name in different functions. Thus, many of your
    program functions can use a variable named count, yet QuickC maintains and
    refers to each one separately.

Variables Used in Functions Are Automatic

    Another important characteristic of a local variable is that it is
    "automatic": The variable is created (meaning that internal storage is
    allocated and the address recorded) each time its function is called.
    Conversely, the variable is destroyed and its internal storage released
    when the function ends and control returns to the calling statement. Only
    a local variable can be automatic (and temporary) because the compiler
    knows that it is valid only while the function executes. (A global
    variable must be stored permanently, because the compiler must always
    assume that the program will need its value again.) Automatic variables
    permit more efficient storage allocation because the same block of memory
    can store many temporary variables as the program executes. C programs
    that use local, automatic variables also are smaller than comparable
    programs that make the same variables global.

    The LOCAL.C program (Listing 6-2 on the following page) illustrates the
    way local variables work. (Again, refer to Figure 6-3.) The main()
    function declares an int variable, n, and prints its value and internal
    address. The program then calls the func1() and func2() functions. Each of
    these functions also defines a variable called n and prints its value and

    The Scope of Variables
    In Pascal, you can "nest" function or procedure definitions within each
    other. A variable defined in a function or procedure is not only
    accessible to that function or procedure, but also to any definitions
    nested within the outer definition. This can make questions of the scope
    of certain variables rather complex. In C you cannot nest function
    definitions; therefore, a variable defined within a function is accessible
    only within that function.

    If you've programmed in older versions of BASIC, you probably expect all
    variables to be global, that is, accessible throughout the program. You
    also might remember times that this "feature" created nasty bugs. For
    example, you might have used count as the control variable of a loop in
    one subroutine and then days later used another variable called count in a
    different subroutine. Depending on the order in which BASIC called the
    subroutines, hard-to-trace bugs probably resulted because BASIC remembered
    the last value in count when it started the new count. If you are a BASIC
    programmer coming to C, rest assured that QuickC will never confuse the
    count of one function with the count of another.

    /* local.c -- local variables defined */
    /*            within functions        */

        int n = 12;
        int func1(), func2();
        printf("n in main(): val %d ", n);
        printf("address %d\n", &n);

        printf("Calling func1()\n");
        printf("Calling func2()\n");

    int func1()
        int n = 8; /* local variable */
        printf("n in func1(): val %d ", n);
        printf("address %d\n", &n);

    int func2()
        int n = 20; /* local variable */
        printf("n in func2(): val %d ", n);
        printf("address %d\n", &n);

    Listing 6-2.  The LOCAL.C program.

    The program's output (which may vary from setup to setup) demonstrates
    that QuickC recognizes each variable's correct value and address without
    any confusion:

    n in main(): val 12 address 8706
    Calling func1()
    n in func1(): val 8 address 8694
    Calling func2()
    n in func2(): val 20 address 8694

    Also notice that n in func1() and n in func2() use the same address. This
    occurs because when func1() ends, it discards its reference to the now
    useless local variable n. When the program calls func2(), QuickC reuses
    the same address for the new func2() local automatic variable n.

    Note that the address of the n in main() wasn't reused when the n in
    func1() or func2() was created. Variables declared in main() have no
    special status──the n in main() is as automatic and local as the variable
    in the other functions. Remember, however, that QuickC discards an
    automatic variable only when the function in which it is defined
    terminates. Because main() doesn't end execution until the program itself
    ends, its variables (including n) are not destroyed and reused.

The auto Storage Class

    All variables declared within function definitions are, by default,
    automatic. Consider the following example:

    void plot_object()
        auto int length, width;

    The variables length and width are automatic because they are declared
    within a function. The auto designation merely reminds us of this. Note
    that auto is not a data type. Rather, it (and the keywords register,
    static, and extern) is what is called a "storage class." It refers to how
    QuickC manages the variable as the program runs. Specify the storage class
    before and in addition to the variable's data type (int in the previous

Static Variables

    Occasionally you will need a variable to retain its value after its
    function terminates. To do this, you must declare the variable used in the
    function with the static storage type, as follows:

    static int total;

    Now the value of total calculated during a previous call is still there
    when you call the function again. You use this storage class to keep
    running totals, for example. Note that a static variable is still local
    and accessible only within the function in which it is defined.

    The STATIC.C program (Listing 6-3 on the following page) uses the
    function countline() to count the words and characters in a line of text.
    (It simply counts a word whenever it encounters a space.) The static
    variables chars and words accumulate the counts. Because the variables are
    static, they retain the previous total each time the function is called.

    A sample of the program's output follows:

    Type some lines of text.
    Start a line with a . to quit.

    By now you should be able to
    Words so far 7. Chars. so far 28
    function very well in C!
    Words so far 12. Chars. so far 52

    /* static.c -- demonstrates a static variable */
    /*             that holds count of lines,     */
    /*             words, and characters          */

        void countline();
        printf("Type some lines of text.\n");
        printf("Start a line with a . to quit.\n\n");

        while (getche() != '.')
            countline();  /* accumulate word and */
                        /* line counts         */

    void countline()
        static int words = 0; /* static variables */
        static int chars = 0;
        char ch;
        ++chars; /* count char typed when */
                /*function was called   */

        while ((ch = getche()) != '\r')
            if (ch == ' ')
            ++words; /* count last word */

        printf("\nWords so far %d. Chars. so far %d\n", words, chars);

    Listing 6-3.  The STATIC.C program.

External Variables

    You can declare global variables in C when you want two or more functions
    to share relevant values or to communicate with each other by periodically
    changing the value of a common variable. A global variable in C is
    referred to as external because it is defined outside the function
    definitions in the program.

    To declare an external variable, simply put its declaration outside of any
    function definition. External variables are usually placed after any
    #includes and #defines, but before the definition of main(). In the
    following example, we declare scale and palette outside of the function,
    making them external (global) and accessible throughout the program. The
    variables length and width, on the other hand, are local and accessible
    only within main().

    #include <stdio.h>
    #define VERSION 1.0

    int scale = 1.5,        /* global variables */
        palette = 1;        /* go here          */

        int length, width;  /* local variables */
        ...                /* go here         */

    The EXTERNAL.C program (Listing 6-4) shows how you might use an external
    variable. We declare the variable length before main() to make it an
    external variable. After the user supplies a value for length, main()
    calls three functions: square(), triangle(), and circle(). Each of these
    functions accesses and uses the value of length to calculate the
    appropriate area.

    /* external.c -- shows an external variable */

    #define PI 3.14159
    int length; /* external (global) variable */
                /* declared before main()    */

        void square(), triangle(), circle();

        printf("What length do you want to use? ");
        scanf("%d", &length);

        square();   /* calculate areas */

    void square()
        float area;
        area = length * length;
        printf("A square with sides of %d has an area of %f\n",
            length, area);

    void triangle()
        float area;
        area = (length * length) / 2;
        printf("A right triangle with sides of %d has an area %f\n",
            length, area);
    void circle()
        float area;
        area = (length * length * PI);
        printf("A circle with radius of %d has area of %f\n",
            length, area);

    Listing 6-4.  The EXTERNAL.C program.

    Try to resist the temptation to make all your variables external; this
    invites the problems we discussed earlier. Variables used in only one
    function should remain local. Variables used by only two or three
    functions might be better handled as parameters passed from one function
    to another. (We will discuss function parameters shortly.)

Register Variables

    Let's look at one more storage type for variables, the register type.
    Microprocessors such as the 8088 and 80286 have several built-in storage
    locations called registers. A program can store and retrieve data from a
    register more quickly than from a location in regular memory, where C
    usually stores variables. As a result, you gain a performance advantage by
    assigning a register to a frequently used variable in a time-sensitive

    The only problem with using registers is that usually there aren't enough
    registers to store all the data of a given operation. As shown in Figure
    6-4, the IBM family of Intel microprocessors (8088, 8086, and 80286) have
    four general-purpose, 16-bit registers that hold data being manipulated at
    the machine level.

                16 bits
            (2 bytes)
    │     AH      │     AL      │ AX
    │     BH      │     BL      │ BX
    │     CH      │     CL      │ CX
    │     DH      │     DL      │ DX
        8 bits        8 bits
        (1 byte)      (1 byte)

    Figure 6-4. General-purpose registers for the Intel 8086 family.

    In other languages, the compiler software determines which variables, if
    any, will be assigned registers. In C, however, you can tell the compiler
    to assign a register to a specific variable when you declare that
    variable, as follows:

    register int count;

    This declaration tells QuickC to store the count variable in a CPU
    register. (You can't specify which physical register to use, however.)
    Because registers in the 8088, 8086, and 80286 cannot store variables with
    values larger than two bytes, only char and int variables can be
    accommodated. Additionally, you cannot declare external or static
    variables as register variables, because registers cannot serve as
    permanent storage locations. Finally, QuickC assigns only two variables
    per function as register variables: You can make more declarations, but
    only the first two are honored. In fact, depending on the CPU workload,
    there is no guarantee that both, or even one, of the registers will be
    available. If speed is important to your program, try the declarations to
    see if they help.

    Which variables should you declare to be register variables? Obvious
    candidates include loop control variables or variables that are part of
    statements performed in a loop. But even though most loops execute many
    times, you shouldn't specify these variables as register storage type.
    QuickC uses optimization techniques to try to produce the fastest machine
    code possible from your program, and one of its basic speedup techniques
    is the assigning of registers to variables in loops. Thus, you gain little
    by specifying these as register variables──in fact, you might even confuse
    the compiler and end up with less efficient code. The best variables to
    specify as register type are variables that are not involved with loops
    yet are used three or more times each time the function is called.

Passing Information to a Function

    Thus far, our user-defined functions have not required that any
    information be passed when we called them. Most functions, however,
    require one or more items of information, called arguments, or parameters.
    This is often true of the QuickC core library functions: printf() needs to
    know what to print and how to print it; scanf() needs to know what
    information to get from the user and where to store it; and so on. Indeed,
    because it uses no parameters, the line() function in Listing 6-1 on p.
    152 is extremely limited. As it stands, it always prints a line of 40
    characters. However, suppose we want 20 or 60 characters. Let's define
    this function so that when you call it, a parameter tells it how long the
    line should be:

    void line (length)   /* length is parameter      */
        int length;      /* declaration of parameter */
        int pos;
        for (pos = 1; pos <= length; pos++)

    When you call this function, the length parameter (placed in parentheses
    in the function definition) receives the current value of the variable
    length. The items named in the definition of a function are called "formal
    parameters." The definition of the line() function includes one formal
    parameter, length. When you call a function, you must include an actual
    value, such as a constant or a variable, for each formal parameter the
    function requires. Thus, if we use the statement line(10); to call the
    line() function, the value 10 is the "actual parameter" corresponding to
    the formal parameter, length. Inside the line() function, length becomes a
    variable of type int with the value 10.

    Notice that we declare the parameter length as an int in the function
    definition for line(). This declaration serves the same purpose as the
    declaration of an ordinary variable: It tells the compiler what type of
    data the parameter represents. Notice also, however, that unlike the
    declaration of an ordinary variable, which follows the opening brace of
    the function definition body, the declaration of a function parameter
    precedes the opening brace. An alternative syntax, favored by many
    programmers, places the parameter type before the name within the
    parentheses, as in the following:

    void line (int length)

    Note that when a function uses parameters, the parameter should also be
    put in the declaration of the function in or before main(). Thus, in a
    program that uses line(), the declaration would be:

    void line (length);


    void line (int length);

    It's easiest to use the same form for the function declaration and for the
    first line of the function definition──but remember that the declaration
    ends with a semicolon, whereas the definition does not.

    Once you declare the parameter length, the line() function refers to it as
    though it had been declared and initialized as an ordinary variable. In
    our example, length sets the limit for the loop condition, thus
    controlling the length of the line.

    Parameters make functions versatile. The program now can call line() and
    set the length of the line with any appropriate value: a number, a
    variable name, a #define constant, or an expression.

    By the way, you can call the line() function using a variable with the
    same name as the parameter (length). The variable in the function that
    calls line() belongs to the calling function, and the parameter "belongs"
    to the called function. They are, in effect, separate local variables──the
    only connection between them is the value.

    Let's look at the ALERT.C program (Listing 6-5), which uses a function
    with a parameter. The beep() function uses a parameter named times to
    control the number of times a beep is sounded. When the function executes,
    the if statement checks to see if times has a value in the range of 1 to
    4. If it doesn't, the program prints an error message. Notice that the
    error message prints the name of the function and the value that length
    passed to it. Including this type of information helps you debug your

    If the value of times is in the correct range, a for loop with the limit
    of times generates the correct number of beeps. Try changing the calling
    statement in main() to beep(0) or beep(100).

    /* alert.c -- sounds alarm by calling a        */
    /*            beep() function with a parameter */

        void beep(times);  /* function declaration */
        printf("*** Alert! Alert! ***\n");
        beep(3);     /* call beep() with parameter */

    void beep(times)
        int times;   /* declare function parameter */
        int count;

        /* check that parameter is between 1 and 4 */
        if ((times < 1) || (times > 4))
            printf("Error in beep(): %d beeps specified.\n",
            printf("Specify one to four beeps");
        else /* sound the beeps */
            for (count = 1; count <= times; count++)
                printf("\a");  /* "alert" escape sequence */

    Listing 6-5.  The ALERT.C program.

How Parameters Work

    Now that you know how to use parameters, let's take a detailed look at how
    they work. Figure 6-5 on the following page shows what happens when a
    program calls a function that has a parameter. When it executes the
    function call line(short), QuickC places the value of the variable short
    in an internal memory area called the "stack" and passes control to the
    line() function. The line() function "knows" from its definition that it
    should expect one parameter, called length. It also knows that it is an
    int value. Thus, the function reads two bytes (an int) from the stack and
    creates a temporary storage location for them. This value can now be
    accessed by length within the line() function. It is basically a local,
    automatic variable that behaves as if it had been declared and initialized
    within the function definition.

    │  main ( )            │     ┌─────────►Discarded when
    │  int short=10;       │     │          function terminates
    │  {         ││        │     │
    │┌───────────┘└───────┐│     │
    ││   line (short);    ├┼───►10 ───┐
    │└────────────────────┘│   ────   │       Called function
    │    ───────────────── │   ────   │   ┌──────────────────────┐
    │    ───────────────── │   ────   │   │ void line (length)   │
    │  }                   │   ────   └──►│ int length; (=10)    │
    └──────────────────────┘  Stack       │ {                    │
                                        │  ──────────          │
                                        │  ──────────          │
                                        │  ──────────          │
                                        │ }                    │

    Figure 6-5. Function parameters and the stack.

    Note that the parameter in a function call is not normally affected by the
    operation of the function. The function operates with a local variable it
    creates, not with the variable in the calling statement.

Multiple User-written Functions

    The TIMER2.C program (Listing 6-6) uses a callable custom function,
    delay(), to improve the timer program we used in Chapter 4. The main()
    function asks the user for the number of seconds to be timed and the
    interval by which the program should count off the time, beeping once at
    each interval. The while loop repeatedly calls delay(interval) to wait for
    interval seconds; then it sounds the beep (by calling the beep() function)
    and prints the elapsed seconds. Be sure you understand the positions and
    components of the function declarations, the function definitions, and the
    parameter declarations for the delay() and beep() functions.

    Passing Parameters in Pascal and C
    In Pascal, you can pass either the value of a variable or its address in a
    parameter. (The first is a "call by value," the second is a "call by
    reference.") In C, function parameters are always passed by value: The
    variable itself is never passed. The value can, however, represent the
    address of a variable. In this case the variable itself, and not merely
    the value, can be accessed and changed by the called function. (This is
    called a "pointer," which we discuss in Chapter 8.)

    /* timer2.c -- interval timer                */
    /*             calls delay(), uses beep      */

        /* function declarations */
        void beep (times);
        void delay (seconds);

        /* variable declarations */
        int seconds, interval, tick;

        printf("Set for how many seconds? ");
        scanf("%d", &seconds);
        printf("Interval to show in seconds? ");
        scanf("%d", &interval);
        printf("Press a key to start timing\n");

        tick = 0;                /* run "clock" for */
        while (tick < seconds)   /* time specified  */
            delay(interval); /* wait interval seconds */
            tick += interval;
            printf("%d\n", tick);

    void delay(seconds)
                    /* wait for number of seconds specified */
                    /* See TIMER.C in chapter 4 for details */
                    /* on the library function time().      */

    int seconds;   /* parameter declaration */
        /* variable declarations */
        long start, end, /* starting and ending times */
                        /* measured in seconds since */
                        /* Jan. 1, 1970 */
            ltime;      /* used to get value from time function */

        start = time(&ltime);  /* get system-elapsed seconds */
                                /* since 1-1-70 */
        end = start + seconds; /* calculate alarm time */

            {;}                       /* null statement for loop body */
        while (time(&ltime) < end);   /* wait for end of time  */

    void beep(times)
        /* parameter declaration */
    int times;
        /* variable declaration  */
        int count;

        /* check that parameter is between 1 and 4 */
        if ((times < 1) || (times > 4))
            printf("Error in beep(): %d beeps specified.\n",
            printf("Specify one to four beeps\n");
        else /* sound the beeps */
            for (count = 1; count <= times; count++)
                printf("\a");  /* "alert" escape sequence */

    Listing 6-6.  The TIMER2.C program.

    The delay() function has the same code as the TIMER.C program in Chapter
    4, except that it adds the parameter seconds to the current time to
    determine what time should be compared with the system time in the do
    loop. A sample run follows:

    Set for how many seconds? 30
    Interval to show in seconds? 5
    Press a key to start timing
    5────────────────────────────────────────────────Beeps after each number

    Putting User Functions in an Include File

    Notice in TIMER2.C that the definition of the beep() function follows that
    of delay(). We've used the beep() function before, and we will use it
    again in several other programs. You would save program space if you put
    the definitions of beep() and related functions (perhaps line(), another
    function that prints characters in reverse video, a function that draws a
    box around a string, and so on) in a header file. Then you could include
    these functions in any program without typing or pasting them in by hand.
    Simply save the definitions in a file that uses the traditional .h
    extension (hilite.h, for example). Then insert the line #include
    "hilite.h" at the beginning of your program. (You can use the existing
    INCLUDE subdirectory, but your file system would be better organized if
    you created a subdirectory called INCLUDE\USER to hold these user-written
    include files.)

Functions with Many Parameters

    Functions can use any number of parameters. The actual parameters
    specified in the function call parentheses are assigned, in order, to the
    corresponding formal parameters in the function definition.

    As a general rule, however, functions that use more than five parameters
    become cumbersome to use. If you must use more than this number,
    reconsider the operations your function performs and try to do the
    operations in two or more simpler functions. After all, one of the chief
    benefits of using functions is that they keep each piece of a program at a
    manageable size. The LINES.C program (Listing 6-7) uses five parameters
    in its line() function, which draws a colored line on the screen. (To run
    this program, you need a CGA, EGA, or Hercules board.)

    /* lines.c -- calls line() with */
    /*            five parameters   */

    #include <graph.h>

        void line (x1, y1, x2, y2, color);

        int x1, x2, y1, y2, i, color;

        _setvideomode(_MRES16COLOR); /* 320-by-200  16 col. */
        srand(2);                    /* new random seed    */
        for (i = 0; i < 100; i++)
            x1 = rand() % 319;       /* random coordinates */
            x2 = rand() % 319;
            y1 = rand() % 199;
            y2 = rand() % 199;
            color = (rand() % 14) + 1; /* random color 1-15 */
            line(x1, y1, x2, y2, color); /* draw a line   */
        while(!kbhit()); /* wait for key to be hit */

        _setvideomode(_DEFAULTMODE); /* restore video mode */

    void line (x1, y1, x2, y2, color)
    int x1, y1, x2, y2, color;
        _moveto(x1, y1); /* position at first endpoint   */
        _lineto(x2, y2); /* draw line to second endpoint */

    Listing 6-7.  The LINES.C program.

    The line() function draws a line between the points (x1,y1) and (x2,y2)
    using a specified color. As you learned from PIXELS.C in Chapter 5, you
    must use a system of coordinates to specify pixel locations on the
    graphics screen. This program uses the 320-by-200, 16-color mode, with
    coordinates starting at (0,0) in the upper-left corner of the screen and
    ending with (319,199) in the lower-right corner.

    After the program sets the video mode and initializes the random number
    generator with the QuickC library function srand(2), a for loop executes.
    The body of the loop generates random sets of endpoints for lines. (The
    QuickC random number function rand() generates random integers between 0
    and 32,767. To produce a random number between 0 and a number n, we use
    the expression rand() % n. Because the modulus operator gets the remainder
    by dividing the first number by the second, the result is a number greater
    than or equal to 0 [no remainder] and less than the second number.) A
    similar expression generates a random color between 1 and 15. (We can't
    use color 0 because that is the background color──a line drawn in that
    color would be invisible.)

    Finally, the program calls line() and passes five parameters: the two
    pairs of endpoint coordinates and the color number. Notice that commas
    must separate the parameters. The line() function draws the line by first
    calling the QuickC graphics function _moveto() to position the cursor at
    the randomly specified point (x1,y1). Another graphics function,
    _setcolor(), sets the current drawing color to the value of color.
    Finally, the QuickC _lineto() function draws the line to the second
    endpoint (x2,y2). The for loop in main() then repeats the process to draw
    100 random lines in random colors.

Functions That Return Information

    Sending values to a function is only one way that information flows
    between a calling program and a function in C. A function can also send
    information to the program. For example, in the expression

    ch = getche()

    getche() returns a value (the character) to the calling statement, which
    in turn assigns the value to the variable ch. To have a function return a
    value to its caller, add a statement to the function definition that
    consists of the keyword return with the value to be returned enclosed in
    parentheses, as follows:

    return (value);

    Replace value with any information you want the function to return──the
    value of a variable, the result of a calculation, a character the function
    has read, or anything else that can be expressed using one of C's data
    types. When the flow of execution reaches a return statement, the function
    immediately terminates, returning the specified value to the calling
    statement. (A function can have several return statements to return
    different values under different conditions, such as in the branches of an
    if or switch statement. However, the function always terminates at the
    first return statement it encounters.) In the calling statement, the
    returned value replaces the function call, and execution of the statement

    Let's look at a very simple example, a function that accepts a quantity in
    yards, converts it to feet, and returns the result:

    int ytof(yards)
        int yards;
        return(yards * 3);

    This function takes the number of yards passed to it through its formal
    parameter yards. The return statement calculates the number of feet with
    the expression yards * 3 and returns the value to the caller. Suppose the
    calling program uses the following statement:

    distance = ytof(course_length);

    and assume that the user supplies a course_length of 750 yards. When the
    statement calls ytof(), it passes the value of course_length to the ytof()
    function. The function returns 2250 (750 * 3), which replaces the function
    call in the statement. The statement now reads:

    distance = 2250;

    and the assignment operator assigns this value to distance.

    Also note that the definition of the ytof() function begins not with the
    type void but rather with the type int. The return type void signifies
    that the function does not return a value. (We also must declare the
    parameter yards to be an int in the definition of ytof().)

    Now let's look at a more useful example. Some languages have a built-in
    exponentiation operator; C does not. However, the math.h include file
    contains a function called pow() that you can call as pow(x,y). It raises
    the first parameter (x) to the power specified in the second parameter
    (y). Because this function uses double values, it can handle both integer
    and floating-point values with great precision. The EXPO.C program
    (Listing 6-8 on the following page) creates an integer version of this
    function that can respond to various types of input.

    The expo() function takes two parameters (the number to be raised to a
    power and the power to raise it to) and returns a value to the calling
    statement. However, part of designing functions that return values is
    deciding how to handle special cases. This program must be able to handle
    three special cases: an exponent less than 0 (negative), an exponent equal
    to 0, and an exponent equal to 1. Thus, we designed EXPO.C to respond to
    valid inputs, special inputs, and error conditions with appropriate
    messages and return values.

    /* expo.c -- uses exp() function to  */
    /*            calculate powers       */

        int expo(number, power);
        int number, power;

        printf("Enter a number: ");
        scanf("%d", &number);
        printf("Raise to what power? ");
        scanf("%d", &power);

        printf("Result: %d", expo(number, power));

    int expo(number, power)
        int count, value;
        int total = 1;      /* store value of calculation */
        if (power < 0)      /* reject negative exponents  */
            printf("Error in expo(): negative exponent\n");

        if (power == 0) /* any number to 0 power is 1 */

        if (power == 1) /* any number to 1 power is itself */

        /* calculate for power > 1 */
        for (count = 1; count <= power; count++)
            total *= number;

    Listing 6-8.  The EXPO.C program.

    A negative exponent results in a fraction that is less than 1. For
    example, 2 to the -3 power is the same as:

        1                     1
    ─── which is equal to ─── or 0.125
    2^3                    8

    This function handles only positive powers because it uses int type
    variables that can't handle fractions. The first if statement tests the
    power parameter and prints an error message if it is less than zero. The
    return statement returns a value of zero to the calling program, and the
    function terminates.

    Now let's look at the remaining two cases in expo(). If the power
    specified in calling expo() is 0, a return statement returns 1 (any number
    to the zero power is one). If the power specified is 1, expo() returns the
    number itself.

    Finally, a for loop calculates all other cases (positive powers greater
    than 1). Because we initialized total to 1 at the beginning of the
    function, the expression total *= number in the for loop multiplies number
    by itself power times.

    The main() function simply lets you test the expo() function by assigning
    it a number and a power.


    Thus far, calling functions and returning values from them has been a
    simple and straightforward matter. However, you can use function calls in
    a way that disturbs these rules slightly──with amazing results. Recursion
    is an idea that some find difficult to grasp at first, yet a little
    perseverance will lead you to a programming tool of great beauty,
    elegance, and power. Here's how it works: In C, any function can call any
    other function. In fact, a function can call itself──and that is the
    essence of recursion.

    A classic example of recursion is the calculating of the factorial of a
    number. Recall that the factorial of a number is the product of all the
    integers between 1 and that number, inclusive. For example, the factorial
    of 4 (written by mathematicians as 4!) is 1 * 2 * 3 * 4, or 24.

    A program could calculate a factorial by the "brute force" method, using a
    for loop and multiplying the loop control variable by the previous total.
    But there is another way to calculate factorials. Consider that 4! = 4 * 3
    * 2 * 1, and 3! = 3 * 2 * 1. From this we can deduce that 4! = 4 * 3!, or
    in general terms, that the factorial of a number n is equal to n * (n -
    1)!, which in turn is equivalent to n * (n - 1) * (n - 2)!, and so on.
    Eventually we get to 0!, which, by definition, is equal to 1.

    Special Return Values and Error Numbers
    Why return a value if an error occurs? Because the value returned can warn
    the caller that an error has occurred. The warning value selected is a
    value that the function will not return by normal operation. If normal
    operation of the function returns a positive number, a number such as 0 or
    -1 sometimes indicates an error. For functions that don't normally return
    a number, the return value usually indicates something relevant about the
    function's operation: For example, the return value of scanf() is the
    number of data fields read, and a 0 indicates an error. Other functions
    return 0 if their operation was successful; a nonzero return value not
    only indicates an error but also represents an error number that specifies
    the precise problem. As you continue to work with QuickC, you will become
    familiar with how library functions handle errors and what the return
    values mean.

    Figure 6-6a depicts the calculation of 4! and then generalizes the
    calculation of a factorial in Figure 6-6b. Note that you keep breaking
    down the expression by subtracting 1 from the current value of n, taking
    its factorial, and multiplying it by the preceding value. Eventually the
    expression becomes equal to n - n, or 0. But 0! = 1, so we no longer need
    to break down n any further. Multiplying all the values together gives the
    factorial of the original number n.

    The RECURSE.C program (Listing 6-9) demonstrates how to use this
    recursive method to calculate factorials.

                / \
                4 * 3!
                    / \
                    3 * 2!
                    / \
                    2 * 1!
                        / \
                        1 * 0!
                    4! = 24

    (A)           BREAKING DOWN 4!

                /  \
                n * (n-1)!                     ┌───────────────────┐
                /      \           ▒▒▒▒▒▒▒▒►│ (n-(n-1) * (n-n)! │
                (n-1) * (n-2)!       ▒        │   = 1 * 0!        │
                        /      \      ▒        │   = 1 * 1 = 1     │
                    (n-2) * (n-3)!  ▒        └───────────────────┘
                                .     ▒
                                .     ▒
                                .     ▒
                            (n-(n-1) * (n-n)!

    (B)                       BREAKING DOWN n!

    Figure 6-6. Breaking down factorial expressions.

    /* recurse.c -- demonstrates recursion */

    int level = 1; /* recursion level */
        int num, result;

        printf("Factorial of what number? ");
        scanf("%d", &num);
        result = factorial(num);
        printf("Result is: %d\n", result);

    factorial(int number)
        int result;
        printf("entering: ");
        printf("level %d. number = %d. &number = %d\n",
                level++, number, &number);

        if (number == 0)
            result = 1;
            result = number * factorial(number - 1);

        printf("exiting : ");
        printf("level %d. number = %d. &number = %d. ",
                --level, number, &number);
        printf("result = %d\n", result);


    Listing 6-9.  The RECURSE.C program.

A Recursive Function at Work

    Let's examine how the factorial(number) function in RECURSE.C works. An if
    statement terminates the function and returns 1 if number is 0 (because 0!
    is 1). For any other value of number, the else branch executes

    result = number * factorial(number - 1);

    This is a recursive call because factorial() is actually calling itself.

    Now let's follow execution from start to finish. The main() function makes
    the first call to factorial() with the value of number. Because number
    initially is not 0, factorial() executes the else branch and calls itself
    with number - 1. In this new call, execution again encounters the else
    branch, and another call to factorial() results. Although this call also
    uses number - 1, the number here is actually the value factorial()
    received from its previous call, or number - 2.

    As a result of the repeated calls of factorial() to itself, the decreasing
    values passed with each call accumulate on the stack (because the values
    passed to a function call are not removed from the stack until the call
    terminates). Also, remember that each call to factorial() creates a new
    set of the automatic variables number and result. These variables
    accumulate (one set for each call) until the function terminates. It is as
    though QuickC places a "bookmark" in each function call when the next call
    executes so that the compiler can keep track of what remains to be done in

    The printf() statements in the program record the above process, as

    Factorial of what number? 4
    entering: level 1. number = 4. &number = 8720
    entering: level 2. number = 3. &number = 8706
    entering: level 3. number = 2. &number = 8692
    entering: level 4. number = 1. &number = 8678
    entering: level 5. number = 0. &number = 8664
    exiting : level 5. number = 0. &number = 8664. result = 1
    exiting : level 4. number = 1. &number = 8678. result = 1
    exiting : level 3. number = 2. &number = 8692. result = 2
    exiting : level 2. number = 3. &number = 8706. result = 6
    exiting : level 1. number = 4. &number = 8720. result = 24
    Result is: 24

    Notice the new address for each call's version of the number variable:
    This proves that separate automatic variables are being created. (The
    actual addresses the program returns may vary from setup to setup.)

    The "turning point" in the recursive process occurs when number decreases
    to 0, the call factorial(0) is made, and the if branch finally executes.
    Finally the function returns to the caller (the preceding version of
    factorial()), after assigning the value 1 to result. Now the preceding
    call can "pick up its bookmark" and replace factorial(number - 1) with 1,
    multiply it by the value of number that was saved in its automatic
    variable, and then return this value to its preceding caller. You can see
    this happening in the second half of the output listing: The calls move
    back through the recursion levels, back through the addresses of the
    accumulated automatic variables, with each result being multiplied by the
    preceding one, until the function returns to main() with the correct
    result, 24.

Recursion and Stack Size

    Some problems are naturally recursive──searching through directories and
    their subdirectories, parsing commands into subcommands, or working with
    tree structures. One thing to keep in mind, however, is that recursion
    uses a lot of memory for storing automatic variables and the stack. (The
    stack holds not only the parameters passed for each call but also the
    register values and return addresses.) Try running RECURSE.C with larger
    input numbers. You can only use numbers to 7 before type int, which stores
    the result, overflows. If you rewrite the program to use long, you can use
    numbers to 16. With type double, you can generate some truly impressive
    factorials, but trying to generate 62! causes a stack overflow error. The
    stack, which by default can store 2048 bytes, simply cannot hold any more
    recursions. (You can specify a larger stack size by selecting Set Runtime
    Options from the Run menu. Still, there is a limit in our medium memory
    model because the stack and the program data share one 64 KB segment. In
    Chapter 11, we also will show you how to use a compiler command-line
    switch to use memory models in which the stack has an entire 64 KB segment
    to itself.)

Noninteger Functions

    The value returned by a function does not have to be an integer type.
    Functions can return a float, a char, or any other standard C data type.
    So far, we have declared and defined functions using a return data type,
    such as:

    int expo(number, power)

    An older style of C programming omits the return type when the function
    returns an int, because the compiler defaults to int if no type is
    specified. We base our style on the new ANSI standard, which encourages
    declaring return types for all functions and using void for functions that
    do not return values. For example, we might define a function that
    calculates the cube root of a number as:

    float cube_root(number)

    This specifies that the cube_root() function returns a value of type
    float. Remember that we also must declare this function in or before

        char response;
        int x, y;
        float result;
        float cube_root(number);   /* function declaration */

    The GETYN.C program (Listing 6-10 on the following page) demonstrates the
    declaration and use of a noninteger function. It defines a function,
    getyn(), that prompts for a "yes or no" answer, checks to make sure the
    character entered by the user is either a y or an n, and returns the
    entered character. We declare the function at the start of main() and
    define it as follows:

    char getyn()

    because it returns a value of type char. Notice that this function does
    not use a parameter. Although many functions that return values, such as
    the expo() function, require parameters, some functions receive their
    information not from the calling statement but from some other source. In
    the case of getyn(), and indeed with the standard functions that read
    characters (getch(), getche(), and so on), the user supplies the value.

    /* getyn.c -- calls char function getyn()    */
    /*               with error checking         */

    #define TRUE 1

        char ch;
        char getyn();

        printf("Do you want to continue? ");
        if ((ch = getyn()) == 'y')
            printf("Answer was y\n");
            printf("Answer was n\n");
        printf("Value of ch was %c\n", ch);

    char getyn()
        char ch;
        while (TRUE)
            printf(" (y or n) ");
            ch = getche();
            if ((ch == 'y') || (ch == 'n'))
            /* valid response, break out of loop */
            /* give error message and loop again */
            printf("please enter ");

    Listing 6-10.  The GETYN.C program.

    In the getyn() function, a while loop prompts the user to enter a y or n,
    a getche() gets a character, and an if statement checks to see if the
    character is a y or an n. If it is, a break statement exits the loop, and
    the return statement returns the character. If the character is something
    other than y or n, a prompt asks the user to reenter a value, and then the
    loop repeats. Putting an input-type statement in a loop provides a
    framework for error checking.

    The getyn() function is a handy tool that you can use in place of getch()
    or getche() whenever you want the user to enter a valid response to a
    yes/no question.

Function Prototypes

    We've seen that you can declare the return type for a function at the
    beginning of your program, such as:

    float distance(x1, y1, x2, y2);

    This declaration tells QuickC that your function returns a float value.
    That takes care of the value coming out of the function. But what about
    the values going into the function──the function parameters?

    As it stands, this definition does not specify a data type for the
    parameters. Consequently, we might design the function to work with
    integer values and then accidentally give it some double values as real
    parameters. If you don't specify the data type of the function parameter,
    QuickC isn't even aware of the potential problem.

    The MIXTYPES.C program (Listing 6-11) shows what can happen when the type
    of data passed to the function does not match the expected type.

    We wrote this program in the older C style: It has no function declaration
    and only the minimum function definition. After all, the examine()
    function merely prints its parameter. (It expects a parameter of the
    default int type, hence the "%d" format specifier in the printf() control

    In main() we incorrectly declare the variable n as float and call
    examine() with it.

    /* mixtypes.c -- shows problem with calling           */
    /*               a function with wrong type parameter */

        /* didn't bother to declare int function */
        float n = 5.0;
        int i;

        printf("n in main() is %f\n", n);
        i = examine(n);    /* pass float to function */
        printf("examine() returned n as %d\n", i);

    examine(num)  /* function didn't declare return type */
        printf("examine() says n is %d\n", num);

    Listing 6-11.  The MIXTYPES.C program.

    What happens when you run MIXTYPES.C?

    n in main() is 5.000000
    examine() says n is 0
    examine() returned n as 0

    The printf() statement in main() verified a float type. But when we try to
    print the value of this parameter inside examine(), we see that its value
    is now 0. Because we didn't specify a type for the num parameter in
    examine(), the float passed to the function without comment. However,
    because the function expects num to be an int (the default), the float is
    fetched from the stack as if it were an int. (Treating the 4-byte value
    5.0 as a 2-byte int returns two zero bytes, or 0.) Finally, examine()
    returns this incorrect value to main().

    How can we avoid this problem? In Chapter 3, we discussed strategies for
    ensuring sensible type conversions, such as using type casts. Here we need
    to tell QuickC the data type of the function's parameters. You do this by
    providing a complete declaration called a function prototype.

    A function prototype declares the name of the function, its return type,
    the data type of each parameter, and, optionally, the parameter's name.
    Below are some sample declarations:

    int factorial(int number);
    int expo(int number, int power);
    void line(int x1, int y1, int x2, int y2, int color);
    char getyn(void);

    These are all functions we used earlier in this chapter. Although you have
    already used return type declarations, the full ANSI prototypes add an
    additional specification──the data type for each formal parameter. The
    prototype for factorial() indicates that it returns a value of type int
    and accepts one parameter of type int. The prototype for expo() specifies
    that it returns an int and accepts two int parameters.

    Type Checking in Pascal and C
    In the type-mixing situation described above, a Pascal compiler would show
    an error. Pascal checks actual parameter types against the expected
    parameter types and is very strict about making you define types for
    everything. The traditional C philosophy, on the other hand, expects the
    programmer to anticipate problems carefully, so the compiler permits the
    mixing of function parameter types. Thus, because C does not force you to
    declare types for function parameters, it often cannot tell you that
    anything is wrong when the types of the actual and expected parameters
    don't match. If you follow modern C programming practice and define
    function return and parameter types, the compiler will alert you to many
    potential problems.

    The line() function uses a return type of void because it does not return
    a value; it does, however, take five int parameters. Finally, getyn()
    returns a char but has a parameter of void, which signifies that the
    function takes no parameters. In addition, the prototypes specify the
    names of the parameters. (Although the names are optional──you could say,
    for example, int factorial(int)──we recommend using the parameter names in
    the declaration so that all the information is in one place.)

    The beginning of the function definition should also contain the function
    return type, function name, and the list of formal parameters (the
    parameter names), as in the following:

    int factorial(int number)
    int expo(int number, int power)
    void line(int x1, int y1, int x2, int y2, int color)
    char getyn(void)

    You also could declare the parameter types separately on the following
    lines. (Again, the void in parentheses after getyn signifies that the
    function takes no parameters. Also, notice that the function definitions
    do not end with semicolons.)

Advantages of Using Prototypes

    Using prototypes might involve a little more thought and a little more
    typing, but it offers many advantages, which is why the ANSI C standard
    and QuickC support it. First, the complete prototype contains all of the
    information you need to use the function: what you can put in and what you
    can expect to get out. Indeed, if you look up a library function using the
    on-line help, you will find the complete prototype prominently displayed.

    It is important to note that if you use prototypes, QuickC will check both
    the type and number of the parameters in your function calls against the
    type and number of the parameters you specify in the prototype. In cases
    where types are mixed, QuickC will try to promote smaller to larger types.
    (See the examples in Chapter 3.) If you use the wrong number of
    parameters, QuickC will display an error message.

    The PROTO.C program (Listing 6-12 on the following page) is a revision of
    the problem program MIXTYPES.C, rewritten to use function prototypes. This
    program produces more reasonable output than that produced by MIXTYPES.C:

    n in main() is 5.000000
    examine() says n is 5
    examine() returned n as 5

    With the prototype, QuickC knew that examine() needed an int. When the
    program tried to pass it a float, QuickC converted the value to an int
    before passing it to the function. (Some conversions can cause variables
    to lose precision, but the resulting value will be much more likely to be

    /* proto.c -- demonstrates function prototyping */
    /*            and parameter checking           */

        float n = 9995.997;
        int i;
        int examine(int num);  /* declare function */
                                /* with prototype   */

        printf("n in main() is %f\n", n);
        i = examine(n);    /* pass float to function */
        printf("examine() returned n as %d\n", i);

    int examine(num)
        printf("examine() says n is %d\n", num);

    Listing 6-12.  The PROTO.C program.

Putting It All Together: A Larger Program

    To sum up our work with functions and function calls, we present the
    number game GETCLOSE.C (Listing 6-13), which uses 10 functions (counting
    main()). Although this program is much longer than previous programs,
    notice how the use of functions breaks this large, more complex program
    into manageable pieces. Look at the following listing; then we'll see how
    easy to understand this program actually is.

    /* getclose.c -- a number game using */
    /*               random numbers      */

    #define TRUE 1
    #define FALSE 0

        /* external variables */
        int number,     /* total number in current game   */
            moves,      /* number of moves in current game*/
            target,     /* target number to reach         */
            done,       /* true if game is over           */
            score,      /* score of current game          */
            wins = 0,   /* number of games won            */
            losses = 0, /* number of games lost           */
            total = 0;  /* total score                    */

        char move;
        /* function prototype declarations */
        void intro(void);       /* tell player about game   */
        char getyn(void);       /* get yes/no response      */
        int  random(int num);   /* random between 1 and num */
        void new_target(void);  /* target number for game   */
        char get_move(void);    /* get player's move        */
        void do_move(void);     /* generate num from move   */
        void check_move(void);  /* won, lost, or continue?  */
        void show_score(void);  /* show score for game      */
        void show_total(void);  /* show total score         */
        intro();         /* print instructions */

        while (TRUE)     /* new games until user quits */
            printf("\nDo you want to continue? ");
            if (getyn() != 'y')
                break;   /* exit program */

            done = FALSE;
            number = moves = score = 0;
            new_target();  /* target number for this game */
            while (!done)  /* play one game */
                get_move();/* user selects random number  */
                do_move(); /* generate random number      */
                            /* and add                     */
                check_move();  /* win, lose, or continue? */
            show_score();  /* score for this game */
            show_total();  /* total score         */

    void intro(void)
        printf("Welcome to Getclose\n\n");
        printf("The object of this game is to\n");
        printf("try to get as close to the target\n");
        printf("number as possible in as few\n");
        printf("moves as possible by choosing from\n");
        printf("various ranges of random numbers.\n");
        printf("You score if you get within 4 of the\n");
        printf("target. You get a 100-point bonus for\n");
        printf("hitting the target, but you get no score\n");
        printf("if you go over.\n\n");
    char getyn(void)
                        /* get yes or no answer      */
                        /* repeats until valid entry */
        char ch;  /* character to read and return */
        while (TRUE)
            printf(" (y or n) ");
            ch = getche();
            if ((ch == 'y') || (ch == 'n'))
            /* valid response, break out of loop */
            /* give error message and loop again */
            printf("please enter ");

    int random(int num)
        /* generate random number between 1 and num     */
        /* doesn't use library function srand() because */
        /* we don't want the same seed each time        */
        long seconds, result;
        time(&seconds);   /* randomize with system time */
        return(abs ((int)seconds * rand() % num) + 1);

    void new_target(void)
                /* generate a new target number */
                /* between 50 and 99            */
        target = 50 + random(49);
        printf("\nYour target for this game is %d\n",

    char get_move(void)
        while (TRUE)
            printf("\nPick a random number from 1 to\n");
            printf("a) 5  b) 10  c) 25  d) 50  e) 100 ");
            move = getche();
            if ((move >= 'a') && (move <= 'e'))
                ++moves; /* count the move */
                break;   /* valid response */
            /* invalid response, try again */
            printf("\nPlease type a, b, c, d, or e\n");

    void do_move(void)
        int num = 0;  /* random value to obtain */
        switch (move)
            case 'a' :
                num = random(5);
            case 'b' :
                num = random(10);
            case 'c' :
                num = random(25);
            case 'd' :
                num = random(50);
            case 'e' :
                num = random(100);
        number += num;  /* add new number to total */
        printf("\n\nYou got a %d. Number is now: %d ", num, number);
        printf("(Target is %d)\n", target);

    void check_move(void)
        int temp;
        if (number > target)
            printf("\nYou went over! ");
            printf("No score this game.\n");
            done = TRUE; /* to break out of loop */
        if (number == target)
            printf("\nYou hit the target ");
            printf("for 100 bonus points!\n");
            score = (100 / moves) + 100;
            total += score;
            done = TRUE;
        if ((number >= (target - 4)) && (number < target))
            temp = 100 / moves;
            /* does player want to go for broke? */
            printf("\nTake %d points (y) or continue (n)? ",
            if (getyn() == 'y')
                score = temp;
                total += score;
                done = TRUE;

    void show_score(void)
        printf("\nYou scored %d points in %d moves.\n",
            score, moves);

    void show_total(void)
        printf("You have won %d game(s) ", wins);
        printf("and lost %d.\n", losses);
        printf("Your total score is %d\n", total);

    Listing 6-13.  The GETCLOSE.C program.

Overview of the Game

    GETCLOSE.C is a number game in which you try to reach a randomly generated
    target number between 50 and 99 in as few moves as possible. Each "move"
    consists of choosing one of five possible ranges of random numbers: 1─5,
    1─10, 1─25, 1─50, and 1─100. You start at zero, and each number you choose
    is added to your total. Thus, each move brings your total closer to the
    target number. If you get within 4 of the target, you can settle for a
    score that depends on the number of moves you've made, or you can continue
    to try to hit the target exactly. If you actually hit the target number,
    you get a 100-point bonus. If you go over, however, you lose and score
    nothing. The program lets you play new games, and it keeps track of your
    total score and the number of games you have won and lost.

    The strategy of the game involves deciding how large a range from which to
    pick the next random number. If you pick from the larger ranges, you can
    reach the target number in only a few moves, gaining you a high score. But
    the big ranges also present you with a greater chance of overshooting the
    target, giving you no points at all. (Playing the game is a little like
    playing blackjack, except that you have five different decks from which to
    choose cards.)

Playing the Game

    Type in the program, run it, and play a few games to get an idea of the
    different operations the program performs. Then read the next section to
    explore the program's inner workings. The following is a sample game:

    Welcome to Getclose

    The object of this game is to
    try to get as close to the target
    number as possible in as few
    moves as possible by choosing from
    various ranges of random numbers.
    You score if you get within 4 of the
    target. You get a 100-point bonus for
    hitting the target, but you get no score
    if you go over.

    Do you want to continue?  (y or n) y

    Your target for this game is 93

    Pick a random number from 1 to
    a) 5  b) 10  c) 25  d) 50  e) 100 e

    You got a 31. Number is now: 31 (Target is 93)

    Pick a random number from 1 to
    a) 5  b) 10  c) 25  d) 50  e) 100 d

    You got a 13. Number is now: 44 (Target is 93)

    Pick a random number from 1 to
    a) 5  b) 10  c) 25  d) 50  e) 100 d

    You got a 19. Number is now: 63 (Target is 93)

    Pick a random number from 1 to
    a) 5  b) 10  c) 25  d) 50  e) 100 d

    You got a 13. Number is now: 76 (Target is 93)

    Pick a random number from 1 to
    a) 5  b) 10  c) 25  d) 50  e) 100 c

    You got a 16. Number is now: 92 (Target is 93)

    Take 20 points (y) or continue (n)?  (y or n) y

    You scored 20 points in 5 moves.
    You have won 1 game(s) and lost 0.
    Your total score is 20

The main() Function

    As you have seen, the main() function of a C program controls the overall
    flow of the program, while calls to various functions do the actual work.
    To explore GETCLOSE.C, look at the structure of main(). Even without
    knowing how the functions work, you can see the general structure of the
    program. First, it calls the intro() function to explain the game briefly.
    (Notice that we commented each of the function prototype declarations in a
    block before main(). This quickly lets you understand the purpose of every

    The outer while loop permits unlimited games until the user decides to
    quit. At the start of each game, new_target() generates a new target
    number, and then the inner while loop processes the player's moves until
    the game ends. In this loop, getmove() prints a menu and lets the user
    select a range of random numbers for the next move. The do_move() function
    gets the random number and adds it to the player's current total;
    check_move() then compares the current total and the target number and
    decides if the player has won, lost, or can continue playing.

    Finally, when the inner loop (which represents one game) ends,
    show_score() displays the score of the last game, and show_total()
    displays the total score and the games won and lost thus far.

    You can also use the QuickC Calls menu to help you understand how the
    function calls work. First, compile GETCLOSE.C with Debug selected, and
    then move the cursor to the body of a function you want to examine. Use
    the Debug menu to set a breakpoint there, then run the program. When the
    program stops at the breakpoint, pull down the Calls menu to see a list of
    all called functions. Then display the text of any listed function by
    selecting it. The Calls menu is especially useful for examining programs
    that nest function calls.

Modifying GETCLOSE.C

    One of the best ways to improve your knowledge of C is to take a program
    such as GETCLOSE.C and try to add features to it. You might already have
    some ideas in mind from playing and studying the game. Here are some

    1.  Give points for how close the player gets to the target number.

    2.  Generate a "poison number" between 1 and the target number minus 10.
        If the player gets within 4 of this number without going over, he or
        she loses.

    3.  Similarly, you could specify a "free number" that, when reached, is
        not counted as a turn, thus potentially increasing the player's score.


Chapter 7  Arrays

    In programming, it is often advantageous to collect variables into sets or
    lists, so that many values can be stored and manipulated as a single
    conceptual unit. Such a collection of variables, when all those variables
    are the same type, is called an array. Arrays are used to organize values
    in applications that range from the "top ten" scores in a video game to
    the payroll records for thousands of employees.

    To visualize the advantage of arrays over simple variables, imagine that
    you run a business and that you want to store each employee's working
    hours in your computer. If you have even two employees, a year's worth of
    variables might look like this:

    int emp1_jan_1, emp2_jan_1;
    int emp1_jan_2, emp2_jan_2;
    ...──────────────────────────────────And so on for 362 intervening lines
    int emp1_dec_31, emp2_dec_31;

    Clearly, even the typing is a monumental undertaking. You can express the
    same number of variables as an array in C in a single line of code:

    int employees[2][365];

    Without a doubt, arrays let you organize data more concisely than do
    simple variables. This is convincingly illustrated in Figure 7-1.

    Although the details of declaration, storage, and retrieval differ from
    language to language, the basic nature of an array does not. In its
    simplest form, an array consists of one or more variables of the same
    storage type (size and number of bytes), arranged one after the other,
    continuously upward in memory. All variables in an array are referenced by
    a single name, called an identifier.

    │ Figure 7-1 can be found on p.190 of the printed version of the book.   │

    Figure 7-1. Arrays provide superior organization over simple variables for
    many common C programs.

How Arrays Are Stored in Memory

    The elements in an array are stored consecutively in memory. An array
    consisting of only four int values, for example, is stored in memory as
    shown in Figure 7-2. Four int values (of two bytes each on the IBM PC)
    are arranged together in ascending order in memory. That is, the array
    begins with the leftmost int──the one lowest in memory──and continues
    upward in memory with one or more adjoining int values.

    Array of ──── │     int     │     int     │     int     │     int     │
    4 ints        └──────┴──────┴──────┴──────┴──────┴──────┴──────┴──────┘

    Memory ───────── 46     47     48     49     50     51     52     53

    Figure 7-2. An array of four int values as stored in memory.

How to Declare Arrays

    When you declare an array, you must tell the compiler how many items of
    which data type to set aside in memory for storage and the name to use
    when referencing that storage.

    The rules for declaring an array in C are relatively straightforward:

    ■  First, declare the type (char, float, int, etc.).

    ■  Second, declare the name. Array names use the same naming conventions
        as variable names.

    ■  Third, declare the number of items in the array by placing an integer
        constant expression inside square brackets; for example, [15].

    You recall, of course, that an "integer constant expression" is any
    integer constant value or any combination of integer constant values and
    arithmetic operators. Thus, 15 is an integer constant value, and so are 3,
    0x0F, and `a'. The last is a character constant that C views as an integer
    constant. But 3.0 is a floating-point constant and thus illegal for
    specifying the array size. The expression 15 * 2 is an integer constant
    expression because it is one integer constant value multiplied by another.

    Remember, you cannot specify the number of elements in an array with a
    variable. The following array declaration, for example, is illegal:

    int line_num = 15;
    int widths[line_num];

    BASIC and Pascal Array Declarations
    Declaring an array in C is similar to declaring an array in BASIC and
    Pascal. In BASIC, an array is declared as widths%(15); in Pascal, the same
    array is declared as widths : ARRAY [1...15] OF INTEGER. In C, we declare
    this array as:

    int widths[15];

    All three of these declarations identify an array named widths composed of
    15 integer variables. Each sets aside room in memory for 15 int values (30
    bytes on the IBM PC because each int occupies two bytes). As you can see,
    declaring an array in C retains the simplicity of BASIC but is clearer
    than BASIC.

    The 1...15 expression in the Pascal declaration tells the compiler that
    the elements in the array are represented by the numbers 1 through 15. In
    C, array elements are always numbered beginning with 0 and ascending to
    the specified number.

    You cannot use the expression line_num to declare the number of elements
    in the array widths. The C compiler looks at only one small piece of a
    program at a time. It first sees int line_num = 15 and generates code to
    store the value 15 into the variable line_num. When it reaches the array
    declaration, all it knows of line_num is that it currently contains the
    value 15; QuickC has no idea how that value will change as the program

    In C, a constant variable, that is, one declared with the ANSI keyword
    const, is not considered a constant value. Therefore, the following
    example is illegal:

    const int line_num = 15;
    int widths[line_num];

    Using the const keyword declares to the compiler that you do not intend to
    change the value of line_num. It does not prevent a bug in your program
    from accidentally changing that value.

    If you attempt to declare the size of an array with a variable, even a
    const variable, QuickC will print the following error message:

    Error C2057:
    expected constant expression

    Examine the ARRAY1.C program (Listing 7-1). This "do-nothing" program
    demonstrates the correct way to declare arrays.

    In this program, SIZEOARRAY is specified using #define as 26. The number
    of items in ages is therefore declared with 26 * 2, which is legal.

    /* array1.c -- how to declare arrays legally        */

    #define SIZEOARRAY 26

        char  initials[26];
        int   num_men[26], num_women[SIZEOARRAY];
        float ages[SIZEOARRAY * 2];

    Listing 7-1.  The ARRAY1.C program.

Referencing and Using Array Items

    The way you reference the items in an array (whether to store values in
    them or to fetch values from them) looks very much like the declaration.
    You merely state the identifier for the array and place the offset of the
    item within square brackets. The offset is always measured from the
    beginning of the array, with the first item having an offset of 0.

    For example, the expression

    widths[1] = 3;

    stores the value 3 in the second item of the array named widths; that is,
    the item whose offset from the beginning of the array is 1.

    Conversely, the expression

    this_width = widths[1];

    retrieves the value of the second item of the array widths and assigns
    that value to the variable this_width.

    In C, the offset into an array can be the result of any expression that
    returns a value. It can be the value of a variable, the result of a
    computation or logical test, or even the returned value of a function
    call. The only restriction is that the array offset must be specified with
    an integer value. The following are legal specifications:

    widths[1]───────────────────────────────────────────Offset is a constant
    widths[i]──────────────────────────────────────Offset is an int variable
    widths[i++]──────────────────────────────────────Offset is a computation
    widths[getnum()]───────────────────────────Return value of function call
    widths['a']────────────────────────────────────Offset is a char constant

    However, the following float type offset causes a compiler error:

    widths[fltval]──────────────────────────────────────float offset (error)

    Because there are no fractional memory locations, specifying an array's
    offset with a float causes the following QuickC error:

    error C2108:
    non-integral index

    With a legal offset specification, an array element is no different from
    an ordinary variable of that type. You can perform the same operations
    with an array item as you can with any ordinary variable. Consider, for

    widths[1] = 3;
    total  = widths[0] + widths[1] + widths[2];

    In the first operation, the value 3 is stored in the second element of the
    array named widths. In the second, values stored in three array elements
    are added together. Notice that we use the same notation to access each──
    an offset in brackets.

An Example

    Now that you have the basic rules for declaring arrays and accessing items
    in those arrays, examine the XMAS.C program (Listing 7-2).

    In XMAS.C, we declare the array widths to contain 20 items of the type
    int. That array is then filled with values by the for loop; this is one
    way to store values in an array. Finally, each item in the array widths is
    passed to the function Center_out(); this is one way to access the values
    in an array.

    /* xmas.c -- fills an array with values, then passes */
    /*           each of those values to a function      */

        int i, j, widths[20];
        void Center_out();

        for (i = 0, j = 1; i < 18; ++i, j += 2)
            widths[i] = j;
        widths[i++] = 3;
        widths[i] = 3;

        for (i = 0; i < 20; i++)
            Center_out('X', widths[i]);


    void Center_out(char character, int width)
        int i;

        for (i = 0; i < ((80 - width) / 2); ++i)
            putch(' ');
        for (i = 0; i < width; ++i)

    Listing 7-2.  The XMAS.C program.

    Whenever you reference an array element, the value of that element becomes
    available for use──you can assign the value to other variables or pass it
    to a function. Note, however, that passing an array element by giving its
    name and an offset merely passes a copy of that element, not the element
    itself. This is the method for passing ordinary variables.

Bounds Checking Arrays in Your Code

    In XMAS.C, the for loop prevents your specifying an offset beyond the end
    of the array widths:

    for (i = 0; i < 20; ++i)

    In many programs, however, the ability to exceed an array's bounds is not
    prevented by your code but is controlled by the user. For example, the
    SADD.C program (Listing 7-3) is a simple adding machine that lets the
    user enter as many as three numbers, one per line, and terminate entry
    with any non-numeric character such as an x.

    Now run SADD.C and enter (on separate lines) the numbers 1, 2, and 3,
    followed by an x character. The program displays the correct sum,
    which is 6:


    /* sadd.c -- a small adding machine that illustrates  */
    /*            the need for array bounds checking      */

        int offset = 0, i, result = 0;
        int stack[3];

        while (scanf("%d", &stack[offset]) == 1)
        for (i = 0; i < offset; ++i)
            result += stack[i];
        printf("%d\n", result);


    Listing 7-3.  The SADD.C program.

    Now run the program again, but this time enter four numbers:


    The result shown is 20, which is wrong. But QuickC doesn't recognize that
    an error occurred and prints no error message. The C language itself,
    unlike Pascal and BASIC, contains no provisions to prevent offsets beyond
    the end of an array.

    To understand why SADD.C referenced stack[3], even though no fourth item
    exists (remember that arrays begin with item 0), let's look again at the
    way arrays are arranged in memory. As a bonus, we'll find out where the
    value 20 came from. Figure 7-3a shows how the compiler translates into
    memory the declarations from SADD.C (shown on the next page).

    │°                                        Initialized         °│
    │°       int offset=0, i, result=0;▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  °│
    │°       int stack[3];                                     ▒  °│
    │°  ▒▒▒▒▒while (scanf("%d", &stack [offset]) == 1)         ▒  °│
    │°  ▒          ++offset;                 ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  °│
    │°  ▒                                    ▒                 ▒  °│
    │°  ▒    ┌────────┬────────┬────────┬────▼───┬────────┬────▼───┤
    │°  ▒    │    ?   │    ?   │    ?   │    0   │    ?   │    0   │
    │°  ▒    └────────┴────────┴────────┴────────┴────────┴────────┤
    │°  ▒     stack[0] stack[1] stack[2]  result      i     offset◄├─────┐
    │°  ▒                                                     °│     │
    └─────────────▒────────▒────────▒────────▒─────────────────────┘     │
        ▒▒▒▒▒▒▒▒▒▒1▒▒▒▒▒▒▒▒2▒▒▒▒▒▒▒▒3▒▒▒▒▒▒▒▒4                           │
                │                            │                          │
                └──────────────┬─────────────┘                          │
                                │                                        │
                    Stores these input values                          │
                    while incrementing offset──────────────────────────┘


    │    1   │    2   │    3   │    4   │    ?   │    4   │
    stack[0] stack[1] stack[2]│ result      i     offset
            Array ends here───┘    │
                            Position of stack[3]


    Figure 7-3. Consequence of referencing beyond the end of an array.

    int offset = 0, i, result = 0;
    int stack[3];

    First, the variables offset and result are set to 0. Then the while loop
    fills out the items in the 3-element array stack. The last value entered
    was 4, which was the fourth value placed into stack. But stack contains no
    fourth item (that is, no stack[3] ), so the fourth value is placed into
    result because result occupies the place in memory that follows the stack

    The arrangement depicted in Figure 7-3a occurs because QuickC places auto
    variables into memory from the top down, but it places static and global
    variables into memory from the bottom up.

    Now look closely at Figure 7-3b. Because result begins with a value of 4
    instead of 0, the for loop adds 1, 2, and 3 to it, resulting in a sum of
    10. The for loop continues (because offset is 4) by adding stack[3] to
    result (both reference the same address and the same value, 10), thus
    yielding the erroneous value of 20.

    To make programs less sensitive to improper user input, always provide
    code that detects array out-of-bounds conditions. You can do this by
    simply terminating the program when an error is detected, but writing your
    program so that it can recover from errors is better. The SADD2.C program
    (Listing 7-4) shows a common approach to array bounds checking that
    corrects the previous program's weakness.

    /* sadd2.c -- a small adding machine that includes */
    /*            array bounds checking                */

    #define MAXSTAK 3

        int offset = 0, i, result = 0;
        int stack[MAXSTAK];

        while (scanf("%d", &stack[offset]) == 1)
            if (++offset >= MAXSTAK)
                printf("Stack Full\n");
        for (i = 0; i < offset; ++i)
            result += stack[i];
        printf("%d\n", result);


    Listing 7-4.  The SADD2.C program.

    In this revision of SADD.C, we use the preprocessor #define directive to
    create an alias for the size of the array stack. Thus, you can later
    easily change the number of items in stack by changing a single #define
    and then recompiling.

    Next, we insert an if statement that checks a preincremented offset to be
    sure it does not exceed the number of items in stack, that is, MAXSTAK. If
    offset becomes too large, the if statement first causes a warning message
    to be printed and then breaks out of the while loop. The user gets an
    accurate sum for the numbers entered, despite the error, and any
    out-of-range numbers are simply ignored.

How to Initialize Arrays

    When you declare an auto array (an array declared inside a function and
    without the keyword static), it is initially filled with garbage values,
    regardless of its type and size. (This also occurs when you create auto
    variables.) On the other hand, static arrays and arrays declared outside
    of functions always have their initial values set to zero──again just like
    ordinary variables. For example:

    char Letters[26];───────────────────────────────────Initialized to zeros

        char vowels[5];──────────────────────────────────Starts with garbage
        static char consonants[21];─────────────────────Initialized to zeros

    When zero is not an appropriate initial value, you can give static and
    global variables starting values of your choice. To initialize an array:

    ■  Follow the right square bracket (]) in the array identifier with an
        assignment operator (=) and a left brace ({).

    ■  Then state each initializing value, followed by a comma.

    ■  Finally, terminate your comma-separated list of initializers with a
        right brace (}) and the usual semicolon.

    For example, to initialize the array Letters with the letters of the
    alphabet, you need simply declare it as:

    char Letters[26] = { 'a', 'b', 'c', 'd', 'e',
                            'f', 'g', 'h', 'i', 'j',
                            'k', 'l', 'm', 'n', 'o',
                            'p', 'q', 'r', 's', 't',
                            'u', 'v', 'w', 'x', 'y',
                            'z', };

    Or, for an array of numbers you could declare:

    int Values[5] = { 12, 2, 44, 19, 7 };

    In both of the above examples, the type of the initializing values matches
    the type of the array and is a constant value. This is mandatory. Values
    in array initializing

    lists must be constant values or constant expressions, and those values
    must fit in the number of bytes declared for each array item. The array
    Letters is initialized with the type char, and `a' is a char constant. The
    array Values is initialized with type int, and 12 is an integer constant.
    A float array would have to be initialized with the type float, such as
    76.98, which is a floating-point constant.

    The ASIMOV.C program (Listing 7-5) contains in the initialized array
    Letters the name of a famous Isaac Asimov character. By entering the
    correct series of numbers, you can reveal that name.

    /* asimov.c -- illustrates how to initialize an      */
    /*             array with starting values            */

    #define MAXL 16
    char Letters[MAXL] = {
        'e', 'I', 'a', 'N', 'o', 'R', 'O', 'o',
        'u', 't', 'o', 'R', 'l', 'o', 'B', 'b', };

        int num, i;

        printf("Guess my identity with numbers.\n");
        printf("(any non-number quits)\n\n");

        while (scanf("%d", &num) == 1)
            if (num <= 0)
                printf("Guesses must be above zero\n");
            for (i = 1; i <= num; ++i)
                printf("%c", Letters[(i * num) % MAXL]);

    Listing 7-5.  The ASIMOV.C program.

    Quick Tip
    The list of initializers for Letters ends with a trailing comma. That is
    not an error. Trailing commas in initializer value lists are optional but
    enable long lists to be rearranged easily with your text editor.

Letting the Compiler Supply the Size

    When you declare the values for an initialized array, it is not always
    possible, or necessary, to state explicitly the number of items in the
    array. C provides an alternative. For example, the following declaration
    omits the size of the array:

    int  Primes[ ] = {1, 2, 3, 5, 7, 11,};
                └─────────────────────────────────── Number of items omitted

    Whenever you omit the size, the C compiler counts the number of
    initializers and dimensions the array to the correct size for that count.
    Because the preceding example contains six initializers, that declaration
    is equivalent to declaring:

    int  Primes[6] = {1, 2, 3, 5, 7, 11,};

    When the size of an array is omitted, you might expect bounds checking in
    your code to be difficult. Fortunately, you can use the sizeof operator to
    find the number of bytes in an array and thus specify, using #define, a
    bounds-checking value. When used with the preceding declaration, the

    #define MAXL (sizeof(Primes)/sizeof(int))

    gives MAXL a value of 6.

    The sizeof keyword, when given the name of an array, yields the number of
    bytes in that array. The #define, then, is the number of bytes in the
    array Primes (12 bytes) divided by the number of bytes in an int (2

Overinitializing and Underinitializing

    One good reason to omit the size of an array is that C permits a mismatch
    between the number of initializing values and the size you declare. When
    there are fewer initializers, the compiler silently fills the remainder
    with zero values. When there are too many initializers, the compiler
    complains and stops. Some compilers (especially under UNIX) will issue a
    warning and truncate the array.

    The UNDOVER.C program (Listing 7-6) demonstrates this behavior. As the
    program stands, it prints the following message:

    The first 6 primes are: 1 2 3 5 7 11

    Now change Primes[6] to Primes[8] in the declaration and run the program
    again. This time it prints:

    The first 8 primes are: 1 2 3 5 7 11 0 0

    The new result shows that underinitializing causes the compiler to fill
    the leftover items with zero values. Now change the declaration again,
    this time from Primes[8] to Primes[3]. This time the QuickC compiler stops
    and issues the message:

    error C2078:
    too many initializers

    /* undover.c -- illustrates the effect of underinitializing and */
    /*              overinitializing arrays                         */

    int Primes[6] = { 1, 2, 3, 5, 7, 11 };
    #define NUMP (sizeof(Primes) / sizeof(int))

        int i;

        printf("The first %d primes are: ", NUMP);
        for (i = 0; i < NUMP; ++i)
            printf("%d ", Primes[i]);

    Listing 7-6.  The UNDOVER.C program.

Arrays and Functions

    As you saw earlier in XMAS.C, passing one of an array's elements to a
    function is like passing an ordinary variable to a function. That is, a
    copy of the value of that element is passed, not the element itself.

    However, when you pass whole arrays to functions, the situation changes.
    Although you are still passing by value, what you are actually passing is
    the address of the array (its location in memory). The effect of this is
    that you appear to be passing the array itself and that the array itself
    will be changed. (See the next chapter for further details.) For now,
    we'll simply show you how to pass arrays to functions and how to use those
    arrays when they get there.

Passing Arrays to Functions

    To pass an array to a function, merely state the array's name (minus the
    offset) in the function call. For example, if you have declared an array
    as follows:

    static int list[7] = { 5, 1, 3, 7, 2, 4, 6 };

    you would pass that array to a function, called Bub_sort() for example, by
    stating its name, as follows:


    This tells the compiler to send the entire array named list to Bub_sort().

    At the receiving end, in Bub_sort(), you need to declare the type of the
    received array. To do so, declare an array in the normal C manner and
    leave the square brackets empty:

    int vals[];

    This declares that a function named Bub_sort() will receive one argument,
    the int array vals. Because Bub_sort() is receiving an array──via the
    array's address──it receives the array itself and not a copy of that
    array. This allows Bub_sort() to change the original array.

    The BUBSORT.C program (Listing 7-7) demonstrates the differences between
    passing array elements to functions and passing entire arrays.

    /* bubsort.c  --  passing an array to a function  */
    /*                affects the original array      */

    #define NUMINTS 6

    extern void Bub_sort();
    extern void Triple();
        int num = 2, i;
        static int list[NUMINTS] = { 6, 5, 4, 3, 2, 1 };

        printf("num before Triple = %d\n", num);
        printf("num after Triple = %d\n", num);
        printf("list[0] before Triple = %d\n", list[0]);
        printf("list[0] after Triple = %d\n", list[0]);
        printf("Before sorting -> ");
        for (i = 0; i < NUMINTS; ++i)
            printf("%d ", list[i]);

        printf("After sorting ->  ");
        for (i = 0; i < NUMINTS; ++i)
            printf("%d ", list[i]);

    void Triple(int x)  /* function doesn't affect original */
        x *= 3;

    void Bub_sort(int vals[NUMINTS]) /* function changes original */
        int i, j, temp;

        for (i = (NUMINTS - 1); i > 0; --i)
            for (j = 0; j < i; ++j)
                if (vals[j] > vals[j+1])
                    temp      = vals[j];
                    vals[j]   = vals[j+1];
                    vals[j+1] = temp;

    Listing 7-7.  The BUBSORT.C program.

    This program first calls the Triple() function, passing it both an
    ordinary variable and one of the elements in the array list. The value of
    the original isn't changed in either case; only the value (a copy) of what
    is sent is changed.

    Next, we pass the array list to the function Bub_sort() (a simple bubble
    sorting function). The program prints the array before and after the
    function call to demonstrate that the Bub_sort() function changes the
    values in the original array; it does not sort a copy.


    When a function receives an array, the number of elements in the
    declaration of that array is usually omitted because the size
    specification is optional:

    int vals[ ];
                └──────────────────────────────────────── Size of array omitted

    Restating that size in the function declaration, however, is often a good
    idea. As we have seen, C does no array bounds checking on your behalf, so
    restating the size of the received array helps to clarify and document
    your program:

    int vals[NUMINTS];
                └────────────────────────────────────── Restated for clarity

    The type of the received array should also match that of the original. If
    the types do not match, your program might not work properly. The reason
    we say "might not" is that you might want to mismatch types intentionally.
    The HEXOUT.C program (Listing 7-8) demonstrates such a planned mismatch.
    This program asks you to enter a floating-point number and then prints out
    that number, one byte at a time, in hexadecimal notation.

    HEXOUT.C first reads a floating-point value into the array fary──a float
    type array consisting of only one element. That array is then passed to
    Hexout(). In Hexout(), we declare the type of the received array as
    unsigned char. This "deception" causes Hexout() to handle the array fary
    as if it were an array of single unsigned bytes, whereas the original was
    actually a 4-byte float type. We will explain this shortly. This deception
    illustrates one of C's primary strengths, the freedom of the programmer to
    change types in midstream.

    /* hexout.c --  prints a floating-point variable in */
    /*              hexadecimal format                  */

    extern void Hexout();
        float fary[1];

        printf("Enter a floating-point number\n");
        printf("(Any non-numeric entry quits)\n\n");
        while (scanf("%f", &fary[0]) == 1)

    void Hexout(unsigned char chary[])
        int i;

        for (i = 0; i < sizeof(float); ++i)
            printf("%02X ", chary[i]);

    Listing 7-8.  The HEXOUT.C program.

How Array Offsets Advance

    When you reference an array element with an array name and an offset,
    QuickC invisibly converts the element offset to a bytes-in-memory offset.
    To illustrate this, let's look at how two different type arrays are
    organized in memory. Figure 7-4 shows that an array of char type occupies
    one byte of memory for each element and that an array of float type
    occupies four bytes of memory for each element.

    For the char array, chary[4], each element occupies one byte, so each
    element offset specification corresponds to the bytes-in-memory offset.
    For the float array fary[2], however, each element occupies four bytes, so
    each element offset specification corresponds to a bytes-in-memory offset
    of 4 bytes. In the latter case, when you specify


    you are really telling the compiler to reference a value that is four
    bytes into the array fary[]. Because a float value occupies four bytes of
    memory, your element offset of 1 becomes a bytes-in-memory offset of 4

    This explains how, in the preceding program, HEXOUT.C, it is possible to
    print out a float one byte at a time. Inside the function Hexout(), the
    compiler thinks it is handling an array of unsigned char type, in which
    each element occupies a single byte. Thus, i increments in unmultiplied
    steps of single bytes.

                    char chary[4] = {'t', 'e', 's', 't'};

                        │ 1 byte │
                        │        │
                        │   't'  │   'e'  │   's'  │    't' │

    (A)                        ARRAY OF char VALUES

    float fary [2] = {1.2, 4.3};

    │ 1 byte │
    │        │
    │                1.2                │                4.3                │
                    fary[0]                             fary[0]

    (B)                        ARRAY OF float VALUES

    Figure 7-4. How offsets differ by number of bytes based on the type of the

Multidimensional Arrays

    In C you can easily create and use arrays of two, three, or many
    dimensions. Two-dimensional arrays correspond to such useful items as
    calendars, spreadsheets, and maps. We begin with the rules for
    two-dimensional arrays and then apply them to arrays of three and more
    dimensions. Three-dimensional arrays find application in items such as 3-D
    graphics, layered indexes, and solid topology. Many-dimensioned arrays
    delve into the arcane worlds of higher math and complex games.

Two-dimensional Arrays

    Two-dimensional arrays represent rectangular grids of data. As illustrated
    in Figure 7-5, they are organized in rows first and then columns. Because
    computer memory is linear, those rows and columns are stored in memory one
    row after the other.

    The rules for declaring a two-dimensional array are similar to those for
    declaring a one-dimensional array. They take the following form:

    type name[rows][columns];

    As with one-dimensional arrays, you must specify the number of rows and
    the number of columns as integer constant expressions. Thus, the

    int week[7][8];

    declares a two-dimensional array of type int, named week, with 7 rows (for
    7 days) and 8 columns (for 8 working hours per day). You can fill it with
    any number of useful items, for example, the number of lines of code
    written per hour per day.

                            │                       │
                            ┌───────┬───────┬───────┐ ───┐
                            │   1   │   2   │   3   │    │
                            ├───────┼───────┼───────┤    │
                            │   4   │   5   │   6   │    ├── Rows
                            ├───────┼───────┼───────┤    │
                            │   7   │   8   │   9   │    │
                            └───────┴───────┴───────┘ ───┘
                                        │  Arranged in memory as
    │   1   │   2   │   3   │   4   │   5   │   6   │   7   │   8   │   9   │
    │                       │                       │                       │
                │                       │                       │
            Zeroth row               First row              Second row

    Figure 7-5. Two-dimensional arrays are arranged in memory row by row.

    Referencing two-dimensional array elements is as simple as referencing
    elements of one-dimensional arrays. Specify the row and column with
    integer expressions:

    int day  = 6, /* Saturday */
        hour = 2; /* 10:00 A.M. */

    printf("Wrote %d lines of code.\n", week[day][hour]);

    Initializing Two-dimensional Arrays

    Before we go into the somewhat more complex rules for initializing
    two-dimensional arrays, enter the MAGIC.C program (Listing 7-9). This
    program initializes an array of type int with scrambled numbers and then
    asks the user to rearrange those numbers into the correct order by
    continually swapping squares adjacent to the 0 with the 0. The object is
    to rearrange the numbers into ascending order, with 0 at the top left,
    counting up row by row.

    /* magic.c  --  demonstrates use of a two-dimensional  */
    /*              array of type int                      */

        static int square[3][3] = { 5, 8, 3, 4, 2, 0, 7, 1, 6 };
        int zrow = 1, zcol = 2;  /* location of the zero */
        int num, row, col, i, j, rowdist, coldist;

        while (1)
            printf("Swap what with zero?\n");
            printf("(Q to quit)\n");

            /* Print the square. */
            for (i = 0; i < 3; ++i)
                for (j = 0; j < 3; ++j)
                    printf(" %d ", square[i][j]);

            /* Enter the user number. */
            if ((num = getch()) == 'Q')
            num -= '0';
            if (num < 1 || num > 9)
                printf("Not a legal number.\n\n");
            /* Find that square. */
            for (row = 0; row < 3; ++row)
                for (col = 0; col < 3; ++col)
                    if (num == square[row][col])
                        goto GOTIT;
            /* Check for a legal move. */
            if (row > 2 || col > 2)
                printf("Bad Box Specification\n\n");
            rowdist = zrow - row;
            if (rowdist < 0)
                rowdist *= -1;
            coldist = zcol - col;
            if (coldist < 0)
                coldist *= -1;
            if (rowdist > 1 || coldist > 1)
                printf("Not a Neighbor\n\n");

            /* Make the move. */
            square[zrow][zcol] = square[row][col];
            square[row][col] = 0;
            zrow = row;
            zcol = col;

            /* See if done, and solved. */
            for (i = 0; i < 3; ++i)
                for (j = 0; j < 3; ++j)
                    if (square[i][j] != ((i * 3) + j))
            if ((i * j) == 9)
        printf("\n\aYOU GOT IT !!!\n");

    Listing 7-9.  The MAGIC.C program.

    We initialize the two-dimensional array in MAGIC.C by filling it row by
    row. When, for clarity, you want to specify where one row ends and the
    next begins, you can enclose each row's initializers in another set of

    static int square[3][3] = {
        {5, 8, 3},─────────────────────────────────────────────────────Row 0
        {2, 4, 0},─────────────────────────────────────────────────────Row 1
        {7, 1, 6}──────────────────────────────────────────────────────Row 2

    This amounts to specifying each row as its own subset of initializers,
    clearly a more readable arrangement.

    To underinitialize a 3-by-3 array, we could use the following:

    static int square[3][3] = {
        5, 8, 3, 2, 4, 7, 1, 6 };
                                └─────────────────────── One initializer short

    Here the last column of the last row is omitted and thus is 0 by default.
    We also could underinitialize by a selected row, as follows:

    static int square[3][3] = {
        {5, 8, 3},
        {2, 4},──────────────────────────────────────────────────Row 1 short
        {7, 1, 6}

    Here we omit the third column of the second row, thus setting the value to

    Two-dimensional Arrays and Functions

    As with one-dimensional arrays, you pass a two-dimensional array to a
    function by merely stating its name:


    Again, this passes the two-dimensional array itself, not a copy.

    For the receiving function, Make_move(), you must always declare the size
    of the columns. The number of rows──as with one-dimensional arrays that
    have only one row──is optional, as shown in the following legal example:

    int field[][3];

    The TTT.C program (Listing 7-10 on the next page) is a somewhat
    unsophisticated tic-tac-toe game that will help you understand how to use
    two-dimensional arrays. It's an easy game to win. Forcing a tie, however,
    is difficult. For clarity, the declaration for field in Make_move()
    includes the number of rows.

    /* ttt.c    --  a tic-tac-toe game demonstrates  */
    /*              passing two-dimensional arrays   */
    /*              to functions                     */

        static char board[3][3] = {
            { '-', '-', '-' },
            { '-', '-', '-' },
            { '-', '-', '-' },
        int row, col, ch;
        extern char Check_winner();
        extern void Make_move(), Draw_field();

        printf("You are X and make the first move.\n");
        while (1)
            printf("Specify coordinate for your X.\n");
            printf("(For example, a2, or Q to quit)\n");

            /* Print the square. */

            /* Enter the user's coordinates. */
            if ((row = getch()) == 'Q')
            row -= 'a';
            col = getch() - '1';

            /* Check for a legal move. */
            if (row < 0 || row > 2 || col < 0 || col > 2)
                printf("Bad Square Specification\n\n");
            if (board[row][col] != '-')
                printf("Sorry, Square Occupied\n\n");

            /* Make the move. */
            board[row][col] = 'X';
            if ((ch = Check_winner(board)) != '-' || ch == 't')
            if ((ch = Check_winner(board)) != '-' || ch == 't')
        if (ch == 't')
            printf("It's a tie!\n");
        else if (ch == 'X')
            printf("You win!\n");
            printf("I win!\n");

    char Check_winner(char field[][3])
        int row, col;

        for (row = col = 0; row < 3; ++row, ++col)
            if (field[row][0] != '-'             /* horizontal */
                    && field[row][0] == field[row][1]
                    && field[row][1] == field[row][2])
            if (field[0][col] != '-'             /* vertical */
                    && field[0][col] == field[1][col]
                    && field[1][col] == field[2][col])
        if (field[0][0] != '-'         /* right diagonal */
                && field[0][0] == field[1][1]
                && field[1][1] == field[2][2])
        if (field[0][2] != '-'         /* left diagonal */
                && field[0][2] == field[1][1]
                && field[1][1] == field[2][0])

        for (row = 0; row < 3; ++row)        /* any moves left */
            for (col = 0; col < 3; ++col)
                if (field[row][col] == '-')
        return ('t');

    void Make_move(char field[3][3])
        int row, col;

        for (row = 2; row >= 0; --row)
            for (col = 2; col >= 0; --col)
                if (field[row][col] == '-')
                    field[row][col] = 'O';

    void Draw_field(char field[][3])
        int row, col;

        printf("\n   1  2  3\n\n");
        for (row = 0; row < 3; ++row)
            printf("%c ", 'a' + row);
            for (col = 0; col < 3; ++col)
                printf(" %c ", field[row][col]);

    Listing 7-10.  The TTT.C program.

Arrays of Three and More Dimensions

    In C you can give an array an unlimited number of dimensions, but remember
    that the more dimensions an array has, the more unmanageable it becomes.
    You have already seen how two-dimensional arrays are declared,
    initialized, and passed to functions. The rules for using more dimensions
    are a simple extension of those same concepts.

    Declaring Multidimensional Arrays

    The general rule for declaring multidimensional arrays is as follows:

    type name[exp][exp][exp][exp] ...
                │    │    │    │    └─────────────────────────────────── Etc.
                │    │    │    └──────────────────────────── Fourth dimension
                │    │    └────────────────────────────────── Third dimension
                │    └────────────────────────────────────── Second dimension
                └──────────────────────────────────────────── First dimension

    First, specify the type that will be stored in each array item, and then
    name the entire array. Each exp is an integer constant expression that
    specifies the number of elements in that dimension. Each succeeding
    square-bracketed [exp] defines another dimension.

    Think of a three-dimensional array as a cube. Figure 7-6 shows a
    conceptualization of a cube that corresponds to the following declaration:

    #define DEPTH  3
    #define ROWS   3
    #define COLS   3

    int cube[DEPTH][ROWS][COLS];
                │   └─────┬────┘
                │         └─────────────── Size of each two-dimensional array
                └─────────────────── Number of two-dimensional arrays (depth)

    You can also think of this three-dimensional array as an array of three
    two-dimensional arrays.

    │ Figure 7-6 can be found on p.213 of the printed version of the book.   │

    Figure 7-6. Three-dimensional arrays can be thought of as a cube.

    Initializing Multidimensional Arrays

    When you use a list of values to initialize a multidimensional static or
    global array, the compiler reads that list from left to right, filling the
    array row by row for each plane of the depth. The following declaration
    places the initializing values into cube, beginning with the value 1, as
    shown in Figure 7-7.

    int cube[3][3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9,
        10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
        22, 23, 24, 25, 26, 27};

    To specify the order for initializing, you can enclose any group with
    braces. Those braces correspond to the depth first, then to the rows and
    columns. You can therefore rewrite the above declaration more clearly as

    int cube[3][3][3] =
        { {1,  2,  3},  {4,  5,  6},  {7,  8,  9} },
        { {10, 11, 12}, {13, 14, 15}, {16, 17, 18} },
        { {19, 20, 21}, {22, 23, 24}, {25, 26, 27} }

    Here each inner set of braces encloses a given row's list of initializers.
    Use this technique when you need to underinitialize a given row, column,
    or depth. Clearly, as you progress beyond three dimensions, initializing
    can become very confusing. Just remember the general rules, or, in
    despair, simplify your algorithm.

            │ │  19   │  20   │  21   │ \
            │\├───────┼───────┼───────┤   \
            │ │  22   │  23   │  24   │     \
            │\├───────┼───────┼───────┤       \
            ┌─\ │  25   │  26   │  27   │         \
            │  \└───────┴───────┴───────┘           \
            │     \           ┌───────────────────────\
            │       \         │\┌───────┬───────┬───────\
            │         \       │ │  10   │  11   │  12   │ \
            │           \     │\├───────┼───────┼───────┤   \
            │             \   │ │  13   │  14   │  15   │     \
            │               \ │\├───────┼───────┼───────┤       \
            │                 │ │  16   │  17   │  18   │         \
    Depth──┤                  \└───────┴───────┴───────┘           \
            │                     \          ┌────────────────────────\
            │                       \        │\┌───────┬───────┬───────┐──┐
            │                         \      │ │   1   │   2   │   3   │  │
            │                           \    │\├───────┼───────┼───────┤  │
            │                             \  │ │   4   │   5   │   6   │  ├──Row
            │                               \│\├───────┼───────┼───────┤  │
            │                                \ │   7   │   8   │   9   │  │
                                            │                       │

    Figure 7-7. Initializing a three-dimensional array.

    Using Multidimensional Arrays in Functions

    To pass a multidimensional array to a function, you need specify only the
    name of that array as an argument:


    On the receiving end──in the function Draw_planes()──you must specify the
    sizes of all but the leftmost dimension. That size is optional, as in the

    int box[][3][3];

    The BOX.C program (Listing 7-11) shows the initialization of a
    three-dimensional array and then prints out the result.

    /* box.c  --  demonstrates the result of initializing  */
    /*            a three-dimensional array                */

        static int cube[3][3][3] = {
            1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
            13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
            23, 24, 25, 26, 27 };
        int plane;
        extern void Draw_plane();

        for (plane = 0; plane < 3; ++plane)
            Draw_plane(cube, plane);

    void Draw_plane(int box[3][3][3], int slice)
        int row, col;

        printf("Plane[%d] =\n", slice);
        for (row = 0; row < 3; ++row)
            for (col = 0; col < 3; ++col)
                printf( "%2d ", box[slice][row][col]);

    Listing 7-11.  The BOX.C program.

Advanced Topics and Tricks

    In this section we discuss three advanced techniques that can be very

    ■  Negative subscripting

    ■  Large and huge arrays

    ■  Passing pieces of arrays

Negative Subscripting

    Recall that unless you include code to perform bounds checking, C lets you
    reference items both beyond the end and before the beginning of an array.
    You have already seen the consequences of referencing beyond an array's
    end. Referencing before its beginning is something new, as Figure 7-8

    If you declare three consecutive arrays such as the following:

    int first[3], second[3], third[3];

    and then reference with a negative subscript:


    you actually reference either third[2] or first[2], depending on whether
    the arrays are auto or static, as shown in Figure 7-8. QuickC places auto
    arrays into memory from right to left (top down) and static arrays from
    left to right (bottom up).

                    second [-1]
    │   7   │   8   │   9   │   4   │   5   │   6   │   1   │   2   │   3   │
    │                       │                       │                       │
                │                       │                       │
            third [3]              second [3]              first [3]

    (A)                         ARRAY DECLARED auto

                    second [-1]
    │   1   │   2   │   3   │   4   │   5   │   6   │   7   │   8   │   9   │
    │                       │                       │                       │
                │                       │                       │
            first [3]               second [3]             third [3]

    (B)                          ARRAY DECLARED static

    Figure 7-8. Effect of negative subscripting on auto and static arrays.

    The L2WORDS.C program (Listing 9-11 on pp. 282─83) illustrates this

Large and Huge Arrays

    On the IBM PC an integer is 2 bytes long, so you have to be careful when
    declaring arrays larger than 32,767 elements. That is because 32,767 is
    the highest positive value a 2-byte integer can hold. If you successively
    reference the elements in an array with the following loop:

    int i;
    for (i = 0; i < 40000; i++)
        printf("%dn", array[i]);

    the 0th through 32,767th elements print out correctly, but the 32,768th
    element prints out as array[-32768].

    Integers are signed variables, so they wrap to negative numbers when they
    exceed their highest positive value. Therefore, to reference elements in
    large arrays, use either unsigned int or long offsets.

    Another problem occurs when arrays grow to more than 65,536 bytes total on
    the IBM PC. In this case, use the huge keyword in the array declaration,
    as follows:

    int huge bigbox[100][100][100];

    Here the keyword huge is required because the total size of the array
    bigbox is 100 x 100 x 100 times 2 (two bytes per int), or 2,000,000 bytes
    total. This tells the compiler to set aside more space for this array than
    the space reserved for ordinary variables. Whenever you use large arrays
    that require the huge keyword, compile with the large memory model. That
    model will be discussed in greater detail in Chapter 12. (Of course, you
    will need lots of memory in your computer, too.)

Passing Pieces of Arrays

    When you reference array elements with fewer dimensional offsets than were
    present in that array's declaration, you are actually referencing the
    address of a subarray. If, for example, you declare:

    int square[3][3];

    and then later reference that array without specifying the second


    you would pass the address of cube's second row (a one-dimensional
    subarray) to Print_row(). Then declare Print_row() to receive a
    one-dimensional array:

    int row[];

The Bitwise Operators, Tiny Arrays

    Just as arrays can get larger and larger and more and more complex, it is
    also possible to go the other direction and store data in the individual
    bits of a single byte. You can manipulate individual bits of a byte using
    the bitwise operators. Those operators are:

    Operator           Description
    &                  The bitwise AND operator
    |                  The bitwise OR operator
    ^                  The bitwise exclusive-OR operator
    ~                  The unary inversion operator (ones-complement)
    >>                 The unary right-shift operator
    <<                 The unary left-shift operator

    Each of these affects the individual bits in the bytes of a value, which
    can be either a constant or a variable. Remember, a char uses 8 bits, an
    int 16 bits, and a long 32 bits. First, we demonstrate the application of
    the bitwise operators, and then discuss the logic of each.

    The BITWISE.C program (Listing 7-12) lets you enter values interactively
    and then apply the bitwise operators to them. By running this program, you
    will better understand the discussion that follows. (Note that a set bit
    is represented with a 1 and a clear bit is represented with a 0.)

    /* bitwise.c -- demonstrates the bitwise operators */

    #include <stdio.h>

        unsigned int val1, val2, result;
        int ch;
        extern void show();

            printf("\nval1: ");
            if (scanf("%d", &val1) != 1)

            printf("val2: ");
            if (scanf("%d", &val2) != 1)
            printf("\tval1   = ");
            printf("\tval2   = ");

            printf("Bitwise Operator: ");
            while ((ch = getchar()) == '\n')
            if (ch == EOF)
            switch (ch)
                case '&':
                    result = val1 & val2;
                    printf("Executing: result = val1 & val2;\n");
                case '|':
                    result = val1 |= val2;
                    printf("Executing: result = val1 | val2;\n");
                case '^':
                    result = val1 ^= val2;
                    printf("Executing: result = val1 ^ val2;\n");
                case '~':
                    result = ~val1;
                    printf("Executing: result = ~val1;\n");
                    printf("\tresult = ");
                    result = ~val2;
                    printf("Executing: result = ~val2;\n");
                case '<':
                    result = val1 <<= val2;
                    printf("Executing: result = val1 <<val2;\n");
                case '>':
                    result = val1 >>= val2;
                    printf("Executing: result = val1 >>val2;\n");
                case 'q':
                case 'Q':
            printf("\tresult = ");
    void bitout(unsigned char num[])
        int bytes = 2, i, j;

        /* IBM PC stores ints low/hi. */
        for (i = bytes-1; i >= 0; --i)
            for (j = 7; j >= 0; --j)

    void show(unsigned int val)
        extern void bitout();

        printf("(%05u decimal)", val);
        printf(" binary\n");

    Listing 7-12.  The BITWISE.C program.

The Binary Bitwise Operators

    The bitwise AND, the bitwise OR, and the bitwise exclusive-OR are binary
    operators. That is, like the addition operator, they operate on two
    values──not one. You can invoke them as follows:

    result = val1 & val2;     /* bitwise AND          */
    result = val1 | val2;     /* bitwise OR           */
    result = val1 ^ val2;     /* bitwise exclusive-OR */

    Or you can invoke them with the op= form:

    result &= val1;    /* bitwise AND          */
    result |= val1;    /* bitwise OR           */
    result ^= val1;    /* bitwise exclusive-OR */

    The Bitwise AND Operator

    The bitwise AND operator, &, compares the bits in two values and produces
    a value based on the comparison of the same bits in each:

    var1           &              var2          yields         result
    1                             1                            1
    0                             1                            0
    1                             0                            0
    0                             0                            0

    For the bitwise AND, the result bit is set only if the same bit in both
    values is set. Otherwise, the result bit is cleared.

    The bitwise AND operator is useful for turning off (clearing) a selected
    bit in a variable. A typical application for the & operator is to turn off
    a blinking cursor when you are accessing screen memory directly:

    var1 =  3;
    var1 &= 0xFFFE;─────────────────────────────────────────Turn off low bit

    This results in the following calculation:

                    0000000000000011 ───────────────────────────────────── var1
                & 1111111111111110 ─────────────────────────────────── OxFFFE
    Yields ───── 0000000000000010
                                └─────────────────────────── Low bit cleared

    The Bitwise OR Operator

    The bitwise OR operator, |, compares the bits in two values and sets the
    result bit for any bit that is set in either of the values:

    var1           |              var2          yields         result
    1                             1                            1
    0                             1                            1
    1                             0                            1
    0                             0                            0

    For the bitwise OR, the result bit is set if either or both corresponding
    bits in both values are set. Otherwise, the result bit is cleared.

    The bitwise OR operator is useful for turning on (setting) a selected bit
    in a variable. A typical application for the | operator is to turn on the
    high bit of a character variable before sending that character to the

    var1 =  0;
    var1 |= 1;───────────────────────────────────────────────Turn on low bit

    This results in the following calculation:

                    0000000000000000 ───────────────────────────────────── var1
                | 0000000000000001 ────────────────── Equivalent to 1 decimal
    Yields ───── 0000000000000001
                                └─────────────────────────────── Low bit set

    The Bitwise exclusive-OR Operator

    The bitwise exclusive-OR operator, ^, compares the bits in two values and
    produces a set bit only if one bit or the other is set, but not both:

    var1           ^              var2          yields         result
    1                             1                            0
    0                             1                            1
    1                             0                            1
    0                             0                            0

    For the bitwise exclusive-OR, ^, the result bit is set if one or the other
    of the corresponding bits in the values is set, but not both.

    The bitwise exclusive-OR operator is useful for toggling (setting,
    clearing, setting, etc.) a selected bit in a variable. A typical
    application for the ^ operator is to toggle a flag in a game, thereby
    determining which of two players is to make the next move:

    var1 =  0;
    var1 ^= 1;
    var1 ^= 1;

    This results in the following calculations:

                    0000000000000000 ───────────────────────────────────── var1
                ^ 0000000000000001 ────────────────── Equivalent to 1 decimal
    Yields ───── 0000000000000001
                            ▓      │
                            ▓      └──────────────────── Low bit set (Player 2)

                    0000000000000001 ──────────────────────── New value of var1
                ^ 0000000000000001 ────────────────── Equivalent to 1 decimal
    Yields ───── 0000000000000000
                                └─────── Low bit toggled to clear (Player 1)

The Unary Bitwise Operators

    The unary bitwise operators affect the bits of a single value. These
    operators are:

    Operator       Description
    ~              The unary ones-complement operator
    >>             The unary right-shift operator
    <<             The unary left-shift operator

    The Unary Ones-Complement Operator

    The ones- complement of a variable is derived by inverting all the bits in
    that value. If a bit is set, that bit changes to clear, and vice versa. In
    C, the ~ causes the bits in a value to be inverted, as follows:

    var1    0000000000000111

    ~var1   1111111111111000──────────────────────────────────────────Result

    Two applications are common for the ones-complement operator. One is to
    set selected bits in a variable regardless of the number of bytes occupied
    by that variable. Suppose you have an int and you want all but the zeroth
    bit set. One way to do this is by:

    number = 0xFFFE;

    This does the job, but pays the price of assuming an int always occupies
    two bytes of storage. Although that is true on the IBM PC, it is not the
    case on most 32-bit machines. The correct way to set all but the zeroth
    bit──and the portable way──is to use the ones-complement operator:

    number = ~1;

    The second common application for the ones-complement operator is turning
    off selected bits. One way to turn off the zeroth bit, while leaving all
    the other bits in a variable unchanged, is:

    masks &= 0xFFFE;

    But again, the ones-complement operator should be used for portability:

    masks &= ~1;

    The Unary Shift Operators

    The shift operators move all the bits in a variable right or left by the
    number of bit positions specified. The shift operators are used as

    result = value <<bits;
    result = value >>bits;

    Here the first line shifts the value in value left──from the low toward
    the high bit──by the number of bit positions specified by bits. The second
    line shifts value in the opposite direction──right, or from the high
    toward the low bit. For example:

    val1       0000000000111000
    val1 <<3   0000000111000000───────────────────────────────────Left shift
    val1 >>3   0000000000000111──────────────────────────────────Right shift

    When shifting left, the bits on the right are filled with clear bits. With
    QuickC, the fill bits for a right shift are always set. For portability,
    however, always use unsigned variables when right-shifting.

    The shift operators are useful for aligning a bit prior to ORing it into a
    variable. Shifting also provides a quick way to multiply or divide by 2.
    Each bit you shift to the left multiplies a number by 2; each bit you
    shift to the right divides it by 2.

    int val = 1;

    val <<= 1;──────────────────────────────────────────────val now equals 2
    val <<= 1;──────────────────────────────────────────────val now equals 4
    val <<= 1;──────────────────────────────────────────────val now equals 8

Summary of Bitwise Operators

    If you haven't already done so, enter, compile, and run the BITWISE.C
    program (Listing 7-12 on p. 218). Watching the actions of bits as the
    program applies each bitwise operator will give you a feel for bits and
    will lead you to develop sophisticated applications of your own. You will
    find the bitwise operators used a great deal in the hardware-specific
    chapters at the end of this book.

Chapter 8  Addresses and Pointers

    One of the chief strengths of C is its ability to manipulate individual
    areas of memory with almost the same precision that assembly language
    provides. This chapter discusses this ability in detail by showing you a
    new kind of variable called a "pointer"──a variable whose contents
    identify a memory address. Using pointers can greatly increase the speed
    at which your programs execute, lets you access your computer's hardware
    directly, and allows you to write subroutines that manipulate variables
    directly (via the address).

Addresses Reviewed

    The concept of memory addresses is vital to C programming. Recall, for
    example, that all arguments passed to scanf() must be preceded by an
    ampersand (&). In the following expression:

    scanf("%d", &num);

    the & tells scanf() to read an integer from the keyboard and to place that
    input value into the variable num, whose address is passed with the
    expression &num.

    You also used addresses with arrays. In the previous chapter we mentioned
    that when an entire array is passed to a function, it is passed as
    addresses. For example, the code fragment

    char choices[4] = {'Q', 'E', 'S', 'L'};


    passes the address of the choices array to the function Get_move(), rather
    than the individual elements of that array. When you use an array name
    without specifying an element in square brackets, the compiler uses the
    internal starting memory address of that array as its value.

    One of C's strengths is the ease with which it lets you manipulate the
    values of variables by way of their addresses. This type of address
    manipulation, known as indirection, is accomplished with pointers.

What Is a Pointer?

    A pointer, in its simplest form, is a variable whose value (contents) is
    an address, or a number corresponding to a specific location in memory.
    That is, if address_var is a pointer-type variable, and num is an integer
    variable, the expression

    address_var = &num;

    causes the address of the variable num to be placed into the pointer
    variable named address_var. This assignment ignores the actual value of

    You can use pointers in your programs to:

    ■  Save information from functions that return addresses

    ■  Indirectly return more than one value from a function

    ■  Speed up execution by manipulating pointers rather than large blocks of

    ■  Access and modify text screen memory

    ■  Call functions using their addresses, thus creating more flexible code

    ■  Access and manipulate strings

    Before you can use a pointer, also called a "pointer to," you must declare
    it. Declaring a pointer is much like declaring an ordinary variable, the
    only difference being that you must always precede the pointer's name with
    the * character.

    The following example declares two variables: an integer called num and a
    pointer called address_var.

    int num, *address_var;

    The * before address_var tells the compiler that address_var is a pointer
    whose contents will be an address. Because address_var is declared as type
    int, the compiler knows that address_var will contain the address of an
    integer variable. Figure 8-1 illustrates this process.

    In this example, we declare two variables and two pointers (Figure 8-1a).
    The variables are num (an int) and fval (a float). We also declare two
    pointers, address_var and faddress_var.

    The pointer address_var contains the address of an int type of variable;
    pointer faddress_var contains the address of a float type of variable. The
    two assignment statements in Figure 8-1b store the addresses in the
    appropriate pointers. The result of the assignment is that address_var now
    holds num's address (and thus points to num), and faddress_var holds
    fval's address (and thus points to fval).

        int num/ *address_var;   ───┐
                                    │▒▒▒▒▒▒▒▒▒▒ These declarations
        float fval, *faddress-var;──┘         ▒ create...

    Memory──  4096   4097  4098  4099  4050  4051  4052  4053  4054  4055
    locations ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
    in bytes  │           │           │           │           │           │
                    │           │     │                       │     │
                    │           │     └───────────┬───────────┘     │
                    │           │                 │                 │
                    num     address-var          fval          faddress-vars


            address_var = &num; ───┐
                                │▒▒▒▒▒▒▒▒▒▒ These assignments
            faddress-var = &fval; ─┘         ▒ yield...

                ┌────►&num──────┐        ┌──────────►&fval──────────┐
                │               │        │                          │
    Memory──  4096   4097  4098 │4099  4050  4051  4052  4053  4054 │4055
    locations ┌─────┬─────┬─────▼─────┬─────┬─────┬─────┬─────┬─────▼─────┐
    in bytes  │           │   4096    │           │           │   4050    │
                    │           │     │                       │     │
                    │           │     └───────────┬───────────┘     │
                    │           │                 │                 │
                    num     address-var          fval          faddress-var
                                ▒                                  ▒
                    ▒           ▒                 ▒                 ▒
                    ▒▒▒▒▒▒▒▒▒▒▒▒▒                 ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
                    Points to                         Points to


    Figure 8-1. Result of assigning an address to a pointer.

Accessing Variables with Pointers

    The * operator (pronounced "star"), when used to signify a pointer, is
    called the indirection operator because it lets you access variables
    indirectly. When you use the * operator in front of a pointer (other than
    in its declaration) you tell the compiler to fetch or store the value that
    the pointer points to. For example, in the fragment

    int num, *address_var;

    address_var = &num;
    *address_var = 3;

    you first declare an int variable (num) and a pointer to an int
    (address_var). Next, the value you store in address_var is the address of
    the variable num. Finally, the * in front of address_var tells the
    compiler to store the value 3 in the variable whose address is stored in
    address_var. Because address_var contains the address of num, that value
    is stored in num. (See Figure 8-2 on p. 234.)

    The POINTER.C program (Listing 8-1) illustrates the procedure for
    declaring, assigning a value to, and using pointers.

    /* pointer.c  --  demonstrates pointer declaration,   */
    /*                assignment, and use                 */

    #define WAIT printf("(press any key)"); getch(); \

        int num, *address_var;

        num = 0;
        address_var = &num;

        printf("The address of the variable ");
        printf("\"num\" is:  0x%04X\n", &num);
        printf("The value in the pointer ");
        printf("\"address_var\" is:  0x%04X\n", address_var);
        printf("The value in the variable ");
        printf("\"num\" is: %d\n", num);
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);
        printf("To verify this, let's store 3 in\n");
        printf("\"*address_var\", then print out ");
        printf("\"num\" and \"*address_var\"\n");
        printf("Doing: *address_var = 3;\n\n");
        *address_var = 3;

        printf("The address of the variable ");
        printf("\"num\" is:  0x%04X\n", &num);
        printf("The value in the pointer ");
        printf("\"address_var\" is:  0x%04X\n", address_var);
        printf("The value in the variable ");
        printf("\"num\" is: %d\n", num);
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);

        printf("Now we will add 15 to \"num\" and print\n");
        printf("\"num\" and \"*address_var\" again.\n");

        printf("Doing: num += 15;\n\n");
        num += 15;

        printf("The address of the variable ");
        printf("\"num\" is:  0x%04X\n", &num);
        printf("The value in the pointer ");
        printf("\"address_var\" is:  0x%04X\n", address_var);
        printf("The value in the variable ");
        printf("\"num\" is: %d\n", num);
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);

        printf("Doing: return (*address_var);\n\n");
        return (*address_var);

    Listing 8-1.  The POINTER.C program.

    The output of this program follows. Compare it to the listing.

    The address of the variable "num" is:  0x1388
    The value in the pointer "address_var" is:  0x1388
    The value in the variable "num" is: 0
    (press any key)

    Since "address_var" points to "num"
    the value in "*address_var" is: 0
    (press any key)
    To verify this, let's store 3 in
    "*address_var", then print out "num" and "*address_var"
    (press any key)

    Doing: *address_var = 3;

    The address of the variable "num" is: 0x1388
    The value in the pointer "address_var" is: 0x1388
    The value in the variable "num" is: 3
    (press any key)

    Since "address_var" points to "num"
    the value in "*address_var" is: 3
    (press any key)

    Now we will add 15 to "num" and print
    "num" and "*address_var" again.
    (press any key)

    Doing: num += 15;

    The address of the variable "num" is:  0x1388
    The value in the pointer "address_var" is: 0x1388
    The value in the variable "num" is: 18
    (press any key)

    Since "address_var" points to "num"
    the value in "*address_var" is: 18
    (press any key)

    Doing: return (*address_var);

    In the POINTER.C program, the pointer address_var contains the address of
    num (as a result of the assignment address_var = &num) and therefore
    yields the value stored in num. That is, we indirectly access num via its
    address (*address_var = 3). Because address_var contains num's address,
    you can use *address_var anywhere you would use num. For example, we could
    have ended the program with return (num) to produce the same result.

Passing Pointers to Functions

    Until now, with the exception of arrays, we have passed arguments to
    functions by value. Thus, you might think we could write a function that
    squares the argument passed to it as follows:

    square(int num)
        num *= num;

    This doesn't work, however, because the variable num is a local variable
    to the function square(), and the result is not accessible by other
    functions. Thus, calling square() with

        int val = 5;


    does not result in main()'s variable val being squared, because main()
    doesn't "see" the variable num.

    You can get around this by having square() return a value, as follows:

        int val = 5;

        val = square(val);────────────────────────Value returned by square()

    square(int num)
        num *= num;
        return (num);─────────────────────────────────square() returns value

    Another approach is to use pointers. When you pass a pointer to a
    function, you still pass a copy of its value, but the value you pass is an
    address. Therefore, in square(), you must declare num as a pointer because
    it will receive an address:

    square(int *address_var) ─────────────────── Pointer receives an address

        *address_var *= *address_var;
    }                └────────────────────────────── Multiplication operator

    This form of square() receives an address as its argument. The pointer to
    hold that address, address_var, is declared as int *address_var because it
    receives the address of an int variable.

    To use this new square() function, we must pass it an address. We can do
    this in either of two ways. We can use the & operator, as follows:

        int val = 5;

        square(&val);────────────────────────────────────────Pass an address

    Or we can pass a pointer:

        int val = 5, *here;

        here = &val;
        square(here);─────────────────────The value of here is val's address

    After making our declarations, we place the address of val into the
    pointer here. When we pass here to square(), its value──the address of
    val──is what is actually passed. This results in val being squared.

    The SQUARE.C program (Listing 8-2) summarizes this passing of pointers
    and addresses in an interactive quiz. In it, we've expanded on our
    original square() subroutine. In the new Square(), we return two values
    from a single function! The first, returned by return, is an error
    status──zero for a successful square and -1 for any attempt to square a
    number larger than 181 or less than -181 (the square root of 32,767, the
    largest signed int on the IBM PC). We return the second value with the
    pointer *where.

    /* square.c  --  a quiz to demonstrate passing        */
    /*               pointers and addresses in functions  */

        int val, count, guess;

        for (count = 1; count < 255; ++count)
            val = count;
            printf("What is the square of %d?\n", val);
            if (scanf("%d", &guess) != 1)
                return(0);        /* non-number exits   */

            if(Square(&val) != 0)    /* pass val's address */
                printf("Range Error\n");
            if (val != guess)
                printf("Wrong. It is %d.\n", val);
            printf("Continue? ");
            if (getche() != 'y')
    int Square(int *where)
        if (*where > 181 || *where < -181)
            return (-1);
        *where = (*where) * (*where);
        return (0);

    Listing 8-2.  The SQUARE.C program.

    In this program, we use a separate variable, count, in the for loop
    because the value of val is indirectly changed by the call to Square(). If
    we had used val as follows:

    for (val = 1; val < 255; ++val)

    you would be prompted only for the numbers 1, 2, 5, and 26, and then you
    would receive a Range Error.

Pointers and Arrays

    Pointers let you manipulate strings and arrays more succinctly and
    efficiently. We'll learn about strings in the next chapter. Here we will
    discuss the relationship between arrays and pointers, detailing potential
    pitfalls along the way.

    Recall from the previous chapter that referencing an array by name,
    without an offset, yields that array's address. What we didn't tell you
    was that the address of an array is the same as the address of the array's
    first element. For example, in the following array declaration:

    int coins[4] = {25, 10, 5, 1};

    the reference

    Find_change(coins, amount);

    actually causes the address of the array coins[4] to be passed to the
    function Find_change(). Because the address of an array is the location in
    memory of its beginning, we can also reference that array with the


    Here the address operator & yields the address of the first item in the
    array coins and, therefore, the address of the array itself.

    You can assign the address of another variable to a pointer with the &
    operator (address_var = &num[1]). Because each item in an array is a
    variable, the assignment

    address_var = &coins[0];

    results in address_var containing the address of the first integer in the
    array coins.

    Because &coins[0] and coins are equivalent, the following expression is
    the same as the one above:

    address_var = coins;

    Now here comes the exciting part. When a value, say 1, is added to a
    pointer, it increments the address in that pointer by the number of bytes
    in the type to which it points. For example, in Figure 8-2 the variable
    address_var begins with a value that is the address of coins. Notice what
    happens when we add 1 to address_var. Because address_var is a pointer to
    the type int, and because an int occupies two bytes (on the IBM PC), the
    value in address_var is increased by 2. The new value in address_var is
    thus the address of coins[1] (the next element in the array).

                                Points to
                                ▒        ▒
                        ┌─────────────┐ ▒
                        │             │ ▼
    Memory locations──  4392   4393  4394  4395  4396  4397  4398  4399
    in bytes           ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
                        │   4394    │           │           │           │
                            │           │           │           │
                            │           │           │           │
                            │           │           │           │
                        address-var   coins[0]    coins[1]    coins[2]
                            •                       ▒           ▒
                            •                       ▒           ▒
                        4392   4393                  ▒           ▒
                        ┌─────┬─────┐                 ▒           ▒
                        │   4394    │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒           ▒
                        └─────┴─────┘   Next points to            ▒
                            │                                   ▒
                            │                                   ▒
                            │                                   ▒
    Increment value───address_var += 1;                          ▒
    of pointer               •                                   ▒
                            •                                   ▒
                            •                                   ▒
                        4392   4393                              ▒
                        ┌─────┬─────┐                             ▒
                        │   4398    │▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
                        └─────┴─────┘    Then points to
    Increment pointer───address_var += 1;

    Figure 8-2. The value of a pointer increases by multiples of the number of
    bytes in the data type to which it points.

    The CHANGE.C program (Listing 8-3) demonstrates how the pointer coin_ptr
    advances through the array coins, each step determined by the number of
    bytes for the type int. Compile the program with the Debug option set
    because we want to trace its execution.

    /* change.c  -- a change-making program demonstrates   */
    /*              how pointers advance the correct       */
    /*              number of bytes based on type          */

    #define NCOINS (4)
    #define CENT (0x9b)  /* IBM PC cent character */
    #define WAIT printf("(Press any key to continue)"); \
                getch(); printf("\n\n")

        static int coins[NCOINS] = {25, 10, 5, 1};
        int *coin_ptr, i = 0;
        int pennies1, pennies2, count;
        float amount;

        printf("Enter an amount and I will ");
        printf("give you change.\nAmount: ");
        if (scanf("%f", &amount) != 1)
            printf("I don't know how to change that!\n");
        pennies2 = pennies1 = (int)(amount * 100.0);

        coin_ptr = coins;
        for (i = 0; i < NCOINS; ++i)
            count = 0;
            while ((pennies1 -= coins[i]) >= -1)
            if (count > 0)
                printf("%4d %2d%c", count, coins[i], CENT);
                printf(" coins by array offset.\n");
            if (pennies1 == 0)
            pennies1 += coins[i];

            count = 0;
            while ((pennies2 -= *coin_ptr) >= 0)
            if (count > -1)
                printf("%4d %2d%c", count, *coin_ptr, CENT);
                printf(" coins by pointer indirection.\n");
            if (pennies2 == 0)
            pennies2 += *coin_ptr;

    Listing 8-3.  The CHANGE.C program.

    After you compile CHANGE.C, turn off screen swapping and specify the
    following four watch variables:


    (See pp. 119-20 if you forgot how to specify watch variables. You don't
    need to specify the types with a comma; the defaults are correct.) Now
    step through the program with the F8 function key. Observe that as ++i
    followed by coins[i] steps through the array, so does ++coin_ptr followed
    by *coin_ptr. Figure 8-3 shows the screen as the program is being traced.

    │ Figure 8-3 can be found on p.236 of the printed version of the book.   │

    Figure 8-3. Incrementing a pointer moves it through an array in steps
    that correspond to the number of bytes in the data type.

    This equivalence between arrays and incrementing pointers is one of C's
    chief strengths. It can also be confusing and can lead to some unexpected
    bugs. In the CHANGE.C program, we perform bounds checking with the for
    loop. If we rewrite that loop without i, we need to do one of two things.
    One option is to put some stop value into our array, such as the last
    element (0) in the following:

    int coins[5] = {25, 10, 5, 1, 0};

    This approach is often used with string variables.

    The other option requires that we add some means of detecting when the
    address in coin_ptr becomes too large, as follows:

    for (coin_ptr = coins; coin_ptr < &coins[4]; ++coin_ptr)

    This approach is more common for situations where a stop value is not
    practical. In a database, for example, you might have 32 bytes available
    and want to use all 32 for a mailing address, with none reserved for a
    terminating value.

Pointer Arithmetic

    QuickC permits fewer arithmetic operations on pointers than on other kinds
    of variables. Because pointers contain addresses as their values, whenever
    you change one, you reference a new location inside your computer's
    memory. Obviously, you don't want to reference random locations──not only
    would they be meaningless, but they might overwrite crucial memory
    locations and crash your PC.

    To help avoid such meaningless addresses, C permits only a handful of
    mathematical operations to be performed on pointers. They are:

    Addition  You can add values to addresses (like incrementing with ++).
    This is most useful with arrays.

    Subtraction  You can subtract values from addresses (decrementing with --
    and subtracting with -).

    Comparison  You can compare one address to another to see if it is greater
    than, less than, equal to, or not equal to the other.

    Quick Tip
    C does not provide many safeguards against referencing incorrect
    addresses. QuickC, however, lets you compile with Pointer Check turned on.
    Although this provides a measure of safety (by verifying that pointer
    values address program data), it results in slower-executing programs.

    The operations allowed on pointers are a small set when compared to the
    operations allowed on numeric variables and array items. Let's examine why
    you cannot use the other arithmetic operations:

    Multiplication  Doubling an address or even multiplying it by, let's say,
    523 would yield a new address value that, at best, would be somewhere in
    your data and at worst would be beyond the end of your program, possibly
    in the code of another memory resident program (like QuickC or

    Division  Halving an address or even dividing it by, let's say, 10 would
    yield a new address value that, at best, would be somewhere inside your
    own code and at worst would be inside the MS-DOS interrupt vectors.

    Bitwise operators  You cannot manipulate the bits in an address. This
    would result in a totally random address.

    Unary negation  You can't reverse the sign of an address because addresses
    are always unsigned.

    Now let's look at the CHANGE2.C program (Listing 8-4). This rewrite of
    CHANGE.C illustrates the incrementing of pointers and the comparison of
    two pointers.

    /* change2.c -- modified to demonstrate passing       */
    /*              an address to a function              */

    #define NCOINS (4)
    #define CENT (0x9B)  /* IBM PC cent character */

        static int coins[NCOINS] = {25, 10, 5, 1};
        int pennies;
        float amount;

        printf("Enter an amount and I will ");
        printf(" give you change.\nAmount: ");
        if (scanf("%f", &amount) != 1)
            printf("I don't know how to change that!\n");
        pennies = (int)(amount * 100.0);

        Show_change(coins, &coins[NCOINS], pennies);

    Show_change(int amts[], int *end, int due)
        int count;

        while (amts < end)    /* compare pointers */
            count = 0;
            while ((due -= *amts) >= -1)
            if (count > 0)
                printf("%4d %2d%c\n", count, *amts, CENT);
            if (due == 0)
            due += *amts;

            ++amts;                /* increment a pointer */

    Listing 8-4.  The CHANGE2.C program.

    The function Show_change() receives addresses of the array coins and the
    fourth element in that array (one past its end). This introduces some new
    concepts: the interchangeability of the declaration coins[] with the
    declaration *coins and the importance of left versus right values.

The Interchangeability of *amts and amts[]

    In the following declaration:

    Show_change(int amts[]);

    the expression int amts[] tells the compiler to pass this array to the
    function Show_change(). However, you can also use an array declaration of
    the form *amts interchangeably with amts[]. The two are equivalent. In
    fact, if you declare an array as amts[], you can use that array's name as
    though it were a pointer:

    Show_change(int amts[])
        {             └──────────────────────────── Declared as an array
        due = *amts;
                └──┴──────────────────────────────── but used as a pointer

    and vice versa:

    Show_change(int *amts[])
        {              └────────────────────────── Declared as a pointer
        due = amts[i];
                └──┴─────────────────────────────── but used as an array

    Note, however, that this interchangeability works only when the array is
    declared as one of a function's received arguments. An attempt to use that
    singular equivalence elsewhere results in either a Syntax or an lvalue

lvalue vs rvalue

    An lvalue is any variable whose value can change (have a new value
    assigned to it). An rvalue is a variable whose value cannot change. The
    easiest way to differentiate between the two is to remember that rvalues
    go to the right of the assignment operator and lvalues go to the left. Why
    is this important?

    Arrays are usually rvalues because of the way C generates its intermediate
    code. C treats an array as a label (like the target of a goto is a label).
    As an address of a location, an array is a constant value much like the
    number 3 is a constant.

    Confusing lvalues and rvalues with array names is a common source of
    errors for the beginning C programmer. Always remember that array names
    cannot be assigned to, incremented, or decremented, except when they are
    declared as one of the received arguments of a function, as follows:

    char *Amount;───────────────────────────────────Global pointer or lvalue
    int   Bills[4] = {20,10,5,1};────────────────────────────Array or rvalue

    some_function(char amts[];)──────────────────────Equivalent to a pointer
        char *address_var,───────────────────────────Local pointer or lvalue
                old_coins[];──────────────────────────────────────Syntax error

        ++Bills;─────────────────────────────────Illegal operation on rvalue

    In this sample program, *Amount and *address_var are pointers, values that
    can be incremented. Although amts[] is declared as an array, the
    interchangeability we discussed earlier permits us to increment it as
    though it were a pointer. On the other hand, because Bills is not a
    function's argument (it is a global array), it is an rvalue that cannot be
    incremented. Finally, old_coins[] generates a syntax error because only
    arrays in function argument declarations can be used without specifying
    the size of their leftmost dimension.

Type Casting Pointers and Addresses

    Occasionally you will need to perform an arithmetic operation on a pointer
    other than addition, subtraction, or comparison. Fortunately, C is very
    flexible, and it permits you to perform those other operations on pointers
    by using type casts (also called "casts"). In Chapter 3 you used a type
    cast to convert one type to another: You can also use that technique with
    pointers. For example, suppose you need to divide a pointer's value by 2.
    You could use the method:

    unsigned long temp;
    int *point = some_address;

    temp = (unsigned long)point;
    temp /= 2;
    point = (int *)temp;

    First, we assign the address some_address to the pointer int *point. Next,
    we type cast the value in point (the address of some_address) to force a
    change to unsigned long, and then we store the resulting value in temp.
    Because it is legal to divide an unsigned long, we divide temp by 2. Then
    we cast that result, still an unsigned long, to the type int * (meaning
    pointer to an int). Finally, we place the correctly typed new value in

    The PEEK.C program (Listing 8-5) illustrates this use of type casting.
    PEEK.C asks the user for a number, then treats that number as an address
    and shows you the value stored at that address.

    /* peek.c    -- demonstrates how to cast an int to a  */
    /*              pointer                               */

        char *mem_ptr;
        unsigned int address;

        while (1)
            printf("Examine what memory location?\n");
            printf("Enter location in decimal: ");
            if (scanf("%u", &address) != 1)

            mem_ptr = (char *)address;   /* cast  */

            printf("The value in %u is 0x%02X\n",
                    address, (unsigned char)*mem_ptr);

    Listing 8-5.  The PEEK.C program.

far Pointers

    So far, we've assumed that all pointers occupy two bytes of memory. Two
    bytes can represent only addresses in the range 0 through 65535, however──
    not nearly enough to reference every location in the latest PCs.
    Fortunately, QuickC provides a 4-byte pointer, called a far pointer, that
    can address more than four billion bytes of memory.

    In particular, far pointers are useful for directly accessing the text
    screen's memory and for producing sophisticated graphics programs. In this
    section we'll show you how to manipulate the text screen of a graphics

    To declare a far pointer, merely add the keyword far to the pointer
    declaration, as follows:

    int far *screenp;

    We must use a far pointer to access screen memory because that memory is
    located at 0xB000000 (for machines with CGA) or 0xB800000 (for machines
    with EGA or VGA), locations that clearly will not fit into a 2-byte
    pointer (two bytes can hold only four hex digits, not eight). To place
    this hexadecimal constant into a far pointer, use the following type cast:

    screenp = (int far *)0xB000000;──────────────────────────────────────CGA
    screenp = (int far *)0xB800000;───────────────────────────────EGA or VGA

    This tells the compiler to handle the constant 0xB000000 (or 0xB800000) as
    a far address and to assign that address to the far pointer variable

    The SCRINV.C program (Listing 8-6) demonstrates a simple technique for
    manipulating text screen memory. Every time you press a key, the screen
    flips over. (Type Q to quit.) In the listing, adjust the constant assigned
    to screenp to suit your hardware: For EGA or VGA, replace 0xB000000 with

    This program uses a pointer as if it were an array. Although we declare
    screenp as a far pointer:

    int far *screenp;

    we reference its elements using an offset in square brackets, as follows:

    temp = screenp[i];

    Quick Tip
    Be careful when casting pointers to integers. You should always type cast
    to an unsigned long because that type will be large enough to hold all
    addresses. Specifying unsigned will prevent addresses from being (wrongly)
    considered negative, which could lead to incorrect results.

    /* scrinv.c  --  using a far pointer to access text   */
    /*               screen memory                        */

    #define ROWS 25
    #define COLS 80

        int far *screenp;
        int temp, i;

            /* use 0xB800000 for EGA or VGA */
            screenp = (int far *)0xB000000;

            for (i = 0; i < ((ROWS*COLS)/2); ++i)
                temp = screenp[i];
                screenp[i] = screenp[(ROWS*COLS)-i-1];
                screenp[(ROWS*COLS)-i-1] = temp;
            } while (getch() != 'Q');


    Listing 8-6.  The SCRINV.C program.

Functions That Return Addresses

    In Chapter 6, we demonstrated that functions can return values and that
    those values are of type int unless you declare otherwise. You can also
    declare functions that return addresses. The C library contains many
    functions of this type, and your functions can also take advantage of the
    speed and compactness this procedure offers.

    You declare a function that returns an address the way you declare a
    pointer variable──with a type, a *, and a name. For example, the following
    function returns the address of a char type:

    char *function(int arg)

    Quick Tip
    We declared screenp as a pointer to an int because each character on your
    PC text screen is represented by two bytes of information──one byte is the
    character and the other is that character's attribute (normal, blinking,
    inverse, etc.). (We will discuss this organization and the various
    attributes in Chapter 14.)

    This is like using a function as a pointer. Let's try another example.
    Examine the function Range() in the following fragment from an upcoming

    char *Range(int key)
        static char  k2[]   = {'a', 'b', 'c'},
                        k3[]   = {'d', 'e', 'f'},
                        k4[]   = {'g', 'h', 'i'};
        char *kp;

        if (key == 2)
            return (k2);───────────────────────────────────────Address of k2
        else if (key == 3)
            return (&k3[0]);───────────────────────────────────Address of k3
        kp = k4;
        return (kp);───────────────────Return value of a pointer, an address

    This example demonstrates that you can return an address in three ways: as
    an array name (k2), as the address of the first element in an array
    (&k3[0]), or as a pointer (kp).

    Now let's call the above function from another named main():

        char *keys;
        extern char *Range();────────Range will return the address of a char

    Notice that you can only use the return value of Range() after you
    correctly declare it, both in its own declaration and in (or before) any
    functions that call it.

    You can use a pointer value returned by a function the same way you use a
    pointer──with one exception. The address returned by a function is an
    rvalue: Thus, you can neither place it to the left of the assignment
    operator nor change it by computation. The following examples illustrate
    three correct ways to use the value returned by Range():

    keys = Range(2);────────────────────Address assigned to keys (a pointer)

    printf("%cn", Range(2)[1]);─────────────────────Address used as an array

    printf("%cn", *(Range(2)+1));──────────────────Address used as a pointer

    The first example assigns the address value returned by Range() to a
    pointer variable (keys). The second example uses the address returned by
    Range() as if it were an array, printing the second element. The third
    example uses the address returned by Range() as if it were a pointer,
    printing the value stored in that address plus one.

    The PHWORD.C program (Listing 8-7) asks the user for a telephone number
    and then, using the letters of the telephone dial, prints out all the
    possible words that can be made from that number.

    /* phword.c   --  generates all the possible words     */
    /*                in a phone number; demonstrates      */
    /*                functions that return addresses      */

    #define MAXD (7)    /* 7 digits max */

        int digits[MAXD], ndigits = 0, line = 0;
        char *letters;
        signed char digit;
        int a, b, c, d, e, f, g;
        extern char *Range();

        printf("Enter Phone Number (7 digits): ");
            digit = getch() - '0';
            if (digit == ('-' - '0'))
            if (digit < 0 || digit > 9)
                printf("\nAborted: Nondigit\n");
            digits[ndigits++] = digit;
            printf("%d", digit);
            } while (ndigits < 7);

        for( a = 0; a < 3; ++a)
        for( b = 0; b < 3; ++b)
        for( c = 0; c < 3; ++c)
            for( d = 0; d < 3; ++d)
            for( e = 0; e < 3; ++e)
            for( f = 0; f < 3; ++f)
            for( g = 0; g < 3; ++g)
                printf("%c", Range(digits[0])[a]);
                printf("%c", Range(digits[1])[b]);
                printf("%c", Range(digits[2])[c]);
                printf("%c", Range(digits[3])[d]);
                printf("%c", Range(digits[4])[e]);
                printf("%c", Range(digits[5])[f]);
                printf("%c", Range(digits[6])[g]);
                if (++line == 20)
                    printf("Press any key for more");
                    printf(" (or q to quit): ");
                    if (getch() == 'q')
                        return (0);
                    line = 0;

    char *Range(int key)
        static char keys[10][3] = {
            {'0', '0', '0'},
            {'1', '1', '1'},
            {'a', 'b', 'c'},
            {'d', 'e', 'f'},
            {'g', 'h', 'i'},
            {'j', 'k', 'l'},
            {'m', 'n', 'o'},
            {'p', 'r', 's'},
            {'t', 'u', 'v'},
            {'w', 'x', 'y'}

        return (keys[key]);

    Listing 8-7.  The PHWORD.C program.

    The PHWORD.C program also illustrates another point about arrays. When you
    reference a multidimensional array with only a partial list of offsets,
    the value generated is the address of the portion you referenced. Thus,
    although keys in Range() is a two-dimensional array, referencing with only
    a single dimension, as follows:

    return (keys[key]);

    yields the address of only the row specified. In other words, it yields
    the address of a one-dimensional array that is a subset of the
    two-dimensional array.

    Quick Tip
    The extern keyword tells QuickC that the variable or function named will
    be found elsewhere, either later in the current file or in another file
    that you plan to compile separately. It can also be used to tell QuickC
    that you plan to use a variable found in a library routine.

    Notice in PHWORD.C that main() screens your telephone number for illegal
    characters. The function Range() would be more portable if we checked for
    illegal values inside of it and returned an error code. The trick is to
    return an error address that is always illegal. Defined in the standard
    header file stdio.h is the perfect value to convey address errors──NULL.
    This special zero address value is guaranteed to be illegal. By using NULL
    rather than 0, you ensure the portability of your programs.

    The following is a rewrite of Range() that uses NULL:

    #include <stdio.h>       /* for NULL */

    char *Range(int key)
        static char keys[10][3] = ...

        if (key < 0 || key > 9)
            return (NULL);

    Now Range() does its own error checking. It can return NULL, even though
    it is declared char *, because NULL is a special address value that is
    illegal regardless of the expected return type.

Dynamic Arrays

    In the previous chapter, we explained that arrays in C must be
    dimensioned with integer constant expressions, and therefore you cannot
    change the size of a declared array. But now that you have pointers at
    your disposal, the situation is somewhat different. By using standard C
    Library routines, you can allocate memory while the program is running
    (that is, "dynamically") and thus create arrays "on the fly." You can also
    use other C library routines to change the size of dynamically allocated
    arrays, again while the program is running.

    The ability to create, change the size of, and discard arrays from within
    your running program opens a host of new programming possibilities. It
    frees your program from having to know ahead of time how many lines of
    text a user will type, for example, or how many characters it will receive
    via a modem. When you design a database, it is clearly better to allow
    users to add fields at will, rather than restricting them to a
    predetermined record structure. Games are generally more interesting when
    players can add characters at any time. Text editors are more powerful
    when the user can interactively define keyboard macros.

    The standard library routines for the dynamic allocation and reallocation
    of memory are listed in Table 8-1 on the following page. The return types
    for these functions are declared in the header file <malloc.h>. If you
    look at those declarations (using the Include option on the View menu),
    you will see that they are all declared as pointer type void *. This new
    type, when applied to a function's return value, permits the returned
    address to be legally assigned to any type of pointer. This makes it very
    easy for us to create dynamic arrays of any type.

    Table 8-1 Memory Allocation Library Routines
    malloc()           Memory allocate
    calloc()           Computed memory allocate
    realloc()          Reallocate memory
    free()             Free allocated memory
    sbrk()             Request memory from system

The malloc() Memory Allocation Function

    The malloc() function is the most frequently used library allocation
    function. It takes a single argument, the number of bytes of memory you
    wish to allocate (reserve), and returns the address of that memory. If
    malloc() cannot find as much free memory as you specify, it returns a NULL
    value. The correct form for using malloc() (including a check for failure)

    #include <stdio.h>──────────────────────────────────────────────For NULL
    #include <malloc.h>─────────────────────────────For malloc() declaration
    int *iptr;────────────────────────────────────────────To receive address
    size_t bytes = 100;──────────────────────────────────────Number of bytes

    if ((iptr = malloc(bytes)) == NULL)
        /* handle error here */
    printf("Now let's fill the array iptr[]\n");

    The parentheses in the malloc() expression force the result of the
    assignment──the value of iptr──to be compared to NULL. If malloc succeeds
    in allocating memory, iptr contains the address of that dynamically
    allocated memory.

    Because the value of iptr evaluates as an address, you can use iptr as if
    it were an array. For example, the following expression is perfectly


    Quick Tip
    Note in the above example that we declare bytes as type size_t. This type
    is defined in <malloc.h> as an unsigned int for QuickC. Because the type
    size_t is a part of the ANSI standard, you should use it rather than
    unsigned int to ensure the portability of your programs.

    However, to transport programs written with size_t to different machines,
    you might need to use #define to make size_t an unsigned int.

    The TOTAL.C program (Listing 8-8) asks you to type numbers, one per line,
    and dynamically builds an array of those numbers. When you enter a
    non-numeric character, the program displays your list of numbers from the
    array and totals them.

    This program introduces two new elements to our memory allocation
    routines: free() and realloc(). The free() function releases memory that
    you reserve with malloc() or realloc(). The realloc() function copies
    memory into a larger or smaller block of memory.

    /* total.c  -- how to build an array on the fly     */

    #include <stdio.h>         /* for NULL   */
    #include <malloc.h>        /* for size_t */

        int *iptr, count = 0, i, total;
        size_t bytes = sizeof(int);

        /* Start the array with room for one value. */
        if ((iptr = malloc(bytes)) == NULL)
            printf("Oops, malloc failed\n");

        printf("Enter as many integer values as you want.\n");
        printf("I will build an array on the fly with them.\n");
        printf("(Any non-number means you are done.)\n");

        while (scanf("%d", &iptr[count]) == 1)
            /* Enlarge the array. */
            if ((iptr = realloc(iptr,bytes*(count+1))) == NULL)
                printf("Oops, realloc failed\n");
        total = 0;
        printf("You entered:\n");
        for (i = 0; i < count; i++)
            printf("iptr[%d] = %d\n", i, iptr[i]);
            total += iptr[i];
        printf("\nTotal: %d\n", total);

    Listing 8-8.  The TOTAL.C program.

    The free() function takes a single argument, the address returned by
    malloc() or realloc(), and uses it to release that memory. Note that if
    you pass free() an address other than one returned by one of these
    functions, your program may crash.

    The realloc() function takes two arguments: first, the address returned by
    malloc() or one returned from a previous call to realloc(); and second, a
    new size in bytes. The function copies the contents of the old memory to
    the new memory (truncating if the new size is smaller) and returns the
    address of the new memory. Like malloc(), realloc() returns a NULL address
    if it fails.

The calloc() Memory Allocation Routine

    QuickC supplies a companion routine to malloc() called calloc() (for
    "calculated allocate"). The calloc() function also allocates memory, but
    with a twist that makes it ideal for arrays. Instead of merely allocating
    a number of bytes, it takes a pair of arguments: the number of items and
    the number of bytes (sizeof) of each item. The form for using calloc() is
    as follows:

    address = calloc(items, sizeof(item));

    Like malloc(), calloc() returns the address of successfully allocated
    memory or NULL if not enough memory is available.

    The advantage offered by calloc() is that it initializes allocated memory
    to zero values, whereas malloc() can leave memory that is filled with
    garbage. The free() function also releases memory reserved by calloc().

The sbrk() Memory Extension Function

    The malloc() family of routines keeps track of all the memory you
    allocate. These routines use extra bytes to keep a list of allocated
    memory and still more bytes to ensure that all addresses are even. If your
    program is short on space, those pieces of memory might be too valuable to
    waste on mere housekeeping. These functions also need to search through
    lists of available memory to find blocks of the requested size. If you
    have allocated many chunks of memory, that search slows the execution of
    your program.

    QuickC's sbrk() function offers a quick and efficient way to allocate
    memory when you don't need to keep track of how much has been allocated.
    When you load a program into memory, it is arranged as shown in Figure
    8-4. The address of the end of the data segment serves to record the
    highest memory location you can legally access. The sbrk() function
    requests that the highest location be extended by a specified number of
    bytes, as follows:

    address = sbrk(bytes);

    The value returned by sbrk() is the address of the old limit before it was
    extended (in other words, the address of the first byte of the newly
    acquired memory). The address of the first byte beyond the new memory
    allocation then replaces the previously stored value.

                ┌- - - - - - - - - - -─┐◄───New highest after sbrk()
                |                      |
                |                      |
                ├──────────────────────┤◄───Offset from DS
                │            └─────────┼────Highest memory location
                │                      │    your program can use
                │                      │
                │        DATA          │────Uninitialized data
                │                      │
                │                      │
                │                      │
                │        DATA          │────Initialized data
                │                      │
                │                      │
        │       │                      │
        │       │        CODE          │────Your program's code
        │       │                      │
        │       │                      │
    Increasing  │                      │
    memory      └──────────────────────┘◄───DS

    Figure 8-4. The sbrk() function lets you extend the limit of accessible

    Because sbrk() returns an address of -1 on failure, the full call to
    sbrk(), including an error check, is as follows:

    #include <malloc.h> /* for size_t */
    int *iptr;
    size_t bytes = 100;
    if ((iptr = sbrk(bytes)) == (int *)-1)
        /*handle error here */
        /* you have 100 bytes at the address in iptr */

    Note that we must typecast the -1 to (int *) so that the comparison will
    be to the same type as iptr.

    The TOTAL2.C program (Listing 8-9 on the following page) uses sbrk() to
    transform the earlier TOTAL.C program into an adding machine of unlimited
    capacity. We can use sbrk() in TOTAL2.C because we only take memory and
    never release or exchange any. Because sbrk() extends memory continuously,
    our array always remains intact. With malloc(), on the other hand, memory
    may not be allocated continuously, so you must call realloc() to enlarge
    and possibly move your array.

    Unfortunately, giving back pieces of memory that you acquired with sbrk()
    requires advanced programming expertise. If you need to juggle memory
    (taking, then giving back part, and so on), malloc() and realloc() are
    much easier to use. Do not, however, mix sbrk() and the malloc() routines
    in the same program.

    /* total2.c -- how to build an array on the fly       */
    /*             using sbrk()                           */

    #include <stdio.h>        /* for NULL   */
    #include <malloc.h>       /* for size_t */

        int *iptr, count = 0, i, total;
        size_t bytes = sizeof(int);

        /* Start the array with room for one value. */
        iptr = sbrk(0);
        if (sbrk(bytes) == (int *)-1)
            printf("Oops, sbrk failed\n");

        printf("Enter as many integer values as you want.\n");
        printf("I will build an array on the fly with them.\n");
        printf("(Any non-number means you are done.)\n");

        while (scanf("%d", &iptr[count]) == 1)
            /* Enlarge the array. */
            if (sbrk(bytes) == (int *)-1)
                printf("Oops, sbrk failed\n");
        total = 0;
        for (i = 0; i < count; i++)
            total += iptr[i];
        /* just print the total this time */
        printf("%d\n", total);

    Listing 8-9.  The TOTAL2.C program.

Advanced Pointer Techniques

    Perhaps you've heard horror stories about C pointers and incomprehensible
    code. Well, some of those stories are true. Reading and understanding
    poorly written code is like trying to untangle a plate of spaghetti. C
    gives you the freedom to design many types of strange but useful
    constructs. But C also gives you the freedom to design the
    incomprehensible. This section discusses some of C's magnificent but
    potentially arcane constructs──those dealing with the more elaborate and
    sophisticated uses of pointers.

Arrays of Pointers

    C lets you create arrays of any type of elements. Thus, you can even
    create an array whose elements are pointers. For example, to create an
    array of 10 pointers, in which each item is a pointer to a float, simply
    declare the following:

    float *array_name[10];

    The * preceding the array name in this declaration tells the compiler that
    the array is an array of pointers; therefore, each element holds an
    address. The float signifies that all pointers will point to float

    You can use this technique for speeding up sorting routines, for example.
    Because an address on a PC occupies only two bytes (except for far
    pointers), while the data it points to occupies four bytes (for a float),
    it's faster to exchange two 2-byte addresses than to exchange the data.
    The advantage offered by arrays of pointers becomes even more evident when
    we use them with strings in the next chapter.

    The REVERSE.C program (Listing 8-10 on the following page) reads in lines
    of characters. The addresses of those lines are stored in an array of
    pointers to char. (See Figure 8-5.) An empty input line causes the lines
    of text pointed to by the array of pointers to be printed in reverse

    Pointer to first line────│     │─────────►This is the text
    Pointer to second line────│     │─────┐    that we typed in.
                            ├─────┤     │
                    etc.    │     │───┐ └─────►It will be reve
                            ├─────┤   │
                            │     │──┐└─────►rsed. Line 3 is
                            ├─────┤  └─────────────┘
                            │     │─────┐    here. Line 4 is
                            ├─────┤     └──────────┘
                            │     │─────┐    here. Line 5 is
                            ├─────┤     └──────────┘
                            │     │─────┐    Here. And so on...
                            ├─────┤     └──────────┘
                            │     │
                            ├─────┤           Continuous memory
                            │     │           allocated with srbk()
                            Array of

    Figure 8-5. An array of pointers, each element of which contains a line of
    text in allocated memory.

    /* reverse.c -- demonstrates an array of pointers     */
    /*              by reversing lines of text            */

    #include <stdio.h>        /* for NULL   */
    #include <malloc.h>       /* for size_t */

    #define MAXL 20

        char *cptrs[MAXL];            /* array of pointers */
        char *cp;
        int count, i, j, ch;
        extern char *Getbyte();

        printf("Type in several lines of text, and I will\n");
        printf("print them back out in reverse order.\n");
        printf("(Any blank line ends input):\n");

        for (i = 0; i < MAXL; ++i)
            cp = Getbyte();
            cptrs[i] = cp;        /* assign address to pointer */
            count = 0;
            while ((ch = getchar()) != '\n')  /* gather line */
                *cp = ch;
                cp = Getbyte();
            *cp = '\0';
            if (count == 0)        /* all done if blank line */
        for (j = i-1; j >= 0; --j)
            printf("%s\n", cptrs[j]);

    char *Getbyte(void)
        char *cp;

        if ((cp = sbrk(1)) == (char *)-1)
            printf("Panic: sbrk failed\n");
        return (cp);

    Listing 8-10.  The REVERSE.C program.

    The fact that we can print an array of characters with printf()
    illustrates the correspondence between arrays of char and strings. We will
    discuss that relationship in detail in the next chapter.

Pointers to Pointers

    As you have seen, a pointer is a variable whose value is an address, and
    that address is usually the location in memory of another variable.
    However, in C, that other variable can also be a pointer. There is no
    limit to how far you can extend this "pointer-to-a-pointer" relationship──
    you can have pointers to pointers to pointers and so on, ad infinitum.
    Here, however, we'll minimize the danger of creating "spaghetti code" by
    restricting ourselves to pointers to pointers, sometimes referred to as

    Figure 8-6 illustrates the relationship of a pointer to a pointer. The
    variable pp contains as its value the address of p. The variable p in turn
    contains as its value the address of num, an ordinary integer. Because p
    points to an int, pp is a pointer to a pointer to an int.

    The following example shows how to declare a pointer to a pointer:

    int **pp, *p, num;
        │     └─────────────────────────────────────────── Pointer to an int
        └──────────────────────────────────── Pointer to a pointer to an int

    The two * characters tell the compiler that pp is a pointer to a pointer
    and holds as its value the address of another pointer.

    When accessing the values pointed to by pp, the number of *s determines
    which value is obtained. Consider the following initialization:

    p = &num;─────────────────────────────────────────────────Address of num
    pp = &p;────────────────────────────────────────────────────Address of p

                                Points to a variable
                    ▒                                              ▒
            ┌───────────┐                                         ▒
            │           │                                         ▼
    Memory──   621    622   623   624   625   626   627   628   629   630
    locations ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
    in bytes  │    629    │           │    621    │           │     3     │
                    │                       │                       │
                    │                       │                       │
                    p                       pp                     num
                    ▒                       ▒
                        Points to a pointer

    Figure 8-6. Pointer to a pointer: a variable whose value is the address of
    another pointer.

    The following statement yields the value stored in the address that pp
    points to:


    Because pp points to p, *pp yields the address stored in p, that of num.
    Placing another * in front of pp:


    tells the compiler to fetch the value stored in the pointer pointed to by
    pp. Because pp points to p, and p in turn points to num, **pp fetches the
    value of num. Thus, all three of the following yield the value stored in
    the variable num:


    One useful application for a pointer to a pointer is in traversing arrays
    of pointers. The REVERSE2.C program (Listing 8-11) is a rewrite of the
    previous REVERSE.C. In this version, we replace the final for loop with a
    while loop that decrements pp, a pointer to a pointer.

    /* reverse2.c -- demonstrates a pointer to a pointer  */

    #include <stdio.h>        /* for NULL   */
    #include <malloc.h>       /* for size_t */

    #define MAXL 20

        char *cptrs[MAXL];
        char **pp;                    /* pointer to pointer */
        char *cp;
        int count, i, ch;
        extern char *Getbyte();

        printf("Type in several lines of text, and I will\n");
        printf("print them back out in reverse order.\n");
        printf("(Any blank line ends input):\n");

        for (i = 0; i < MAXL; ++i)
            cp = Getbyte();
            cptrs[i] = cp;        /* assign address to pointer */
            count = 0;
            while ((ch = getchar()) != '\n')  /* gather line */
                *cp = ch;
                cp = Getbyte();
            *cp = '\0';
            if (count == 0)        /* all done if blank line */
        pp = &cptrs[i];
        while (pp >= cptrs)
            printf("%s\n", *(pp--));

    char *Getbyte(void)
        char *cp;

        if ((cp = sbrk(1)) == (char *)-1)
            printf("Panic: sbrk failed\n");
        return (cp);

    Listing 8-11.  The REVERSE2.C program.

    This program shows that a pointer to a pointer is decremented (or
    incremented) by the number of bytes in a pointer:

    printf("%s\n", *(pp--))
                        └──┬───────────────────────────────────────── The same
    printf("%s\n", cptrs[i--])

    Recall that the address in a pointer changes by a number of bytes that
    corresponds to the type to which it points. A char pointer changes by one
    byte, while a float pointer changes by four bytes. A pointer to a pointer
    changes by the number of bytes in an address because it points to a
    pointer, and thus to an address. Because cptrs[] is an array of pointers,
    and pp points to one of those addresses, decrementing pp causes it to
    point to the immediately preceding element in that array. Figure 8-7 on
    the following page illustrates this process.

    Pointer Pointer
    Pointers are so versatile that they can contain the address of almost
    anything. However, you cannot use pointers to obtain the address of the
    following C elements: constants (such as 5); variables declared with the
    keyword register; labels (the targets of goto); and keywords (such as if,
    while, and so on).

                ┌───end────│     │─────────►This is the text
                │          ├─────┤
                │  --pp────│     │─────┐    that we typed in.
                │          ├─────┤     │
        pp       │  --pp────│     │───┐ └─────►It will be reve
    ┌───────┐   │          ├─────┤   │
    │       │ ──┤  --pp────│     │──┐└─────►rsed. Line 3 is
    └───────┘   │          ├─────┤  └─────────────┘
    Points to   │  --pp────│     │─────┐    here. Line 4 is
    pointer    │          ├─────┤     └──────────┘
                │  --pp────│     │─────┐    here. Line 5 is
                │          ├─────┤     └──────────┘
                └─start────│     │─────┐    Here. And so on...
                            ├─────┤     └──────────┘
                            │     │
                            ├─────┤           Continuous memory
                            │     │           allocated with srbk()
                        Array of

    Figure 8-7. Decrementing a pointer to a pointer moves it down through an
    array of pointers.

Pointers to Functions

    It is often useful to know the address of a function. You declare a
    pointer to a function as follows:

    int (*pointer_name)();

    This declares the variable pointer_name to be a pointer *pointer_name. The
    trailing parentheses tell the compiler that the pointer *pointer_name
    contains the address of a function. The int specifies that the function
    pointed to returns an int.

    To obtain the address of a function, merely state its name. However, be
    sure you declare the function before you take its address:

    int (*funptr)();─────────────────────────────────A pointer to a function
    extern int Quit();───────────────────────────────────A function declared

    funptr = Quit;──────────────────────Address of Quit() assigned to funptr

    In this example, funptr contains the address of Quit(), and we can call
    Quit() through funptr, as follows:


    The preceding * tells the compiler to use the value pointed to by funptr
    (the address of Quit()). The trailing parentheses tell the compiler to
    call the function whose address we just fetched.

    The CHOOSE.C program (Listing 8-12) goes one step further by creating an
    array of pointers to functions. First, the program asks you to choose a
    menu item. Then it translates your choice into an array offset and calls
    the function whose address is stored at that offset.

    /* choose.c   --  an array of pointers to functions   */
    /*                used to create a menu               */

    void Choice1(), Choice2(), Choice3();

    void (*Dochoice[3])() = {Choice1, Choice2, Choice3};

        int ch;

        printf("Select 1, 2 or 3: ");
        ch = getch(); putch(ch);
        ch -= '1';
        if (ch < 0 || ch > 2)
            printf("\nNo such choice.\n");


    void Choice1(void)
            printf("\nThis is choice 1\n");

    void Choice2(void)
            printf("\nThis is choice 2\n");

    void Choice3(void)
            printf("\nThis is choice 3\n");

    Listing 8-12.  The CHOOSE.C program.

    Arrays of pointers to functions are best applied in interactive programs.
    Believe it or not, you'll find it easier to design word processors and
    complex games once you master this technique.

    The following example illustrates the advantage of using an array of
    pointers to functions instead of a simpler switch statement. Examine the
    following fragment from a hypothetical text processor:

    int (*commands[128])() = {
                Go_left,     /* l */
                Mark_line,   /* m */
                Next_search, /* n */

    This array has 128 pointers to functions, each of which corresponds to a
    key on the keyboard. Pressing l causes Go_left() to be called, moving the
    cursor left. If the user wishes to change the meaning of the keys,
    swapping the functions of l and n, for example, you need only use the

    int (*temp)();───────────────────────────────────────────Scratch pointer
    int from, to;

    from = 'l';
    to   = 'n';
    temp = commands[from];
    commands[from] = commands[to];
    commands[to] = temp;

    Here we first declare a scratch variable to be used in the swap. We
    declare it as a pointer to a function because we will be swapping pointers
    to functions. We then assign to temp the address stored in commands[from],
    where from is the offset that corresponds to the numeric value of the
    letter `n'. Because that array item is a pointer to the function
    Next_search(), we are saving the address of that function. We then copy
    the address in commands[to] into commands[from]. Finally, we assign the
    address saved in temp to commands[to]. The result of this exchange is that
    typing n now causes the Go_left() function to be called, and typing l
    causes the Next_search() function to be called, thereby reversing their

    Contrast this flexible form of programming with an inflexible switch
    statement, such as the following:

        case 'l':

        case 'm':

        case 'n':

    Clearly, a program that a user can customize is more difficult to write,
    yet a versatile program is always worth the extra effort.

Unscrambling the Spaghetti

    In the previous sections of this chapter you've seen some complicated
    declarations. You will see more of them in the chapters to follow, so it
    behooves us to establish some rules that will help us understand complex

    Remember: Always start reading at the inside of a declaration with the
    name (identifier); then work your way outward. For example, to unscramble
    the following declaration:

    int (*name)();

    follow the definition from the inside out: name is a pointer to a function
    of type int. Thus, this declaration is a pointer to a function that
    returns an int.

    Let's try this same technique on a different declaration:

    float (*name)[3];

    In this example, name is a pointer to an array of three float variables.
    Thus, it is a pointer to an array of three floats. Contrast that
    declaration with the following:

    float *name[3];

    Here name is an array of three pointers to float variables. This example
    is an array of three pointers to floats. The difference lies in the
    parentheses. Be sure to obey the order of precedence for operators.

    As an example of using parentheses, try to decipher the following
    declaration from CHOOSE.C:

    int (*funs[4])();

    Here the * operator has a higher precedence than the [] operators, so *
    binds to funs first. Therefore, funs is a pointer, and four such pointers
    exist in an array; these pointers point to functions that return the type
    int. Thus, the declaration is an array of four pointers to functions that
    return int.

Chapter 9  Strings

    A "string" is a sequence of ASCII characters──this sentence, for example,
    is a string. Strings give your programs life by enabling them to
    communicate with the user. Nearly all programs──from our simple printf()
    statements to the sophisticated dialogues of complex interactive
    programs──use strings of one type or another.

    Unlike BASIC and Pascal, the C language has no built-in string-type
    variable. Instead, C uses the convention that a string is an array of type
    char whose final, or terminating, value is the special character '\0'──a
    one-byte zero value. Figure 9-1 on the following page illustrates such an

    We refer to this arrangement as a convention because nothing in C prevents
    you from handling strings in another manner. For example, you might store
    strings as arrays of short variables, using one byte to hold the character
    and the other to hold the character's attributes (more on this in Chapter
    13). Or you might store strings as a value length followed by length
    number of characters. However, because you will most often handle strings
    in the conventional way, we will emphasize that method in our discussion
    of strings.

    Address of bytes──  9876   9877  9878  9879  9880  9881  9882  9883
    in memory          ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
                        │ 'H' │ 'e' │ 'l' │ 'l' │ 'o' │'\n' │'\O' │     │
                        │                             │        
                        └──────────────┬──────────────┘        │
                                    │                       │
                                ASCII characters        Terminating zero

    Figure 9-1. In C, a string is an array of type char terminated by a zero

Declaring and Initializing Strings

    A string is merely an array of type char, and you initialize it the same
    way you would any other array. The following example fills the char array
    named phrase with ASCII character constants that spell "Hello" followed by
    the newline character:

    char phrase[] = {'H', 'e', 'l', 'l', 'o', '\n', '\0'};

    We made this array a conventional C string by adding a terminating zero
    value (the character constant '\0'). As with all arrays, string arrays can
    be initialized only if you use the keyword static or declare them
    globally──outside of all functions.

    The HELLO.C program (Listing 9-1) illustrates the proper way to
    initialize string arrays. It also demonstrates the printf() format command
    %s, which tells printf() to print the next argument as a string.

    Because zero-terminated char arrays so commonly represent strings in C,
    the language provides a built-in shorthand. When C finds text enclosed in
    full quotation marks (called string constants), it immediately stores that
    text as an array of type char and adds the terminating '\0'. This
    characteristic of C provides you an alternate way to initialize arrays.

    /* hello.c  --  legal ways to initialize strings as */
    /*              arrays of char values               */

    char Gphrase[] = {
        'H','e','l','l','o','\n','\0' };    /* global initialization */

        static char gphrase[] = {
            'h','e','l','l','o','\n','\0' };    /* local initialization */

        printf("Global: %s", Gphrase);
        printf("Local:  %s", gphrase);


    Listing 9-1.   The HELLO.C program.

    For example, you can create the same arrays as those declared in HELLO.C
    by substituting the following lines of code:

    char Gphrase[] = "Hello\n";────────────────────────Global initialization

    static char lphrase[] = "Hello\n";──────────────────Local initialization

    As an aid in declaring long string constants, the compiler combines
    adjacent quoted strings into a single string constant. This feature lets
    you easily initialize long strings, as in the following example:

    static char long_phrase[]  = "This is one long "
                                    "sentence that the compiler "
                                    "combines into a single string.";

    C uses the rule that if nothing but white space (spaces, tabs, or
    newlines) separates two quoted strings, those strings are concatenated to
    form a single string. Thus, the above QuickC declaration is equivalent to
    the following:

    static char long_phrase[]  =
    "This is one long sentence that the compiler combines into a single

    Under pre-ANSI C, long string initializers can be emulated with the
    #define preprocessor directive. Recall that you can extend #define lines
    by ending each with a backslash and a newline character (that is, type \
    and press Enter). Because this #define technique is portable to all
    compilers, we will use it throughout the rest of the book:

    #define PHRASE \
    "This is one long sentence that the compiler \
    combines into a single string."

    static char long_phrase = PHRASE;

    A Constant Reminder
    When you declare string constants, remember that it is illegal for a
    newline character to appear anywhere between full quotation marks. The
    following example is illegal:

    static char long_phrase = "This is one
    long sentence that the...";

    and results in the following QuickC error message:

    error C2001:
    newline in constant

    If you want to insert a newline character into a string constant, use the
    escape sequence for a newline character (\n) instead:

    static char long_phrase[] = "This is one \n long sentence that the ..."


The String Pool and String Addresses

    QuickC copies all of a program's quoted strings into a common area of
    memory called the string pool. They are copied there, one after the other,
    in the order that they occur in the program. (Figure 9-2 illustrates this

    The STRPOOL.C program (Listing 9-2) dumps the contents of the string pool
    to your terminal screen. Note in STRPOOL.C that any char array that ends
    with a zero value, such as Cent_string, is placed into the string pool.

    We place nonprinting characters into quoted strings as we did with
    printf()──that is, a newline character, with \n; a carriage return, with
    \r; and a tab, with \t. Other special characters that you can place in
    string constants are the full quotation mark, with \"; the formfeed
    character, with \f; the backspace character, with \b; and the bell (beep)
    character, with \a.

    You can include any character from the PC's extended character set in a
    string constant by using a \x followed by a two-digit hexadecimal number.
    For example, \x9B is used to represent the ¢ character. (QuickC's General
    help screens include a handy table that lists these escape sequences.)

    Note also in the program that we assigned the address of a string to a
    pointer (cp = Start). Nowhere are pointers used more heavily than with

            Your program                              String pool
    ┌────────────────────────────────┐     ┌─────┬─────┬─────┬─────┬─────┬─────
    │°char phrase [ ] = "Hello\n";───┼─────┼►'H' │ 'e' │ 'l' │ 'l' │ 'o' │'\n'
    │°main ( )                      °│     ├─────┼─────┼─────┼─────┼─────┼─────
    │°{                             °│  ┌──┼'\O'─┼►'T' │ 'y' │ 'p' │ 'e' │ ' '
    │°  printf("Type in a line of");─┼──┘  ├─────┼─────┼─────┼─────┼─────┼─────
    │°  printf(" text and I will");──┼──┐  │ 'i' │ 'n' │ ' ' │ 'a' │ ' ' │ 'l'
    │°  .                           °│  │  ├─────┼─────┼─────┼─────┼─────┼─────
    │°  .                           °│  │  │ 'i' │ 'n' │ 'e' │ ' ' │ 'o' │ 'f'
    │°  .                           °│  │  ├─────┼─────┼─────┼─────┼─────┼─────
    │°                              °│  └──┼'\O'─┼►' ' │ 't' │ 'e' │ 'x' │ 't'
    │°                              °│     ├─────┼─────┼─────┼─────┼─────┼─────
    └────────────────────────────────┘     │ ' ' │ 'a' │ 'n' │ 'd' │ ' ' │ 'I'
                                            │ ' ' │ 'w' │ 'i' │ 'l' │ 'l' │'\O'
                                            |     |     |     |     |     |
                                            |     |     |     |     |     |

    Figure 9-2. Quoted string constants are placed one after the other into
    the string pool.

    /* strpool.c  -- dumps the string pool to show how    */
    /*               quoted strings are stored            */

    #define PHRASE \
    "This is one long sentence that the compiler \
    combines into a single string."

    char Start[]        = "start";
    char Long_phrase[]  = PHRASE;
    char Short_phrase[] = "This is a short phrase";
    char Cent_string[]  = "\x9B";

        static char local_phrase[] = "This is local";
        char *cp;

        printf("Dump of the string pool:\n");

        printf("\"");                /* print leading quote */

        * Note that the address of a string can be
        * assigned to a pointer: cp = Start
        for (cp = Start; *cp != '^'; ++cp)
            if (*cp == '\0')        /* print '\0' as a quote */
            else if (*cp == '\n' )  /* print '\n' as '\' 'n' */
                printf("%c", *cp);
        printf("^");                /* marks end */

    Listing 9-2.  The STRPOOL.C program.

Pointers and Initialized Strings

    In the last chapter we assigned the address of an array to a pointer. We
    can also initialize a pointer to char with the address of a quoted string
    constant, as follows:

    char *str = "This is a phrase";

    This example initializes the char pointer *str to contain the address of
    the quoted string constant. Because the compiler places all string
    constants into the "string pool," the address in *str is that of the
    letter "T" (the first character of the char array) in the string pool.

    Recall that an array declaration creates an rvalue and a pointer
    declaration creates an lvalue. Consider the following declarations:

    char ary[] = "This is a phrase";
    char *str = "This is another";

    The ary[] declaration creates an rvalue (an address reference, such as a
    label) that cannot be changed with calculations.

    The *str declaration, on the other hand, creates an lvalue (a pointer
    variable whose value is an address), which can be changed with
    calculations. You can, for example, increment the pointer as follows:


    The distinction between lvalue and rvalue can be a confusing one for
    beginning C programmers. Remember that an array name (such as ary[]) is a
    fixed location and cannot be changed; a pointer (such as *str) is a
    variable and can be changed.

    The BIFFRED.C program (Listing 9-3) demonstrates that you can use
    pointers to manipulate strings in the string pool. Examine the program
    before you run it. Can you predict what it will do?

    /* biffred.c  --  strings in the string pool can be */
    /*                manipulated via pointers          */

    char Start[] = "start";

        char *cp;
        int pass;

        for (pass = 0; pass < 2; ++pass)
            printf("My name is FRED\n");

            cp = Start;

            while (*cp != 'F')

            *cp   = 'B';
            *++cp = 'I';
            *++cp = 'F';
            *++cp = 'F';

    Listing 9-3.  The BIFFRED.C program.

Formatting Strings with printf()

    So far, we've used printf() to print and format numbers (int with %d and
    float with %f, for example), individual characters with %c, and quoted
    strings with %s. The ability of printf() to print strings, however, goes
    far beyond the mere echoing of quoted string constants. In the following

    printf("%s\n", ary);

    the expression ary can be the address of any char type array that ends
    with the character constant value '\0'. It can be a quoted string constant
    such as

    printf("%s\n", "This is a phrase");

    or the address of a string from either a char array or a value in a
    pointer, as in the following examples:

    char *str, ary[] = "This is a phrase";

    str = ary;

    printf("%s\n", ary);─────────────────────────────────Address of an array
    printf("%s\n", str);──────────────────────────────────Value in a pointer

    Because all quoted strings are placed into the string pool and replaced
    with their starting address in that string pool, it follows that the
    format specification in the control string of this example:

    printf("%s\n", str);
                └────────────────────────────────────────────── Control string

    can also be expressed as either an array address or the value in a
    pointer, as follows:

    char *str, ary[] = "This is a phrase.";
    char *cp,  ctl[] = "%s\n";

    str = ary;
    cp  = ctl;

    printf(ctl, ary);────────────────────────────────────Addresses of arrays
    printf(cp, str);──────────────────────────────────────────Pointer values
    printf(ctl, str);─────────────────────────────────────Mixture of the two

    The CONTROL.C program (Listing 9-4 on the following page) demonstrates
    this equivalence. This program asks you to type either an l or an r, and
    then it prints out a string with the corresponding left or right

    CONTROL.C lets you see how the printf() format specifier %s is used to
    format strings. The various options you can use with %s are summarized in
    Table 9-1 on the following page. You can also combine them as in the
    following statement, which prints the first four letters of computer
    right-justified in a 25-character field.

    printf("%25.4s\n", "computer");

    /* control.c  --  demonstrates string justification */
    /*                using printf()                   */

    char Some_text[] = "Some Text";
    char Left_control[] =    "<<%-15s>>";
    char Right_control[] =    "<<%15s>>";

        char ch;

        while (1)
            printf("Select l)eft r)ight or q)uit: ");
            ch = getch();

            switch((int) ch)
                case 'l':
                case 'L':
                    printf(Left_control, Some_text);
                case 'r':
                case 'R':
                    printf(Right_control, Some_text);
                case 'q':
                case 'Q':
                    exit (0);

    Listing 9-4.  The CONTROL.C program.

    Note: In these format specifiers, num must be a decimal integer. You can
    combine the last option, %.nums, with any of the others, producing, for
    example, %25.5s.

    Table 9-1 Variations of the printf() %s Specifier
    %s          Prints the string exactly as it is
    %nums       Prints the string right-justified in a field of width num
    %-nums      Prints the string left-justified in a field of width num
    %.nums      Prints num characters of string

String Input and Output

    The standard C Library contains several functions specifically designed to
    facilitate input and output of strings. Here we discuss some that read
    from your keyboard or print to your screen. The next chapter ("Managing
    Files") deals with file-handling counterparts to these functions. In
    Chapter 13, we will present additional routines that directly access the
    keyboard and screen hardware.

String Input with scanf()

    We've already used scanf() several times: Now let's discuss it in detail.
    The scanf() function uses the same % specifiers that printf() does, but it
    uses them to read values, not to print them. Unfortunately, scanf()
    handles strings a little differently than does printf(). Where printf()
    prints the entire string to a terminating '\0', scanf() reads only
    space-delimited words of text. That is, for each %s in its control string,
    scanf() reads all characters up to, but not including, space, tab, or
    newline. Therefore, scanf() is best used for reading words rather than
    lines of text.

    The scanf() routine, when used with %s to read words of text, takes the

    scanf("%s", buf);

    where buf is the address of a char array (buffer) into which scanf()
    places the text it reads from the keyboard. The array buf can be either a
    char array or a pointer to memory created by malloc(). (Note that you do
    not need to use an ampersand with an array name.) The scanf() function
    appends a terminating '\0' to the text in buf.

    The short SCANLINE.C program (Listing 9-5 on the following page)
    illustrates a simple way to use scanf() for reading words of text from the
    keyboard. It asks you to type in a line of text and then uses scanf() to
    print the words of that text, one word per line.

    When you run SCANLINE.C, notice that it prints nothing until you press the
    Enter key. This is because scanf() is a "buffered I/O" routine. It reads
    from the standard input (the keyboard), but it "sees" nothing until you
    "flush the standard input buffer" by pressing the Enter key. (We discuss
    this concept of buffered versus unbuffered I/O in the next chapter.)

    The scanf() function provides two variations for the %s specifier. (See
    Table 9-2.) These let you read more than individual words.

    Table 9-2 Variations of the scanf() %s Specifier
    %nums       Reads num characters including space, tab, or newline
                characters (Specify num as a decimal integer.)
    %[range]    Reads a specified range of characters

    /* scanline.c  -- demonstrates how scanf() reads  */
    /*                the individual words of a line  */

    #define INTRO \
    "Type in lines of text. They will be printed out\n\
    one word per line, thus demonstrating scanf().\n\
    (Type Ctrl-Z to Quit)\n"

        char buf[512];    /* should be big enough */


        * scanf() returns the number of items
        * its control string matched
        while (scanf("%s", buf) == 1)
            printf("%s\n", buf);


    Listing 9-5.  The SCANLINE.C program.

    The following example reads 127 characters from the keyboard and places
    them into the array buf:

    char buf[128];

    scanf("%127s", buf);──────────────────────────────────────────%nums form
    buf[127] = '\0';

    This form of scanf() has two disadvantages. First, because newline
    characters can be read into buf, you can't easily tell whether buf
    contains a complete line or a partial line or a number of lines. Second,
    because this form does not append a terminating '\0' to the text, you must
    add it yourself.

    For better control, use the more complex scanf() %[range] directive. Here
    range is any list of characters that you want to include in buf. The
    following example:

    scanf("%[0123456789]", buf);

    reads in only the digits 0 through 9. Anything else causes scanf() to stop
    reading and terminate buf with a '\0'.

    A more useful variation of the %[range] directive can be constructed using
    the ^ character. When you use a ^ as the first character in range, scanf()
    reads all characters up to, but not including, any characters in range and
    stops reading at the first excluded character. This version of scanf()
    also appends a terminating '\0' to the characters it reads. The following
    example shows how to use this variation:

    scanf("%[^\n]", buf);─────────────────────────────Read all but a newline
    scanf("%[\n]", dummy);───────────────────────────────Read only a newline

    The first line tells scanf() to read all characters up to, but not
    including, the newline character and to place those characters into buf.
    The second line tells scanf() to read only a newline character (the one
    that terminated the first scanf()) and to place it into dummy. The scanf()
    function can be tricky to use (witness the need for the second statement),
    but with practice, you will find it a valuable and powerful programming

    The SCRANGE.C program (Listing 9-6) summarizes the scanf() function. It
    prompts for, and reads in, several lines of text, displaying exactly what
    scanf() reads as it executes.

    /* scrange.c  --  illustrates scanf()'s control */
    /*                directives                    */

        char buf[512],    /* should be big enough */
            dummy[2];    /* for \n and \0        */
        int  num;

            printf("\tscanf(\"%%d\", &num);\n");
            printf("\tscanf(\"%%[^\\n]\", buf);\n");
            printf("\tscanf(\"%%[\\n]\", dummy);\n");

            printf("\nType enough to satisfy this:\n");
            printf("(Set num equal to zero to quit)\n");

            scanf("%d", &num);
            scanf("%[^\n]", buf);
            scanf("%[\n]", dummy);

            printf("\n\tnum = %d\n", num);
            printf("\tbuf[] = \"%s\"\n", buf);

            } while (num != 0) ;


    Listing 9-6.  The SCRANGE.C program.

Lines of Text with gets() and puts()

    Although we can use variations of scanf() to read lines of text, the
    QuickC library contains a pair of routines specifically tailored for
    reading and writing strings as lines of text. A line of text, in this
    case, is any string that includes a terminating newline. This is the most
    natural form of text entry because it corresponds to a line of text on the

    Although the newline, '\n', is used throughout C to represent the end of a
    line of text, it does not correspond to the characters produced or
    expected by your hardware. The Enter key, for example, actually produces
    the '\r' character. And printing a '\n' to your screen moves the cursor
    down but not to the left on the screen. Fortunately, scanf() and gets()
    convert an Enter keypress ('\r') to a newline ('\n'), and both printf()
    and puts() convert a newline ('\n') into a carriage return/linefeed
    combination ('\r' '\n') when writing to your screen.

    The gets() (pronounced "get s") function reads all typed characters up to
    and including a newline (generated when you press Enter) and places those
    characters into a char array. The newline is then replaced with a '\0' to
    form a C string. The puts() (pronounced "put s") function prints a string
    on the screen and adds a newline to the end.

    The DIALOG.C program (Listing 9-7) uses gets(), puts(), and printf() to
    carry on a simple conversation. Note that because the gets() function
    returns NULL if it fails, we must use the directive #include <stdio.h> to
    incorporate the definition of NULL.

    /* dialog.c  -- a conversation using gets() and puts()  */

    #include <stdio.h>        /* for NULL and BUFSIZ */

    #define THE_QUESTION \
    "And what is your view on the current price of corn\n\
    and the stability of our trade import balance?"

        char name[BUFSIZ],
        extern char *gets();

        name[0] = '\0';        /* clear the name */

        puts("\n\nHi there. And what is your name?");

        if (gets(name) != NULL && name[0] != '\')
            printf("\nPleased to meet you, %s.\n", name);
            * force an extra <enter> before replying
                if (gets(buf) == NULL)

                } while (*buf != '\0');        /* wait for empty line */

            puts("Sorry. I needed to think about that.");
            printf("Nice talking to you, %s.\n", name);
            puts("How rude!");


    Listing 9-7.  The DIALOG.C program.

String Manipulation Routines

    As you can see, the string I/O routines in DIALOG.C are not very
    sophisticated. Fortunately, the QuickC library contains a host of
    functions that permit more complex string manipulations. We won't describe
    all of the functions here──each has its own page in the Microsoft QuickC
    Run-Time Library Reference──but we do list many of them in Table 9-3 on
    page 279. We will, however, use many of these functions in one large
    program and then discuss those selected string-handling routines.

    The ACME.C program (Listing 9-8) asks the user to fill out an employment
    application for a fictional company. It isn't particularly user friendly,
    and it terminates if you type something it can't understand.

    /* acme.c  --     illustrates an assortment of the    */
    /*                C library string-handling routines  */

    #include <stdio.h>        /* for NULL */
    #include <string.h>        /* for strchr(), et al */

    #define NAME_PATTERN \
    "first<space>last  or\n\

    #define ADDRESS_PATTERN \

    char Buf[BUFSIZ];        /* global I/O buffer */
        char *ocp, *cp, *first, *last, *street, *city;
        void Prompt(), Cant();

        printf("Acme Employment Questionnaire\n");

        * Expect first<space>last or
        *        first<space>middle<space>last
        Prompt("Full Name");

        /* search forward for a space */
        if ((cp = strchr(Buf,' ')) == NULL)
            Cant("First Name", NAME_PATTERN);
        *cp = '\0';
        first = strdup(Buf);
        *cp = ' ';

        /* search back from end for a space */
        if ((cp = strchr(Buf,' ')) == NULL)
            Cant("Last Name", NAME_PATTERN);
        last = strdup(++cp);

        * Expect number<space>street<comma><space>city<comma>
        Prompt("Full Address");

        /* search forward for a comma */
        if ((cp = strchr(Buf,',')) == NULL)
            Cant("Street", ADDRESS_PATTERN);
        *cp = '\0';
        street = strdup(Buf);

        /* Search forward from last comma for next comma */
        if ((ocp = strchr(++cp,',')) == NULL)
            Cant("City", ADDRESS_PATTERN);
        *ocp = '\0';
        city = strdup(++cp);

        printf("\n\nYou Entered:\n");
        printf("\tFirst Name: \"%s\"\n", first);
        printf("\tLast Name:  \"%s\"\n", last);
        printf("\tStreet:     \"%s\"\n", street);
        printf("\tCity:       \"%s\"\n", city);


    void Cant(char *what, char *pattern)
        printf("\n\n\bFormat Error!!!\n");
        printf("Can't parse your %s.\n", what);
        printf("Expected an entry of the form:\n\n");
        printf("%s\n\nAborted\n", pattern);

    void Prompt(char *str)
        while (1)
            printf("\n%s: ", str );
            if (gets(Buf) == NULL || *Buf == '\0')
                printf("Do you wish to quit? ");
                if (gets(Buf) == NULL || *Buf == 'y')
                    exit (0);

    Listing 9-8.  The ACME.C program.

The strchr() String Function

    The first new function in ACME.C is strchr() (for "string character").
    This routine requires two arguments──a string to search and a character to
    look for in that string:

    strchr(Buf, ' ')
            └────┴────────────────────── Search string for a space character

    If strchr() finds the character in the string, it returns the address of
    that character. If it doesn't find the character, it returns NULL. Thus,
    we can handle the error as follows if the character is not in the string:

                        ┌────┬─────────────── Search Buf for a space character
    if ((cp = strchr(Buf, ' ')) == NULL)
            └──────────────────────────┼───────────────────────── Save address
                                        └─────────────── Then test for an error

    In the example, cp is a pointer to char into which we assign the address
    returned by strchr(). If the result of that assignment (the value of cp)
    is NULL, the string Buf contains no space character.

    Because strchr() returns the address of a string, you must either declare
    it in your program as char *strchr(); or use the statement #include
    <string.h> (as we did in ACME.C), to supply the declaration for strchr().

The strdup() String Function

    The second new function in ACME.C is strdup(). This is a Microsoft QuickC
    function that does not exist in other C libraries. When passed a string,
    strdup() makes a copy of that string and returns the address of the copy.
    Because this type of "string duplication" is not portable, we'll show you
    a version (Listing 9-9) that is. The implementation of this portable
    version of strdup() introduces two new string-handling functions, strlen()
    and strcpy().

    #include <stdio.h>  /* for NULL  */
    #include <malloc.h> /* malloc    */

    char *
    char *str;
        char *newstr;
        int  bytes;

        bytes = strlen(str);
        if ((newstr = malloc(bytes + 1)) == NULL)
            return (NULL);
    88     (void)strcpy(newstr, str);
        return (newstr);

    Listing 9-9.  The strdup() function.

The strlen() String Function

    The strlen() function counts the number of characters in a string
    (excluding the terminating '\0') and returns that count. For example, the

    static char word[] = "Biff";

    bytes = strlen(word);

    cause bytes to be assigned the value 4 because the string word contains
    four letters.

The strcpy() String Function

    The strcpy() function copies its second argument (a string) into its
    first, a buffer large enough to hold that copy. The value returned by
    strcpy() is the address of its first argument. Because we wanted to ignore
    that return value in our version of strdup(), we typecast the call as type

    if ((newstr = malloc(bytes + 1)) == NULL)
            return (NULL);
    (void)strcpy(newstr, str);

    To create the space for the copy, we call malloc() with an argument of
    bytes + 1, which creates room for both the copy of the string and the
    appended terminating '\0'. (Remember strlen(), which gave us the value in
    bytes, does not count the terminating '\0'.)

    Table 9-3 QuickC Library String Manipulation Functions
    strlen(str)          Returns the length of a string str, not counting the
                        terminating '\0'
    strcat(s1, s2)       Concatenates the second string (s2) to the end of the
                        first (s2)
    strcmp(s1, s2)       Compares two strings (s1 and s2); returns 0 if they
                        are the same, otherwise returns the arithmetic
                        difference of the first two nonmatching characters
    stricmp(s1, s2)      Compares two strings without regard to case
    strncmp(s1, s2, n)   Compares n characters in the two strings (s1 and s2)
    strcpy(buf, str)     Copies a string (str) into a char buffer buf, which
                        must be large enough to hold both the string and its
                        terminating '\0'
    strncpy(buf, str, n) Copies n characters of the string str into the buffer
    strchr(str, ch)      Finds a character (ch) in a string (str); returns the
                        address of ch if found, otherwise returns NULL
    strcspn(s1, s2)      Finds a substring in s1 that begins with anything
                        other than one of the characters in s2; returns the
                        address of that substring if found, otherwise returns
    strstr(s1, s2)       Finds the first occurrence of the substring s1 in the
                        larger string s2; returns the address of that
                        substring if found, otherwise returns NULL
    strrev(str)          Reverses the characters in the string str; returns
                        the address of that reversed string
    strupr(str)          Converts a string (str) to uppercase characters
    strset(str, ch)      Clears a string (str), converting all its characters
                        to the character ch
    strdup(str)          Duplicates a string (str), returning the address of
                        the new copy
    sprintf(str, cntl,   Formatted print into a string (str), converting args
    args,...)            based on the control string cntl
    sscanf(str, cntl,    Formatted convert, like scanf(), but converts from
    addrs,...)           the string rather than from the keyboard

    You should be aware that although stricmp(), strcspn(), and strupr() are
    supplied with the Microsoft QuickC library, they are not a part of ANSI C.
    Do not use them if you want your programs to be portable to other
    compilers and computers.

C vs BASIC String Functions

    As you have seen, sophisticated C string handling can require complicated
    programming. Although the C library string-handling routines can emulate
    much of BASIC, the following example demonstrates that such emulation is
    usually less straightforward:

    A$ = B$────────────────────────────────────────────────────────────BASIC
    first = strdup(Buf);───────────────────────────────────────────────────C

    Some functions common to BASIC are missing from C. Among them are LEFT$,
    MID$, and RIGHT$. Listing 9-10 shows a C version of LEFT$. We leave it as
    an exercise for you to write C versions of the other two BASIC commands.

    C offers two principal advantages over BASIC──it permits the programmer to
    extend string-handling library routines with customized routines, and it
    allows easy access to strings from pointers.

    #include <stdio.h>   /* for NULL     */
    #include <string.h>  /* for strdup() */

    char *
    leftstr(str, cnt)
    char *str;
    int  cnt;
        char *cp;

        if (strlen(str) < cnt || cnt <= 0)
            return (NULL);
        if (strlen(str) == cnt)
            return (strdup(str));
        cp = strdup(str);
        cp[cnt - 1] = '0';
        return (cp);

    Listing 9-10.  The leftstr() function.

Arrays and Strings

    Because a string is nothing more than an array of type char, you can use a
    two-dimensional array of type char as an array of strings. However, you
    must be sure to terminate each row (string) with a '\0' character, as

    char names[3][6] = {
            { 'J','o','e','\0' },
            { 'D','u','k','e','\0' },
            { 'O','z','z','i','e','\0' }

    You also can take the easier route of using string constants (quoted
    strings) as array initializers:

    char names[3][6] = { "Joe", "Duke", "Ozzie" };

    Both forms create identical arrays, as illustrated in Figure 9-3. Also
    notice that underinitializing rows sets the trailing characters in rows 0
    and 1 to '\0'.

        char names [3] [6] = {"Joe", "Duke", "Ozzie"};

                │                                      ┌────────┼─┐
            ┌─┌───────┬───────┬───────┬───────┬──────▼┬───────┐ │
            │ │  'J'  │  'o'  │  'e'  │  '\O' │  '\O' │  '\O' ◄─┼─ Auto-initia
            │ ├───────┼───────┼───────┼──────┼───────┼───────┤ │  trailing ze
    Rows of ─┤ │  'D'  │  'u'  │  'k'  ││ 'e'  │  '\O' │  '\O' ◄─┘
    strings  │ ├───────┼───────┼───────┼┼──────┼──────┼───────┤
            │ │  'O'  │  'z'  │  'z'  ││ 'i'  ││ 'e'  │  '\O' │
                                        │       │       │
                                        └───────┴───────┴────────── String-

    Figure 9-3. A two-dimensional array of char values as an array of strings.

    As we've already seen, strings can be easily manipulated by pointers.
    Because of this, arrays of pointers to strings are often used in place of
    the two-dimensional arrays of char. The previous sample arrays, declared
    and initialized as an array of pointers, appear as follows:

    char *names[3] = { "Joe", "Duke", "Ozzie" };
                └──────────────────────────────────────────── Array of pointers

    This pointer form also uses storage space more efficiently than the
    two-dimensional array. Compare the memory use of this form, depicted in
    Figure 9-4, with that of the preceding approach (shown in Figure 9-3).

        ┌──────────┐       ┌───────┬───────┬───────┬───────┐
        │          │──────►│  'J'  │  'o'  │  'e'  │  '\O' │
        ├──────────┤       ├───────┼───────┼───────┼───────┼───────┐
        │          │──────►│  'D'  │  'u'  │  'k'  │  'e'  │  '\O' │
        ├──────────┤       ├───────┼───────┼───────┼───────┼───────┼───────┐
        │          │──────►│  'O'  │  'z'  │  'z'  │  'i'  │  'e'  │  '\O' │
        └──────────┘       └───────┴───────┴───────┴───────┴───────┴───────┘
    Array of 3 pointers
    char *names[3] = {"Joe", "Duke", "Ozzie"};

    Figure 9-4. Arrays of pointers to strings use memory efficiently.

    The L2WORDS.C program (Listing 9-11) illustrates one application for an
    array of pointers to strings. It asks you to enter a line of text, then it
    breaks that line into individual words and returns an array of pointers to
    the substrings that form those words. Line2words() assumes that spaces
    separate words, but it can take multiple words as a single word if you
    surround them with full quotation marks. A routine like Line2words() is
    useful for writing your own command-line interpreter (COMMAND.COM).

    /* l2words.c  --  employs an array of pointers to  */
    /*                strings to break a line of text  */
    /*                into its component words         */

    #include <stdio.h>        /* for NULL and BUFSIZ */

        char **Line2words();    /* declare function type */
        char **list;            /* pointer to pointer    */
        char buf[BUFSIZ];       /* buffer for input      */
        int  count, i, quote_flag;

        printf("Enter a line of text and I will break\n");
        printf("it up for you.\n");

        if (gets(buf) == NULL)

        list = Line2words(buf, &count);

        for (i = 0; i < count; i++)
            quote_flag = 0;
            if (list[i] != buf)
                if( list[i][-1] == '"')    /* negative subscript */
            printf("%s", list[i]);

            if (quote_flag)

    #define MAXW 64

    char **Line2words(char *line, int  *count)
        static char *words[MAXW];
        int  index;

        index = 0;        /* zero internal index */

        while (*line != '\0')
            /* turn spaces and tabs into zeros */
            if (*line == ' ' || *line == '\t')
                *(line++) = '\0';
            words[index] = line++;    /* found a word */

            /* is it quoted? */
            if ( *(words[index]) == '"')
                /* Yes, advance pointer to just past quote. */

                /* find next quote. */
                while (*line && *line != '"')

                /* and turn it into a '\0'. */
                if (*line)
                    *(line++) = '\0';
                /* otherwise skip to next space */
                while (*line && *line != ' ' && *line != '\t')
            if (++index == MAXW)
        *count = index;        /* set count via pointer   */
        return (words);        /* return address of array */

    Listing 9-11.  The L2WORDS.C program.

    L2WORDS.C does a few tricky things: First, notice that we declare the
    function Line2words() as char **. This means that it returns a pointer to
    a pointer. That pointer contains the address of the first element of our
    array of pointers. The first element in that array points to the first

    Second, notice that when the program prints words, it checks lines[i][-1]
    (negative subscripting) to see if the string has full quotation marks
    around it. If it does, the program replaces them when it prints the word.

The Arguments to main()──argv and argc

    When you run a program from the command interpreter (COMMAND.COM under
    MS-DOS, or sh or csh under UNIX), you can specify arguments for the
    program on the command line. For example, when you run QuickC by typing

    C> qc file.c

    QuickC starts with the file named file.c already loaded. All C programs,
    including QuickC, retrieve arguments from the command line in the same
    way. That is, every C program begins execution with the function named
    main(), and that function, like any other, can receive arguments.
    Traditionally called argc and argv, these arguments are received by main()
    as follows:

    main(argc, argv)
    int argc;
    char *argv[];

    These arguments to main() contain all the information that you need to
    access the command-line arguments: argc is the number of command-line
    arguments, and argv is an array of pointers to those arguments.

    The SHOWARGS.C program (Listing 9-12) shows how to access and use the
    arguments passed to main(). To run this program from within QuickC, you
    must first set the command-line arguments with Set Runtime Options on the
    Run menu.

    When you run SHOWARGS.C with the following command-line preset in the Set
    Runtime Options dialog box:

    kit makes lovely paper

    the program prints the following:

    argc = 5───────────────────────────────────────────Five pointers in argv

    argv[0] -> "C"
    argv[1] -> "kit"
    argv[2] -> "makes"
    argv[3] -> "lovely"
    argv[4] -> "paper"
    argv[5] -> NULL

    /* showargs.c  --  shows how to access the arguments  */
    /*                 passed to main()                   */

    #include <stdio.h>        /* for NULL */

    main(argc, argv)
    int argc;
    char *argv[];
        int i;

        printf("argc = %d\n", argc);

        for (i = 0; i < argc; ++i)
            printf("argv[%d] -> \"%s\"\n", i, argv[i]);
        printf("argv[%d] -> NULL\n", i);

    Listing 9-12.  The SHOWARGS.C program.

    The first string that argv points to (an array of pointers to strings) is
    usually the name of your program. (Under QuickC, your program will always
    be named C when you run it from the Run menu, but argv[0] is correct when
    you run your program later as a .EXE file.)

    Because argv is an array of pointers to char, you often will see it
    alternatively declared as follows:

    main(argc, argv)
    int argc;

    main(argc, argv)
    int argc;
    char **argv;
    {    └─────────────────────────────────────────── A pointer to a pointer

    Recall that this pointer to a pointer and the declaration char *argv[] are

    The main() function is actually passed three arguments, but the third
    argument, called envp, is seldom used. Like argv, it is an array of
    pointers to strings and must be declared as follows:

    main(argc, argv, envp)
    int argc;
    char *argv[], *envp[];

    The strings that envp points to are your system's environmental variables,
    such as PATH.

    Take a moment to modify SHOWARGS.C so that it matches the SHOW2.C program
    (Listing 9-13). After you run this program, choose DOS Shell from the
    File menu and type set. Compare the output produced by the MS-DOS SET
    command to that produced by this program.

    /* show2.c  --     shows how to use main()'s envp */

    #include <stdio.h>        /* for NULL */

    main(argc, argv, envp)
    int argc;
    char *argv[], *envp[];
        int i;

        printf("argc = %d\n", argc);

        for (i = 0; i < argc; ++i)
            printf("argv[%d] -> \"%s\"\n", i, argv[i]);
        printf("argv[%d] -> NULL\n", i);

        for (i= 0; envp[i] != NULL; ++i)
            printf("envp[%d] -> \"%s\"\n", i, envp[i]);
        printf("envp[%d] -> NULL\n", i);


    Listing 9-13.  The SHOW2.C program.

Character Classification and Transformation

    You often need to be able to classify individual characters of a string
    (such as uppercase versus lowercase) and then transform them (such as
    converting uppercase to lowercase). QuickC includes a standard C header
    file called ctype.h, which defines many character classifications and
    transformation routines. (Use the View Include menu to examine it.) To
    access ctype.h, merely use #include to include it at the head of your

    The routines in ctype.h are not true functions: They are #define macros.
    We'll describe #define macros in detail in Chapter 12. In the meantime,
    you can use these routines because they work like function calls.

The Character Classification Routines

    Each of the character classification routines in Table 9-4 takes a single
    argument──the character to classify──and returns a 1 for true or a 0 for

    The WHATCHAR.C program (Listing 9-14) prints all possible classifications
    for each character in a line of entered text. The program limits the line
    of text to 20 characters so that the display doesn't scroll off the

    Table 9-4 The Character Classification Routines in ctype.h
    isalnum()   Tests for alphanumeric ('A' through 'Z,' 'a' through 'z,' and
                '0' through '9')
    isalpha()   Tests for a letter ('A' through 'Z' and 'a' through 'z')
    isascii()   Tests for an ASCII character (0x00 through 0x7F)
    iscntrl()   Tests for a control character (less than ' ' or equal to 0x7F)
    isdigit()   Tests for a digit ('0' through '9')
    isgraph()   Tests for printable character (inverse of iscntrl() but
                excludes space)
    islower()   Tests for lowercase letter ('a' through 'z')
    isprint()   Tests for printable character (inverse of iscntrl())
    ispunct()   Tests for punctuation character
    iswhite()   Tests for white space ('\t,' '\n,' '\f,' and ' ')
    isupper()   Tests for uppercase letter ('A' through 'Z')
    isxdigit()  Tests for a hexadecimal digit ('A' through 'F,' 'a' through
                'f,' '0' through '9')

    /* whatchar.c  --  demonstrates the character         */
    /*                 classification routines in ctype.h */

    #include <stdio.h>        /* for NULL and BUFSIZ */
    #include <ctype.h>        /* for iscntl(), et al */
    #define MAXL 20

        char buf[BUFSIZ];
        int i;

        printf("Enter a line of text (20 chars max):\n");
        if (gets(buf) == NULL)

        for (i = 0; i < MAXL; ++i)
            if (buf[i] == '\0')
            printf("'%c' ->", buf[i]);
            if (isalpha(buf[i]))   printf(" isalpha");
            if (isascii(buf[i]))   printf(" isascii");
            if (iscntrl(buf[i]))   printf(" iscntrl");
            if (isgraph(buf[i]))   printf(" isgraph");
            if (isprint(buf[i]))   printf(" isprint");
            if (isdigit(buf[i]))   printf(" isdigit");
            if (isupper(buf[i]))   printf(" isupper");
            if (islower(buf[i]))   printf(" islower");
            if (ispunct(buf[i]))   printf(" ispunct");
            if (isspace(buf[i]))   printf(" isspace");
            if (isalnum(buf[i]))   printf(" isalnum");
            if (isxdigit(buf[i]))  printf(" isxdigit");

    Listing 9-14.  The WHATCHAR.C program.

    The include file ctype.h also defines routines to transform characters.
    Each of the routines in Table 9-5 takes a single argument, the character
    to transform, and returns the transformed character, as in the following

    ch = toupper('a');

    Here toupper() is given a lowercase 'a'. Because 'a' is lowercase,
    toupper() transforms it to an uppercase 'A' and assigns that value to the
    variable ch.

    The INVERT.C program (Listing 9-15) uses both the character
    classification and transformation routines to reverse a line of entered
    text. That is, it prints the line backward and inverts the case of each

    Table 9-5 The Character Transformation Routines in ctype.h
    toascii()   Converts a non-ASCII character to an ASCII character (clears
                all but the low-order seven bits)
    toupper()   Converts a lowercase character to an uppercase character
    tolower()   Converts an uppercase character to a lowercase character

    /* invert.c  --  combines character classification and */
    /*               transformation to invert text        */

    #include <stdio.h>         /* for NULL           */
    #include <ctype.h>        /* for toupper, et al. */

        char buf[BUFSIZ];
        int i;

        printf("Type in a line of text and I will invert it.\n");

        if (gets(buf) == NULL)
        /* Print the string backward. */
        for (i = (strlen(buf) - 1); i >= 0; --i)
            if (isupper(buf[i]))            /* upper to lower */
            else if (islower(buf[i]))       /* lower to upper */


    Listing 9-15.   The INVERT.C program.

Chapter 10  Managing Files

    C files are primarily disk files that contain text, executable images of
    programs, or data. These disk files represent stored programs and data
    that form a common "library" of information that is available to a wide
    range of programs.

    The QuickC library functions that handle file input and output are
    arranged in three categories, or levels, as illustrated in Figure 10-1 on
    the following page. At the top level are the buffered (stream I/O)
    routines; below those are the unbuffered (raw I/O) routines; and at the
    bottom are the direct BIOS interfaces. The low-level routines are not a
    part of portable C because they access PC-specific internal routines. The
    higher-level routines, however, are universal to all C compilers. We will
    not cover the low-level BIOS routines in this book.

    The top-level file I/O routines are called "buffered stream" routines
    because they interpose themselves between your program and files. They
    read and write large blocks of information (buffering), and then they pass
    a continuous series (stream) of bytes to your program, as needed.

                            │                    │
                            │      Buffer        │◄────────────────┐
                            │                    │                 │
                            └───────────────────┘                 │
                                        │                            │
                                        │                            │
                            ┌─────────▼──────────┐                 │
                    ┌─────────►│Top-level           │                 │
                    │          │functions like      │         ┌───────▼────────┐
    ┌──────────────▼─┐        │fopen() and fgetc() │         │                │
    │                │        └────────────────────┘         │    Your        │
    │      Disk      │        ┌────────────────────┐         │    program     │
    │                │        │Mid-level           │         │                │
    └──────────────┘        │functions like      │         └──────────────┘
            │     └─────────►│open() and read()   │◄──────────┘     │
            │                └────────────────────┘                 │
            │                ┌────────────────────┐                 │
            │                │Low-level           │                 │
            └───────────────►│functions like      │◄────────────────┘
                            │_bios_disk()        │

    Figure 10-1. The three levels of file I/O.

    The middle file level is called unbuffered because it lets your program
    access files directly. Reads and writes do not pass through an
    intermediate buffer; they pass directly between the operating system and
    your program. These mid-level routines can execute faster than the
    top-level routines, but they are more complex to use.

    Both top-level and mid-level file routines have two modes──text and
    binary. Text mode is used with text files, or files that contain ASCII
    text (which is readable by persons). Binary mode is used with files that
    contain binary information, such as executable programs. In text mode,
    Ctrl-Z (a byte containing the value 0x1A) marks the end of a file. In
    binary mode, Ctrl-Z can legally be a part of the file; the operating
    system keeps track of file length.

Top-Level I/O

    All buffered file I/O functions require that you begin your program with
    #include <stdio.h>. That header file contains the definition for FILE, the
    data type that you use to manipulate files. The type FILE is used as shown
    on the next page.

    #include <stdio.h>
    FILE *fp;

    Remember, always use #include <stdio.h> for the definition of the type
    FILE. Then declare a file pointer to point to the data type FILE.

Opening Files with fopen()

    Before you can access a file for reading or writing or both, you must
    first open that file. For buffered I/O routines (those that use a file
    pointer), open the file with the fopen() function, as follows:

    fp = fopen(filename, activity);
                    │          └───────────────── Open to read, write, or both
                    └──────────────────────────────────── Name of file to open

    The fopen() function requires two arguments: the name of the file to open
    (a string or the address of a string) and an activity (also a string) as
    listed in Table 10-1. The activity determines whether the file is open
    for reading, writing, or appending. (In this case, read means to take
    information sequentially from a file, write means to put information
    sequentially into a file, and append means to add information to the end
    of a file.)

    The fopen() function returns a value of type FILE *. In our example we
    assigned this value to file pointer fp, which we will use to access and
    manipulate the file. If fopen() fails, it returns NULL. Therefore, the
    complete call to fopen(), including error handling, is as follows:

    fp = fopen("test.c", "r");
    if (fp == NULL)
        /* handle error here */

    This opens the TEST.C file for reading (activity "r"). After the file
    pointer returned by fopen() is assigned to fp, we test fp to see if it is
    NULL. We test for an error here because it is possible that the file
    TEST.C does not exist.

    Table 10-1 Possible Modes (Activities) for fopen()
    Mode       Description
    "r"        Open for reading only. The file must already exist.
    "w"        Open for writing only. Creates the file if it does not exist.
    "a"        Open for appending (write-only, starting at the end of a file).
                Creates the file if it does not exist.
    "r+"       Open for both reading and writing. The file must already exist.
    "w+"       Open for both reading and writing. Creates the file if it does
                not already exist.
    "a+"       Open for both reading and writing, starting at the end of the
                file. Creates the file if it does not exist.

    Each open file requires its own file pointer. The following two open
    files, for example, require two separate file pointers:

    #include <stdio.h>
    FILE *fp_in, *fp_out;
    fp_in = fopen("test.txt", "r");
    fp_out = fopen("test.bak", "w");

    In this example, fp_in is the file pointer for the file opened for reading
    (activity "r"), and fp_out is the file pointer for the file opened for
    writing (activity "w").

    File Access in BASIC and C
    If you're used to BASIC file handling, you'll find that QuickC offers
    fewer "built-in" conveniences but ultimately provides more power and
    flexibility. In BASIC, you might open a random access file with the
    following statement, which specifies the file identification number and
    record length:


    Before you can use the file, you have to use FIELD statements to associate
    whatever numeric or string variables you are going to use with the
    corresponding data fields in the file record. Because most versions of
    BASIC don't have a data type similar to the C struct, you have to
    manipulate numerous separate variables to move data to and from the file.
    The built-in random access support does allow you to get a record by its
    record number directly using the GET statement, however.

    C has a different approach: A file can contain any valid C data type, such
    as a struct, which already has its fields defined, so you don't have to
    set up file data fields. On the other hand, file manipulation methods,
    such as random access, are not built-in in C. You can achieve random
    access, however, by converting a record number to an offset and then using
    the library function fseek() to position C's file pointer to the correct
    record. You can also use the fgetpos() and fsetpos() functions to
    manipulate the file pointer.

    Also, because C uses function calls rather than BASIC's procedural
    commands to manipulate files, you can quickly check for errors by putting
    the function call in an if statement.

Reading Characters with fgetc()

    There's more to reading a file than merely opening the file to read. To
    see what we mean, examine the STRINGS.C program (Listing 10-1), which
    reads a file one character at a time and looks for strings of five or more
    printable characters. The program takes a command-line argument, so before
    you run it, you must create the argument using the Set Runtime Options
    screen from the Run menu. In the Command Line box, type c:\qc\qc.exe (or
    the name of any existing file). Figure 10-2 on the next page shows the
    screen after you type the command.

    /* strings.c  --  opens a file and searches it for */
    /*                possible strings                 */

    #include <stdio.h>        /* for FILE, BUFSIZ, & EOF */
    #include <ctype.h>        /* for isprint()           */

    main(argc, argv)
    int argc;
    char *argv[];
        FILE *fp;
        char buf[BUFSIZ];
        int ch, count;

        if (argc != 2)
            fprintf(stderr, "usage: strings file\n");
        if ((fp = fopen(argv[1], "rb")) == NULL)
            fprintf(stderr, "Can't open %s\n", argv[1]);

        count = 0;
        while ((ch = fgetc(fp)) != EOF)
            if (! isprint(ch) || count >= (BUFSIZ - 1))
                if (count > 5)
                    buf[count] = 0;
                count = 0;
            buf[count++] = ch;

    Listing 10-1.  The STRINGS.C program.

    │ Figure 10-2 can be found on p.296 of the printed version of the book.  │

    Figure 10-2. The Set Runtime Options dialog box lets you enter a command

    When we run STRINGS.C on a large file such as QC.EXE, the program prints
    many screens of possible strings. For convenience, you might add a
    "paging" feature to the program.

    STRINGS.C uses the fgetc() function, a file-oriented version of the
    getchar() routine we've used before. After it is passed a single argument
    (a file pointer), the function returns the next character read from the
    file pointed to. Assigning that character to a variable of type int lets
    us detect EOF (End Of File) easily.

    int ch;

    if ((ch = fgetc(fp)) == EOF)
            /* handle end of file here */

    Notice that STRINGS.C calls fopen() with the activity argument "rb". This
    is a PC-specific extension of the normal "open for reading" argument. The
    b tells fopen() to open the file in binary mode but to do no character
    translation for us──that is, to give fgetc() every byte from the file as
    is. If we did not specify the b, fopen() would have opened the file in
    text mode. Had we used text mode, however, our program would not have read
    all of QC.EXE because the Ctrl-Z character, which is a legal byte in
    binary files, would have marked the end of the file. Table 10-2 shows the
    difference between these two modes.

    Also notice that STRINGS.C ends without closing the file. C, unlike BASIC,
    closes all open files when you exit the program. This is true whether you
    exit main() with a return or from another function with an exit().

    Table 10-2 Text vs Binary Modes for fopen()
    t       text mode        Translates carriage return/linefeed combinations
                            into single linefeeds on input and makes the
                            reverse translation on output. Ctrl-Z marks the
                            end of the file.
    b       binary mode      Suppresses the above translations. The operating
                            system keeps track of the file's length.

Closing Files with fclose()

    Although MS-DOS lets you have as many as 20 simultaneously open files, you
    might want to close each open file before you open another one. Closing a
    file writes everything to disk, updates the directory entry for that file,
    and frees a file pointer.

    When you open files with fopen(), you can close them with fclose(), as

    if (fclose(fp) == EOF)
            /* unable to close file */
    /* fp may be reused here */

    If fclose() cannot close a file (because the floppy disk containing that
    file was removed, for example), it returns EOF.

Line I/O with fgets() and fputs()

    The standard C Library contains a pair of file-oriented routines called
    fgets() ("file get string") and fputs() ("file put string"). They are
    similar to the gets() and puts() pair we discussed in the last chapter:
    fgets() reads lines of text from files and fputs() writes lines of text
    into files. Use them as follows:

    #include <stdio.h>
    #define SIZE 512
    FILE *fp_in, *fp_out;
    char buf[SIZE];
    /* open fp_in for reading and fp_out for writing */
    if (fgets(buf, SIZE, fp_in) == NULL)
            /* error reading or EOF */
    /* a line of text is now in buf */

    if (fputs(buf, fp_out) == EOF)
            /* error writing */

    The fgets() function takes three arguments: the address of a char buffer,
    the maximum number of characters to read into that buffer, and a file
    pointer to a file opened for reading. In the example, fgets() reads a
    maximum of SIZE characters (up to and including the first newline
    character) and appends a terminating '\0' to the characters to form a
    string. The fputs() function requires two arguments: the address of a
    zero-terminated string (in buf) and a file pointer to a file opened for
    writing. In the example, fputs() writes the string in buf to fp_out──
    including any newline in that string. Note as well that fputs() does not
    add any newlines.

    The fputs() and fgets() functions differ from their counterparts puts()
    and gets(). Each handles the newline character in a different way, as

    gets(buf)      Reads characters from keyboard and places them into buf.
                    Replaces the trailing newline character ('\n') with a '\0'.

    fgets(buf,     Reads a maximum of len characters from a file opened for
    len, fp)       reading. Places len or fewer characters (up to and
                    including a newline) into buf. Retains the newline
                    character and adds a terminating '\0'.

    puts(buf)      Prints the string in buf to the screen and adds a newline
                    character to the output on the screen.

    fputs(buf, fp) Prints (writes) the string in buf into the file (opened for
                    writing) pointed to by the file pointer fp. Does not add a
                    newline character to the output.

    The CCOPY.C program (Listing 10-2) reads one file and writes to a second.
    The "C" preceding "COPY" (in the program name) signals that this COPY
    "crunches" its input──eliminating all empty lines, leading tabs, and
    spaces. You could use this program to prepare files before sending them
    over a slow modem.

    /* ccopy.c  --  copies a file, cutting blank lines and  */
    /*              leading space from lines of copy        */

    #include <stdio.h>        /* for FILE, BUFSIZ, NULL */
    #include <ctype.h>        /* for iswhite()          */

    main(argc, argv)
    int argc;
    char *argv[];
        FILE *fp_in, *fp_out;
        char buf[BUFSIZ];
        char *cp;

        if (argc != 3)
            printf("usage: ccopy infile outfile\n");
        if ((fp_in = fopen(argv[1], "r")) == NULL)
            printf("Can't open %s for reading.\n", argv[1]);
        if ((fp_out = fopen(argv[2], "w")) == NULL)
            printf("Can't open %s for writing.\n", argv[2]);

        printf("Copying and Crushing: %s->%s ...",
                    argv[1], argv[2]);

        while (fgets(buf, BUFSIZ, fp_in) != NULL)
            cp = buf;
            if (*cp == '\n')    /* blank line */
            while (isspace(*cp))
            if (*cp == '\0')    /* empty line */
            if (fputs(cp, fp_out) == EOF)
                printf("\nError writing %s.\n", argv[2]);

    Listing 10-2.  The CCOPY.C program.

    To run this program you need to set its command line from the Set Runtime
    Options dialog box. The Command Line text box requires two filenames as
    arguments──first, the file to read, and second, the file to write to. For
    example, you might enter the filenames strings.c temp. The first name is
    the existing text file to be read (note that the fopen() in CCOPY.C uses
    "r" for text mode). The second name is the new file that will be created
    (activity "w").

    Within a loop, fgets() reads a line of text from the first file, the
    program crunches that line, and fputs() writes the condensed line into the
    second file. After you run CCOPY.C, choose DOS Shell from the File menu
    and look at the newly created file using the TYPE command and Ctrl-S.

Error Detection with feof() and ferror()

    Using fgets() has a drawback──it returns NULL for both EOF (which you
    expect) and read errors (which you don't expect). However, you can
    differentiate between the two by using feof() and ferror().

    The feof() function tests a file opened for reading and associated with a
    file pointer to see if the end of that file has been reached. It returns
    true (nonzero) at the end of the file; otherwise it returns 0. The
    ferror() function returns true if there is any error with the file──
    including reaching the end of file. The following example shows how to use
    them together to differentiate between the two conditions:

    if (feof(fp_in))
            /* reached end of file while reading */
    else if (ferror(fp_in))
            /* some read error has occurred */

    EOF is meaningful only when reading; use ferror() alone when writing to a

    if (ferror(fp_out))
            /* some write error has occurred */

    Always include error-checking routines in your programs to protect
    yourself from careless users. Users sometimes remove floppy disks while
    the drive light is on or try writing to disks that are write-protected.
    Error detection lets you either take corrective action or notify users of
    their mistakes.

Block I/O with fread() and fwrite()

    So far we've treated files as lines of text. However, you will often want
    to read and write files in specific blocks whose size is measured in
    bytes. (Executable program files and data files, for example, generally
    contain no meaningful lines of text.) To do this, the standard C Library
    provides a pair of routines called fread() and fwrite(). Their forms are
    nearly identical:

    fread(buffer, size, count, fp_in);
                │      │      │    └──────────────────────── A file pointer
                │      │      └────────────────────────── How many size items
                │      └──────────────────────────── How many bytes per item
                └───────────────────── Address of (size * count) bytes buffer

    fwrite(buffer, size, count, fp_out);
                │      │      │    └──────────────────────── A file pointer
                │      │      └────────────────────────── How many size items
                │      └──────────────────────────── How many bytes per item
                └───────────────────── Address of (size * count) bytes buffer

    Both routines require that you specify #include <stdio.h> to define FILE
    for the file pointer and to define the new type size_t for the variables
    size and count:

    size_t size;
    size_t count;
    FILE   *fp;

    QuickC defines the type size_t in the <stdio.h> header file as an unsigned
    long. Because it might be defined differently with other compilers, you
    should use size_t for portability.

    Both functions return the number of bytes actually read or written. When
    that number is less than size times count, an error has occurred. In the
    case of fread(), however, that error can also indicate that you've reached
    the end of the file. Therefore, you need to use feof() to distinguish end
    of file from other errors.

    The UPPITY.C program (Listing 10-3) shows one way to use fread() and
    fwrite(). It reads an entire file into memory (using malloc() to obtain
    that memory), converts it to uppercase, then writes the entire file to a
    new file having the .UP extension.

    /* uppity.c --  makes an uppercase copy of a file using */
    /*              fread() and fwrite()                    */

    #include <string.h>     /* for strrchr() */
    #include <stdio.h>      /* for NULL      */
    #include <malloc.h>     /* for malloc()  */
    #include <ctype.h>      /* for isupper() */

    #define HUNK 512

    main(argc, argv)
    int argc;
    char *argv[];
        char *cp, newname[128], *np;
        FILE *fp;
        int  hunks = 0, bytes = 0, totbytes = 0;
        int  i;
        if (argc != 2)
            printf("usage: uppity file\n");

        if ((fp = fopen(argv[1], "rb")) == NULL)
            printf("\"%s\": Can't open.\n", argv[1]);
        if ((cp = malloc(HUNK)) == NULL)
            printf("Malloc Failed.\n");

        while ((bytes = fread(cp + (HUNK * hunks), 1, HUNK, fp)) == HUNK)
            totbytes += bytes;
            if ((cp = realloc(cp, HUNK + (HUNK * hunks))) == NULL)
                printf("Realloc Failed.\n");
        if (bytes < 0)
            printf("\"%s\": Error Reading.\n", argv[1]);
        totbytes += bytes;

        for (i = 0; i < totbytes; ++i)
            if (islower(cp[i]))
                cp[i] = toupper(cp[i]);


        if ((np = strchr(argv[1], '.')) != NULL)
            *np = '\0';
        strcpy(newname, argv[1]);
        strcat(newname, ".up");
        if ((fp = fopen(newname, "wb")) == NULL)
            printf("\"%s\": Can't open.\n", argv[1]);

        if (fwrite(cp, 1, totbytes, fp) != totbytes)
            printf("\"%s\": Error writing.\n", argv[1]);


    Listing 10-3.  The UPPITY.C program.

    UPPITY.C continually reallocates memory for each HUNK (512 bytes) of the
    file read in. A more direct approach would find the size of the file, then
    read in that many bytes with a single fread(). You can do this with the
    stat() function. Unfortunately, to use stat() you must understand
    "structures," and we won't be describing those until the next chapter.
    Keep in mind that you might want to modify UPPITY.C when you learn how to
    use structures.

Predeclared File Pointers

    When you run any QuickC program, five file pointers are always provided
    for five preopened files. Those file pointers are stdin, stdout, stderr,
    stdaux, and stdprn. (See Table 10-3.) Because these preopened file
    pointers are defined in stdio.h, you must include that header file if you
    want to use them.

    To demonstrate the use of these file pointers, we revised CCOPY.C (the
    "crunch-and-copy program") to produce the CCOPY2.C program (Listing
    10-4). This revision checks for the presence of a second (output)
    filename. If it is missing, fputs() directs the output to stdout (your

    Table 10-3 QuickC's Preopened File Pointers
    stdin      The standard input. Your keyboard viewed as a file. Also, input
                to your program provided by redirection using <file from the
                MS-DOS command line.
    stdout     The standard output. Your screen viewed as a file. Also, output
                to disk files provided by redirection using >file from the
                MS-DOS command line.
    stderr     The standard error output. Always your screen. This file
                pointer is unaffected by redirection from the MS-DOS command
    stdaux     The standard auxiliary. Usually your serial port or COM1. This
                file pointer provides easy access to your modem.
    stdprn     The standard printer output. Usually your parallel port or PRN.
                This file pointer provides an easy way to generate hard copy
                from within a QuickC program.

    /* ccopy2.c  -- copies a file, cutting blank lines and  */
    /*              leading space from lines of copy        */

    /*          Modified to demonstrate stdout and stderr   */

    #include <stdio.h>        /* for FILE, BUFSIZ, NULL */
    #include <ctype.h>        /* for iswhite()          */

    main(argc, argv)
    int argc;
    char *argv[];
        FILE *fp_in, *fp_out;
        char buf[BUFSIZ];
        char *cp;

        if (argc < 2)
            fprintf(stderr, "usage: ccopy2 infile {outfile}\n");
        if ((fp_in = fopen(argv[1], "r")) == NULL)
            fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);
        if (argc == 3)
                if ((fp_out = fopen(argv[2], "w")) == NULL)
                    fprintf(stderr, "\"%s\": Can't open.\n", argv[2]);
            fp_out = stdout;

        while (fgets(buf, BUFSIZ, fp_in) != NULL)
            cp = buf;
            if (*cp == '\n')    /* blank line */
            while (isspace(*cp))
            if (*cp == '\0')    /* empty line */
            if (fputs(cp, fp_out) == EOF)
                fprintf(stderr, "Error writing.\n");
        if (! feof(fp_in))        /* error reading? */
            fprintf(stderr, "\"%s\": Error reading.\n", argv[1]);

    Listing 10-4.  The CCOPY2.C program.

Formatted File I/O with fprintf() and fscanf()

    CCOPY2.C didn't print error messages with printf(); instead, it used the
    file-oriented version of printf(), called fprintf(), to send error
    messages to stderr, which is always your screen. This ensures that you
    will always see error messages, even when the program is printing its
    output to a file.

    C's file-oriented counterparts to printf() and scanf() are called
    fprintf() and fscanf(). They are identical to their nonfile brethren, with
    one exception: Each requires a file pointer as its first argument, as

    fprintf(fp_out, control, args ...);
            └─┬──┘  └───────┬────────┘
                │             └─────────────────────────── Same as printf()
                └─────────────────────────────────────────── A file pointer

    fscanf(fp_in, control, addresses ...);
            └─┬─┘  └─────────┬───────────┘
                │              └──────────────────────────── Same as scanf()
                └──────────────────────────────────────────── A file pointer

Random Access with fseek()

    Sophisticated applications, such as databases, must be able to move around
    in files (recall that files are continuous streams of bytes) reading and
    writing selected portions. The fseek() function lets a program access any
    file element by determining the position of the next read or write in a
    file, as follows:

    fseek(fp, offset, origin)
        │     │   │      └───────────────────────────────────── From where
        └──┬──┘   └───────────────────────────────── How far to reposition
            └───────────────────────────────────────────────── File pointer

    The offset, in bytes, tells fseek() how far to move in the file, and it
    must be type long. The origin determines where to measure offset from; it
    can be any one of three values──the beginning, current position, or end of
    the file. (See Table 10-4.)

    If fseek() cannot reposition in a file, it returns the value -1L (the L is
    needed because fseek() returns the type long). If fseek() is successful,
    it returns the new position in the file, measured in bytes from the
    beginning of the file.

    Table 10-4 origin Positions for fseek()
    SEEK_SET       From the beginning of the file; offset must always be
    SEEK_CUR       Relative to the current position. A negative offset moves
                    toward the beginning of the file; a positive offset moves
                    toward the end of the file. (You can move beyond the end of
                    the file, thus enlarging the file.)
    SEEK_END       From the end of the file; offset can be positive or
                    negative. Movement is the same as SEEK_CUR, but relative to
                    the end of the file.

    The PHONE.C program (Listing 10-5 on the following page) is a miniature
    telephone number database. When run without command-line arguments, it
    asks you for numbers to add to its database file. Run with a command-line
    argument, it searches for an entry that matches the argument and prints
    the data it finds.

    /* phone.c  -- a telephone number mini-database that  */
    /*             demonstrates fseek()                   */

    #include <stdio.h>        /* for FILE, BUFSIZ, NULL */

    #define MAXL (128)
    char Name[MAXL];
    char Number[MAXL];
    char File[] = "C:\\TMP\\PHONE.DB";
    int  Count;
    FILE *Fp;
    int  Distance = (MAXL * MAXL);

    main(argc, argv)
    int argc;
    char *argv[];
        if (argc == 1)


    Find(char *str)
        int i;

        if ((Fp = fopen(File, "r")) == NULL)
            fprintf(stderr, "\"%s\": Can't Read\n", File);
            exit (1);
        if (fread(&Count, 1, sizeof(int), Fp) != sizeof(int))
            fprintf(stderr, "\"%s\": Error Reading\n", File);
            exit (1);
        for (i = 0; i < Count; i++)
            fread(Name, 1, MAXL, Fp);
            fread(Number, 1, MAXL, Fp);
            if (ferror(Fp))
                fprintf(stderr, "\"%s\": Error Reading.\n", File);
                exit (1);
            if (strcmp(*str, *Name) == 0)
                printf("Name: %s\n", Name);
                printf("Number: %s\n", Number);
        fprintf(stderr, "\"%s\": Not in database.\n", str);

        if ((Fp = fopen(File, "r+")) == NULL)
        else if (fread(&Count, 1, sizeof(int), Fp) != sizeof(int))
            fprintf(stderr, "\"%s\": Error Reading\n", File);
            exit (1);
        printf("Name: ");
        if (gets(Name) == NULL || *Name == '\0')
        printf("Number: ");
        if (gets(Number) == NULL || *Number == '\0')
        if (fseek(Fp, (long)(Distance * Count), SEEK_CUR) != 0)
            fprintf(stderr, "\"%s\": Error Seeking.\n", File);
            exit (1);
        fwrite(Name, 1, MAXL, Fp);
        fwrite(Number, 1, MAXL, Fp);
        if (ferror(Fp))
            fprintf(stderr, "\"%s\": Error Writing.\n", File);
            exit (1);
        if (fseek(Fp, 0L, SEEK_SET) != 0)
            fprintf(stderr, "\"%s\": Error Seeking.\n", File);
            exit (1);
        if (fwrite(&Count, 1, sizeof(int), Fp) != sizeof(int))
            fprintf(stderr, "\"%s\": Error Writing\n", File);
            exit (1);
        if ((Fp = fopen(File, "w+")) == NULL)
            fprintf(stderr, "\"%s\": Can't Create\n", File);
            exit (1);
        Count = 0;
        if (fwrite(&Count, 1, sizeof(int), Fp) != sizeof(int))
            fprintf(stderr, "\"%s\": Error Creating\n", File);
            exit (1);

    Listing 10-5.  The PHONE.C program.

    The PHONE.C program might seem more complex than it really is. We included
    many error-checking routines to prevent the user from making careless
    errors. Note how the program checks the first character of each input line
    for a zero character (*Name == '\0'). This shows that the user pressed
    Enter without typing any information.

Moving with the rewind() Function

    Moving to the beginning of a file (rewinding) is so common in C programs
    that the standard C Library includes a special function to perform that
    task. Called rewind(), it takes a single argument──a file pointer for the
    opened file──and moves the position of the next read or write to the
    beginning of the file. Consider the following:


    rewind() returns no value and therefore gives no indication of failure.
    Other than this difference, however, the above rewind() is identical to
    the following fseek():

    fseek( fp, OL, SEEK_SET)
                │     └────────────────────── Move from beginning of file
                └────────────────────────────────── Offset must be a long

Determining Position in a File with ftell()

    Moving through a file with fseek() often requires that you first know your
    current position in the file. When you pass the ftell() function a file
    pointer, it returns your present position in that file. That position, a
    long value, is the measure in bytes from the beginning of the file.
    Consider the following:

    if ((pos = ftell(fp)) == -1L)
            /* can't find position */
    /* current position is pos bytes from beginning */

    Used in that way, ftell() is identical to the following fseek() call:

    if ((pos = fseek(fp, 0L, SEEK_CUR)) == -1L)
            /* can't find position */
    /* current position is pos bytes from beginning */

    As you progress in learning C, you will find need for functions that we
    have not covered in our discussions. For a complete summary of top-level
    (stream) I/O routines, refer to Section 4.8 of the Microsoft QuickC
    Run-Time Library Reference.

Mid-level (Unbuffered) File I/O

    Most of the top-level (buffered) stream file input/output functions have
    mid-level, unbuffered counterparts that permit direct access to disk
    files. Because they do not buffer data, they are frequently faster and
    more efficient, often allowing disk files to be read directly into a
    program's memory. (The top-level fread() function, for example, actually
    calls the mid-level read() to do its work.)

    One disadvantage of the unbuffered routines is that they offer only the
    most basic of services. Although these routines offer a read() and a
    write(), there are no corresponding mid-level versions of fgets(),
    fputs(), fscanf(), fprintf(), or fgetc(). Another disadvantage is that you
    cannot use unbuffered functions in the same program that uses calls to
    top-level functions. If you mix them, as shown in Figure 10-3, you risk
    losing synchronization of data. That is, if you first call fgetc(), then
    call read(), the read() will not begin with the next byte following the
    fgetc(). The fgetc() reads and buffers 512 bytes from the file, then it
    returns the first one of those buffered bytes. The call to read(),
    however, reads a single byte directly from the disk.

                                    512-byte buffer
                                │ │    Now is the      │  'N'
                                │ │    time...         ├──────────┐
    fgetc() reads and buffers  │ └───────────────────┘          │
    512 bytes at a time ───────┤           │                     │
                                │ ┌─────────┴──────────┐          │
        ┌────────────────┐      │ │     Top-level      │        ┌─▼─────────────
        │                ├──────┼►│     fgetc()        │        │
        │ File: "Now is  │      └─└────────────────────┘        │    Your
        │  the time..."  │                                      │    program
        │                │        ┌────────────────────┐        │
        │                ├───────►│     Mid-level      │        └──────────────
        └────────────────┘        │     read()         ├──────────┘
                                └────────────────────┘  'N'
                read() reads ─────┘
                only one byte

    Figure 10-3. Data synchronization is lost when you mix buffered with
    unbuffered file I/O routines.

Opening a File with open()

    Unlike fopen(), open() returns its identifying value as a simple integer.
    This value, called a "file descriptor," is later passed to all other
    mid-level routines. To use the open() function, you must specify #include
    <fcntl.h> (not <stdio.h>, as you would with fopen()), as follows:

    #include <fcntl.h>
    int fd;──────────────────────────────────────────────The file descriptor
    if ((fd = open(filename, oflag)) < 0)
            /* handle error here */

    The file descriptor, fd, is type int. The first argument, filename, is a
    string or the address of a string, and the second, oflag, is an int that
    supplies open() with a file activity (read, write, append, create) and a
    file mode (text or binary). The values for oflag are defined in fcntl.h,
    and their meanings are listed in Table 10-5. Note that you can combine
    oflag values by using the bitwise OR operator (|). For example, the
    following declaration opens the file TEST.EXE for reading in binary mode:

    fd = open("TEST.EXE", O_BINARY | O_RDONLY)
                                │    │     └────────────── For reading only
                                │    └─── Combined with bitwise OR operator
                                └────────────────────── Open in binary mode

    If it fails, open() returns a negative integer value. Thus, all file
    descriptor values are greater than or equal to zero.

    Table 10-5 Values for oflag Declared in fcntl.h
    Value                                Description
    O_RDONLY                             Accesses as read-only.
    O_RDWR                               Accesses as read-write.
    O_WRONLY                             Accesses as write-only.
    O_BINARY                             Sets mode for a binary file.
    O_TEXT                               Sets mode for a text file.
    O_APPEND                             Opens for appending.
    O_CREAT                              Creates file if it doesn't exist.
    O_EXEL                               Returns error if file already exists.
    O_TRUNC                              Truncates existing file to zero

    Of the possible values for oflag, you must use one of the first three
    activities in Table 10-5, (O_RDONLY, O_RDRW, or O_WRONLY); all others in
    the table are optional and can be added using bitwise OR. Unless specified
    otherwise, the mode is set for a text file; reads and writes begin at the
    start of the file; the file is not created if it doesn't exist; and the
    file is not truncated.

    If you combine the O_CREAT value with O_RDWR or O_WRONLY to create a file,
    open() requires a third argument called pmode ("permissions" mode). Use
    the argument as follows:

    fd = open(filename, oflag, pmode);
                            │    └The permissions of the newly created file
                            └──────────────────(O_RDWR or O_WRONLY|O_CREAT)

    The possible values for pmode, listed in Table 10-6, determine whether
    the created file will be a read-only file, a write-only file, or a
    readable and writable file. (You must combine the two defined pmode values
    with a bitwise OR operator to create a readable and writable file.)
    Because pmode values are defined in sys\stat.h>, you must specify the
    following include files to create a file with open():

    #include <fcntl.h>──────────────────────────────────────For oflag values
    #include <sys\types.h>────────────────────────────────────────For stat.h
    #include <sys\stat.h>───────────────────────────────────For pmode values

    Note that #include <sys\types.h> must always precede #include <sys\stat.h>
    because the first contains the definitions needed by the second.

    With MS-DOS, you cannot create a file that is write-only. Because all
    files are always readable, you can omit the S_IREAD value for pmode. (If
    you do use the value, MS-DOS ignores it.)

    The following example is a complete call to open(), including all the
    #include directives:

    #include <fcntl.h>
    #include <sys\types.h>
    #include <sys\stat.h>

    int fd;


    This example opens a file named TEST.EXE in binary mode for reading and
    writing. It creates the file if it doesn't exist, and truncates it if it

    Table 10-6 Values for pmode from <sys\stat.h>
    S_IWRITE                             Creates a writable file.
    S_IREAD                              Creates a readable file.

Closing a File with close()

    Just as fclose() closes a file based on a file pointer, the unbuffered
    close() library function closes a file based on a file descriptor, as

    if (close(fd) != 0)
            /* handle error closing here */

    A successfully executed close() returns a zero value; any nonzero return
    value indicates an error.

    When your program exits, QuickC closes all files opened with the mid-level
    open(). Because you can have only 20 files open at one time, you should
    close files inside your program. Closing a file with close() frees that
    file's file descriptor for reuse.

Writing to a File with write()

    The write() function is used to write to files. It is simpler to use than
    the top-level fwrite() function because it requires only three arguments,
    as in the following:

    write(fd_out, buf, bytes)
            └──┬─┘   │     └────────────────────── Number of bytes to write
                │     └───────────────────── Where to write those bytes from
                └───────────── File descriptor for a file opened for writing

    The expression buf is the address in memory of the first byte that you
    want to write to the file. That address can be any address expression, but
    it is usually the address of an array. The final argument, bytes,
    represents the number of characters you want to write to the file.

    The write() function normally returns the number of bytes written (the
    same value as bytes). If write() fails, however, it returns a smaller or
    negative number.

    The SCRSAVE.C program (Listing 10-6) demonstrates one way to use open()
    and write(). It copies the contents of the text screen into a local
    buffer, which is then written to a disk file. (The program will not
    overwrite an existing file.)

    /* scrsave.c  --  demonstrates write() by saving the */
    /*                text screen to a file              */

    #include <stdio.h>        /* for stderr              */
    #include <fcntl.h>        /* for O_CREAT | O_BINARY  */
    #include <sys\types.h>    /* for stat.h              */
    #include <sys\stat.h>     /* for S_IREAD | S_IWRITE  */

    #define SCRCHARS  (25 * 80)
    int Buf[SCRCHARS];
    main(argc, argv)
    int argc;
    char *argv[];
        int *cp, *ep, fname[16];
        int far *sp;
        int fd_out, bytes;

        if (argc != 2)
            fprintf(stderr, "usage: scrsave file\n");
        if (strlen(argv[1]) > 8)
            fprintf(stderr, "\"%s\": Filename too long.\n", argv[1]);
        strcpy(fname, argv[1]);
        strcat(fname, ".SCR");
        if (access(fname, 0) == 0)
            fprintf(stderr, "\"%s\": Won't overwrite.\n", fname);
        if ((fd_out = open(fname, O_WRONLY | O_CREAT | O_BINARY,
                            S_IREAD | S_IWRITE)) < 0)
            fprintf(stderr, "\"%s\": Can't create.\n", fname);
        /* Copy the screen into a near buffer. */
        ep = &Buf[SCRCHARS - 1];
        cp = Buf;
        /* use 0xB8000000 for EGA or VGA */
        sp = (int far *)(0xB0000000);
        for (; cp < ep; ++cp, ++sp)
            *cp = *sp;
        /* Write it. */
        bytes = write(fd_out, Buf, SCRCHARS * 2);
        if (bytes != SCRCHARS * 2)
            fprintf(stderr, "\"%s\": Error writing.\n", fname);

    Listing 10-6.  The SCRSAVE.C program.

    Note that we copy the screen rather than write it directly because write()
    expects a normal pointer, whereas accessing the screen requires a far

Reading a File with read()

    Use the read() function to read from files. It is a simpler function to
    use than fread() because it takes only three arguments, as follows:

    read(fd_in, buf, bytes)
            │     │     └───────────────────────── Number of bytes to read
            │     └──────────────────────────── Where to place those bytes
            └─────────────── File descriptor for a file opened for reading

    In this example, buf is either an array or the address of allocated
    memory. Be sure it is large enough to hold the number of bytes specified
    by the argument bytes, however, because the compiler does not check this
    for you.

    If the call to read() is successful, it returns the same value as bytes.
    If it returns a smaller value, then that value represents the number of
    bytes left in the file. A zero return value signifies the end of the file,
    and a -1 return value shows that a read error occurred.

    The SCRREST.C program (Listing 10-7) reads a file, copying as much as a
    screenful of what it reads to text-screen memory. It works with any file
    type, but reading files created with SCRSAVE.C (Listing 10-6) is its most
    useful application. Before you run the program, pull down the Debug menu
    and activate Screen Swapping On.

    /* scrrest.c  --  demonstrates read() by restoring */
    /*                text screen from any file        */

    #include <stdio.h>        /* for stderr              */
    #include <fcntl.h>        /* for O_RDONLY | O_BINARY */

    #define SCRCHARS  (25 * 80)
    int Buf[SCRCHARS];

    main(argc, argv)
    int argc;
    char *argv[];
        int *cp, *ep;
        int far *sp;
        int fd_in, bytes;

        if (argc != 2)
            fprintf(stderr, "usage: scrrest file.scr\n");
        if ((fd_in = open(argv[1], O_RDONLY | O_BINARY)) < 0)
            fprintf(stderr, "\"%s\": Can't open to read.\n", argv[1]);
        /* Read it. */
        bytes = read(fd_in, Buf, SCRCHARS * 2);
        if (bytes < 0)
            fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
        if (bytes == 0)
            fprintf(stderr, "\"%s\": Empty File.\n", argv[1]);
        /* Copy the buffer to screen memory. */
        ep = &Buf[bytes / 2];
        cp = Buf;

        /* use 0xB8000000 for EGA or VGA */
        sp = (int far *)(0xB0000000);
        for (; cp < ep; ++cp, ++sp)
            *sp = *cp;

    Listing 10-7.  The SCRREST.C program.

Positioning with lseek()

    The unbuffered lseek() function lets a program position its next read or
    write to begin anywhere in a file. Almost identical to the buffered
    fseek(), lseek() takes a file descriptor as its first argument, rather
    than a file pointer. Therefore, use the lseek() function as follows:

    #include <io.h>      /* defines lseek()    */
    #include <stdio.h>   /* for origin, etc.   */

    long newpos, offset = 100L;
    int  fd;

    newpos = lseek(fd, offset, origin);
        │            │      │      └─── From where (current, begin, or end)
        │            │      └───────────────── Move this many bytes forward
        │            └────────────────────── In this file (file descriptor)
        └───────────────────────────────────────────── New position in file

    In this example, fd is a file descriptor for a file previously opened with
    open(). The second argument, offset, is the number of bytes to move in the
    file and must be of the type long. If offset is negative, you move toward
    the beginning of the file. The last argument, origin, can be one of the
    three possible definitions that specify where the move begins. These
    definitions are the same as those used by fseek(), which were mentioned in
    Table 10-4 on p. 305. Also, as with fseek(), you must specify #include
    <stdio.h> to access those definitions.

    After a successful repositioning, lseek() returns the new position in the
    file. A return value of -1L indicates an error. (Note that lseek() returns
    the type long.)

    The VIEW.C program (Listing 10-8) is a simple file-viewing program that
    illustrates how to use lseek() to move through a file. Pressing + moves
    you forward in the file, pressing - moves you backward, and typing q or Q
    ends the program.

    /* view.c  --  demonstrates lseek() by displaying  */
    /*             a file and moving around in it      */

    #include <fcntl.h>        /* for open()         */
    #include <stdio.h>        /* for SEEK_CUR, etc. */

    #define HUNK 512
    #define MOVE 512L

    main(argc, argv)
    int argc;
    char *argv[];
        char ch, buf[HUNK];
        long position = 0L;
        int  bytes, eofflag = 0, fd_in;

        if (argc != 2)
            fprintf(stderr, "Usage: view file\n");

        if ((fd_in = open(argv[1], O_RDONLY)) < 0)
            fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);

        for (;;)
            bytes = read(fd_in, buf, HUNK);
            if (bytes == 0)
                if (! eofflag)
                    fprintf(stderr, "\n<<at end of file>>\n");
            else if (bytes < 0)
                fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
                eofflag = 0;
                position = lseek(fd_in, 0L, SEEK_CUR);
                if (position == -1L)
                    fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);
                Print(buf, bytes);
                    ch = getch();
                    if (ch == 'q' || ch == 'Q')
                    } while (ch != '+' && ch != '-');

                if (ch == '-')
                    position = lseek(fd_in, -2 * MOVE, SEEK_CUR);
                    if (position == -1L)
                        fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);

    Print(char *buf, int cnt)
        int i;

        for (i = 0; i < cnt; ++i, ++buf)
            if (*buf < ' ' && *buf != '\n' && *buf != '\t')
                printf("^%c", *buf + '@');

    Listing 10-8.  The VIEW.C program.

Finding Current Position with tell()

    Notice that VIEW.C finds the current position in the viewed file with the

    position = lseek(fd_in, 0L, SEEK_CUR);

    Because the need to know the current position is so common, the QuickC
    library provides the tell() function. Similar to the top-level ftell()
    routine, tell() takes a single argument, a file descriptor, and returns
    the current position in the file associated with that file descriptor.
    That position is a type long measure in bytes from the beginning of the
    file. If tell() fails for any reason, it returns a value of -1L.

The File System

    Not only do programs read and write to files, they often need to manage
    the file system as a whole. By the file system, we mean the MS-DOS
    directory hierarchy, the organization of directories, and the naming of
    directories and files. For example, your program might need to create or
    remove a directory or file, or relocate in the directory hierarchy (change
    the working directory), or create unique temporary filenames. In this
    section we discuss the file system and the C Library routines that let you
    manipulate it. We also warn you of possible pitfalls and present a few
    routines that let you handle errors gracefully.


    MS-DOS does not permit you to use fopen() or open() to open a directory.
    You can, however, create and remove directories or establish any directory
    as your current working directory. The routines for handling directories
    are listed in Table 10-7. All of the routines require that you first
    specify #include <direct.h>, which contains their declarations.

    The directory-handling functions chdir(), mkdir(), and rmdir() take a
    single argument──a string or the address of a string that specifies a full
    pathname (such as C:\TMP\JUNKDIR), or a directory name relative to the
    current working directory (such as JUNKDIR). All three return an integer 0
    if they are successful; otherwise, they return a -1. Consider, for
    example, the code fragment on the next page.

    Table 10-7 The Directory-Handling Library Functions
    chdir(path)    Changes the current working directory to path. Returns 0 if
    mkdir(path)    Creates a new directory named path. Returns 0 if
    rmdir(path)    Removes the directory whose name is path. Returns 0 if
    getcwd(buf, n) Places the full pathname of your current working directory
                    into the char buffer buf of length n. Returns NULL if an
                    error occurs.

    #include <direct.h>

    if (chdir("C:\\TMP") != 0)
            /* chdir failed, so exit */
    if (mkdir("JUNKDIR") != 0)
            /* mkdir failed, so exit */
    if (rmdir("JUNKDIR") != 0)
            /* rmdir failed, so exit */

    The #include <direct.h> directive provides definitions for the three
    routines that follow it. The chdir() function changes the current working
    directory to C:\TMP. (Note that in C you must use a double backslash to
    produce a single backslash.) Next, inside C:\TMP, the program uses mkdir()
    to create a new subdirectory called JUNKDIR. The final call to rmdir()
    removes that same subdirectory.

    The last routine in Table 10-7, getcwd() ("get current working
    directory"), takes two arguments and returns the address of a string. You
    can call this function using one of two forms. In the following form:

    #include <direct.h>
    #include <stdio.h>   /* for NULL */

    char buf[512];
    if (getcwd(buf, 512) == NULL)
            /* couldn't get current working directory */

    The getcwd() function is passed the address of a char buffer, buf, into
    which it places the name of the current working directory. The length 512
    is the number of bytes in the buffer. (Remember that the buffer must be
    large enough for both the name and a terminating '\0'.)

    The second form for calling getcwd() is as follows:

    #include <direct.h>
    #include <stdio.h>   /* for NULL */

    char *name;
    if ((name = getcwd(NULL, 0)) == NULL)
            /* couldn't get current working directory */

    This form passes getcwd(), the special zero address NULL, and a length of
    zero. This causes getcwd to use malloc() to allocate enough space for the
    name of the current working directory name (plus 1 for the terminating
    '\0'), to copy that name into the newly allocated space, and to return the
    address of that space. Both forms of getcwd() return NULL if the operation

    The DIRX.C program (Listing 10-9) demonstrates all four of the
    directory-handling subroutines. It first creates a subdirectory in the
    current directory, then relocates to that subdirectory and creates a
    sub-subdirectory. Finally, it returns to the original directory and
    attempts to remove the first subdirectory it created. It fails at this
    point because it is illegal to remove a subdirectory that is not empty. If
    you run the program again, it will fail immediately──it cannot execute the
    first mkdir() because a directory with that name already exists.

    /* dirx.c  --  directory examples  */

    #include <direct.h>
    #include <stdio.h>

    #define SUBDIR "SUBDIR"
    #define SUBSUBDIR "SUBSUB"

        char *current_dir;
        void Err();

        if ((current_dir = getcwd(NULL, 0)) == NULL)
            Err("getcwd()", "Can't get current directory.");

        if (mkdir(SUBDIR) != 0)
            Err(SUBSUBDIR, "Can't make directory.");

        if (chdir(SUBDIR) != 0)
            Err(SUBDIR, "Can't cd into directory.");

        if (mkdir(SUBSUBDIR) != 0)
            Err(SUBSUBDIR, "Can't make directory.");

        if (chdir(current_dir) != 0)
            Err(SUBDIR, "Can't cd back to.");

        if (rmdir(SUBDIR) != 0)
            Err(SUBDIR, "Can't remove directory.");


    void Err(char *what, char *msg)
        fprintf(stderr, "\"%s\": %s\n", what, msg);
        exit (1);

    Listing 10-9.  The DIRX.C program.

Manipulating Files by Name

    Several standard C Library routines make it easy for you to remove and
    rename files and also to create unique filenames from within a program.
    These routines (listed in Table 10-8) are useful in databases, compilers,
    games, and any other program that needs to manipulate files.

    The routines unlink() and remove() are identical. Each takes a single
    argument──the address of a string──and erases (removes from the disk) the
    file whose name is specified in that string. The filename that you specify
    can either be a full path such as C:\TMP\JUNK, which removes the file JUNK
    from the directory C:\TMP, or it can be a relative pathname such as JUNK,
    in which case the called routine removes the file JUNK from the current
    working directory.

    The rename() function can do more than merely rename files. It can rename
    directories and move files from one directory to another (but not from one
    disk to another). Consider the following example, in which JUNK is a file
    and DIR1 and DIR2 are directories:

    rename("JUNK", "OLDJUNK");─────────────────────────────────Rename a file
    rename("DIR1\\JUNK", "DIR2\\JUNK");──────────────────────────Move a file
    rename("DIR1", "OLDDIR1");────────────────────────────Rename a directory

    The first line renames the file JUNK in the current working directory as
    OLDJUNK; the second line moves the file JUNK in the subdirectory DIR1 into
    the subdirectory DIR2. Note that you could have renamed JUNK during the
    move. Also remember that, in C, you must use two backslashes to produce a
    single backslash.

    The third line of the above example renames the directory DIR1 as OLDDIR1.
    It is important to note that directories, unlike files, cannot be moved.

    Table 10-8 Routines That Manipulate Files by Name
    unlink(path)   Removes (erases) the file whose name is specified by path.
                    Returns 0 if the call is successful.
    remove(path)   Same as unlink().
    rename(old,    Renames the file old, giving it the new name new. Also
    new)           allows the renaming of directories. Files can be moved with
                    this routine. Returns 0 if successful.
    mktemp(tmplt)  Fills out the template tmplt with a filename that does not
                    already exist.

    The mktemp() function generates a unique filename that is guaranteed not
    to exist on your disk. Use it as follows:

    #include <io.h>     /* defines mktemp() */
    #include <stdio.h>  /* for NULL */

    static char template[] = "C:\\TMP\\XXXXXX";

    if (mktemp(template) == NULL)
            /* No unique name possible */

    First we specify #include <io.h> for the definition of mktemp(). In that
    header file, mktemp() is defined as returning the address of a string
    (that is, char *). We also must use #include <stdio.h> to define NULL,
    which mktemp() returns if it fails.

    The template passed to mktemp() must take the form baseXXXXXX; that is, it
    may be any prefix, path, or part of a filename, ending with six X
    characters. The mktemp() function replaces the X characters with one
    alphanumeric character followed by five digits, thus forming a unique name
    (one that does not already exist on the disk), such as A00000.

    The FMENU.C program (Listing 10-10) uses all of these routines within a
    small file-handling menu program. It enables you to rename/move a file or
    directory, remove a file, or create a unique file. You can use FMENU.C as
    the core of your own programs that let the user control files without
    exiting to MS-DOS. Before you compile and run FMENU.C, follow the steps
    outlined in the box on page 325, "Making New Library Routines Available
    to QuickC."

    /* fmenu.c  --  demonstrates file renaming, etc. */

    #include <direct.h>
    #include <stdio.h>
    #include <string.h>

    #define MAXPATH (80)
    char From_name[MAXPATH],

    int Input(char *prompt, char buf[])
        printf("%s: ", prompt);
        if (gets(buf) == NULL || *buf == '\0')
            return (0);
        return (1);
    void Rename(void)
        if (!Input("From", From_name)) return;
        if (!Input("To", To_name)) return;
        if (rename(From_name, To_name) != 0)
            printf("Renamed: \"%s\" -> \"%s\"\n",
                    From_name, To_name);
    void Remove(void)
        if (!Input("Remove", From_name)) return;
        if (!Input("Are You Sure", To_name)) return;
        if (*To_name != 'y' && *To_name != 'Y')
        if (remove(From_name) != 0)
            printf("Removed: \"%s\"\n", From_name);
    void Maketemp(void)
        if (!Input("In What Directory", From_name))
        (void)strcat(From_name, "\\XXXXXX");
        if (mktemp(From_name) == NULL)
            printf("Can't create a unique name.\n");
            printf("Created: \"%s\"\n", From_name);
    void Quit(void)
        if (!Input("Are You Sure", From_name))
        if (*From_name != 'y' && *From_name != 'Y')

        static void (*doit[])() = {Rename, Remove, Maketemp, Quit};
        int ch;
        while (1)
            printf("1) Rename/move a file or rename a directory.\n");
            printf("2) Remove a file.\n");
            printf("3) Make a unique temporary file.\n");
            printf("4) Quit.\n");
            printf("Select: ");

                ch = getchar();
                } while (ch < '1' || ch > '4');
            getchar();    /* gobble trailing newline */
            printf("%c\n\n", ch);
            ch -= '1';

    Listing 10-10.  The FMENU.C program.

    FMENU.C uses a technique we discussed in Chapter 8──an array of pointers
    to functions. Each menu choice corresponds to a function in that array,
    and each of those functions utilizes a different routine for file
    manipulation. Note that FMENU.C contains an error-printing routine you
    haven't seen before──perror().

Printing Clear and Meaningful Diagnostics with perror()

    All C programs use a system-defined global variable called errno, which is
    set and cleared with each system or I/O call. A standard C Library routine
    called perror() prints an appropriate error message based on the current
    value in errno. For example, suppose an fopen() for reading a file named
    JUNK fails because the file didn't exist. In that case QuickC sets errno
    to 2, and perror(), when called as


    prints the following to the standard error output:

    JUNK: No such file or directory

    Using perror() helps your program generate clearer and more meaningful
    diagnostic messages. However, remember to call perror() immediately after
    a library routine returns an error. If you call another library routine
    before perror(), it might change errno and cause perror() to print an
    incorrect message. For example,

    if((fp = fopen(fname, "rb")) == NULL)
        fprintf(stderr, "Program Aborted because\n");

    does not work because the fprintf() preceding perror() succeeds and thus
    sets errno to zero, causing perror() to print the incorrect message
    Undefined error.

Advanced Error Handling

    A program that can recover from any error is called "robust." Robust
    programs are not merely carefully written programs──they are programs that
    include library routines for handling all abnormal conditions and that
    issue clear diagnostic messages to the user. Table 10-9 lists the most
    useful routines for handling abnormal conditions.

    Table 10-9 Abnormal-Condition Handlers and Diagnostic Routines
    signal()       Traps errors that can terminate a program, such as Ctrl-C
                    and floating-point exceptions.
    setjmp()       Prepares for a jump between functions.
    longjmp()      Executes a jump between functions.

    Making New Library Routines Available to QuickC
    To compile and run the FMENU.C program successfully, perform the following
    steps to add a few routines to QuickC that are not normally available.

    1.  Create the following program and save it using F.C as its filename.

        #include <direct.h>

    2.  Exit QuickC and run the following MS-DOS command line (ignore any
        warnings about actual arguments):

        qcl /c /am f.c

    3.  Run the following command line to create an add-on Quick Library:

        link c:\lib\quicklib.obj+f.obj,f.qlb,,/q;

        where c:\lib is the location for your QuickC libraries as determined
        when you ran SETUP.

    4.  Rerun QuickC with the following:

        qc /lf

        For a more detailed explanation of Quick Libraries, see Chapter 12 of
        this book and Section 10.1 of your Microsoft QuickC Programmer's


    Signals are conditions that cause a program to terminate prematurely. The
    signals for MS-DOS are listed in signal.h: They include Ctrl-C,
    Ctrl-Break, and floating-point errors such as division by zero. A text
    editor is an example of a program that should not terminate if one of
    these conditions occurs. The user might, for example, be editing a
    temporary copy of a file──you would want to write a user's changes to disk
    before exiting, no matter what.

    To handle errors such as these, use the signal() function as follows:

    #include <signal.h>

    status = signal(sig, funct);
        │             │     └────── Function address or SIG_IGN or SIG_DFL
        │             └──────────── One of the signals defined in signal.h
        └──────────────────────────────────────────────── SIG_ERR on error

    Error-handling Philosophies: BASIC vs C
    Most versions of BASIC build an error-handling mechanism into the language
    in the form of the ON ERROR ... GOTO label construct. When an error is
    encountered, control switches to the appropriate label or line number.
    Although you can turn this facility on and off, you don't have fine
    control of it.

    To review the situation in C, each function is responsible for reporting
    errors back to its caller. This procedure is more flexible than that used
    by BASIC, but it admits some inconsistencies. Functions that return
    pointers (such as fopen(), which returns a pointer to the file opened)
    often return a null pointer, which can be tested against the predefined
    value NULL. Other functions return the value -1 to indicate an error and
    store the specific error number in the global variable errno, using
    error-number values defined in the include file errno.h. Still other
    functions cannot return error values because no values are reserved for
    that purpose: All values might conceivably be returned by normal
    operation. You can, however, use the function ferror() to find out if any
    error occurred during input or output to a particular file. If you are not
    sure how a particular function handles error conditions, a quick way to
    find out is to use QuickC's on-line help facility discussed earlier in
    this book.

    In return for the greater flexibility C provides, you must explicitly test
    for an error (usually by putting the function call in an if or while
    statement or by calling ferror()) and then call any error-handling

    The signal mechanism (discussed in this section) provides an additional,
    UNIX-compatible way to handle error conditions reported by the operating
    system. This mechanism is similar to the BASIC mechanism in that it
    establishes a global connection between a particular error condition and
    an error-handling function.

    We specify #include <signal.h> for definitions of signal(), its return
    value, and all of the possible values for sig. The signal() function takes
    two arguments. The first specifies the type of error, the values of which
    are listed in <signal.h> and summarized for MS-DOS in Table 10-10 on the
    following page. The second argument is the name (or address) of a function
    to be called if sig occurs, or one of the two predefined values: SIG_IGN
    (ignore this signal) or SIG_DFL (resume the default action, that is,
    terminate the program). Figure 10-4 illustrates the use of signal().

        #include <signal.h>
        int Sigflag = 0;    /*  global  */
        main ()
            extern int Funct ();
            1  if  (signal (SIGINT, Funct) == SIG_ERR)
                printf ("Signal () failed. \n");
                exit  (0);
            for (;;)      /* forever      */
    ┌───────────► printf ("Waiting For Ctrl-C\n");
    │      5                                   ◄───2 3
    │             if (Sigflag != 0)                  │
    │             break 6                            │
    │             }                                  │
    │    } 7                                         │
    │                                                │
    │    Funct ()◄───────────────────────────────────┘
    │    {
    │        ++Sigflag;
    │    } 4

    1  Calling siganl() sets up the program to handle the Ctrl-C interrupt.

    2  User presses Ctrl-C (or Ctrl-Break) during the perpetual for loop which
        is printing Waiting for Ctrl-C at the time.

    3  Funct() is immediately called and increments Sigflag.

    4  Funct() returns and...

    5  the printf() statement previously interrupted is then executed (again).

    6  We check Sigflag and because it was set to a nonzero value when Funct()
        was called, we exit the perpetual for loop by breaking out of it.

    7  Program ends.

    Figure 10-4. Analysis of signal().

    As a rule, the signal-handling function, Funct(), should not perform any
    I/O operation. Rather than handling the error itself, it should set a
    global flag variable, then return to let the main body of the code handle
    the error. The main program stops, and the signal-handling function,
    Funct(), is called with the signal number sig as its argument. When
    Funct() finishes and returns, the main program continues from the exact
    point at which it stopped.

    Handling signals under MS-DOS is fairly simple because only six signals
    are defined, and only three of those actually do anything. However, if you
    move your code to XENIX or UNIX, you should be prepared to handle thirty
    or more signals, all of which can affect your program.

    Table 10-10 Signals Defined for MS-DOS
    SIGABRT        Abnormal program termination. Terminates the program and
                    exits with a return value of 3.
    SIGFPE         Floating-point exception (such as division by zero or an
                    invalid operation). Terminates the program.
    SIGINT         Interrupt for keyboard. Sent when the user types the key
                    sequence Ctrl-C. Terminates the program.

Jumping Between Functions with setjmp() and longjmp()

    Sometimes when a signal occurs, your program might not be able to continue
    its main body of code. A signal caused by division by zero, for example,
    would result in a completely wrong answer should it continue. For
    situations such as these, when you need to jump to an earlier stage of the
    program, the standard C Library offers two functions: setjmp() and

    setjmp() prepares the program for an eventual jump to an earlier state, as

    #include <setjmp.h>

    jmp_buf env;

    if (setjmp(env) != 0)
        /* We got here because of a longjmp()
            from someplace else */
    /* all prepared for a longjmp() */

    We specify #include <setjmp.h> for the definition of jmp_buf. The variable
    env is declared as the type jump_buf and is the buffer that will hold all
    the information QuickC needs to perform a jump between functions. Next,
    the call to setjmp() prepares for an eventual call to longjmp(). The
    result of this preparation is always 0. When setjmp() returns a 0, you
    know that the program is set up for a later call to longjmp() but that the
    call has not occurred. A later call to longjmp() causes the program to
    call setjmp() again, but this time the call returns a nonzero value.

    Use the longjmp() routine as follows:

    longjmp(env, ret);

    The program calls longjmp() with the same env with which it called
    setjmp() earlier. The ret argument must be a nonzero number because it is
    the value returned by setjmp(). (Figure 10-5 illustrates this

        #include <set jmp.h>
        jmp_buf Env;        /*  global  */
        main ()
    ┌─────► 1 if setjmp (Env)  != 0) ◄─────────────────────┐
    │             {                                        │
    │             printf ("Exiting at A\n");               │
    │             exit (0); A                              │
    │             }                                        │
    │                                                      │
    └───────────► printf ("Calling Foo ()\n");             │
    ┌────────── 2 Foo();                                   │
    │                                                      │
    │             printf ("Exiting at B\n");               │
    │             Exit (0); B                              │
    │    }                                                 │
    │                                                      │
    └──► Foo()                                             │
        {                                                 │
                printf ("In Foo ()\n");                  │
                longjmp (Env, 1); 3 ─────────────────────┘

    1  The first call to setjmp() returns 0, so flow continues with the first
        line after the if.

    2  Foo() is called.

    3  In Foo(), longjmp() returns us to 1. This time, however, setjmp() retur
        1, so we exit at A. Note that B is never reached.

    Figure 10-5. Analysis of setjmp() and longjmp().

Chapter 11  Advanced Data Types

    Many programs, such as databases, spreadsheets, catalogs, and indexes,
    group information in such a way that each item needs to be a different C
    data type. (See Figure 11-1 on the following page.) To facilitate writing
    these programs, C offers a "structure" type──a special array-like form in
    which each element can be a different type.

    These kinds of programs also need to be able to store different types of
    data, at one time or another, at the same place in memory. The street
    number in Figure 11-1, for example, could be numeric, such as 212,
    requiring an integer variable, or it could be alphanumeric, such as 212B,
    requiring a string variable. The C union data type solves this problem by
    letting you store different types at the same place in memory.

    This chapter shows you how to program with structures and unions. It also
    discusses the less frequently used data types enum and bit fields.
    Finally, we'll detail typedef, an alternative to the #define preprocessor
    directive that lets you create new types from old.

    │            char             char             char        │
    │ NAME  _______________  _______________  _______________  │
    │           first             last            middle       │
    │                                                          │
    │             long                   char                  │
    │ ADDRESS  ___________  _________________________________  │
    │          street_num               street                 │
    │                                                          │
    │                char            char           long       │
    │          ________________  _____________  _____________  │
    │                city            state           zip       │
    │                                                          │
    │          int           long                              │
    │ PHONE  (______)  ___________________                     │
    │       area_code        phone                             │
    │                                                          │

    Figure 11-1. To enter the information on an address/phone index card into
    a computer, you need to use different data types organized as a single
    conceptual unit.

Structure──An Array of Different Types

    An obvious limitation of arrays is that the variables in a single array
    must all be of the same type (all char, all int, and so on). However, you
    will frequently need to group variables of different types together so
    that you can manipulate them as a single conceptual unit. The information
    on the index card in Figure 11-1 is a good example. Because all of the
    different "types" of information actually relate to a single person, it is
    more convenient and conceptually sound to place all of that information in
    a single array. Unfortunately, arrays cannot handle different data types.
    To group strings and integers, for example, you must use a structure,
    which can hold any mixture of types, including arrays, pointers, and

    Think of a structure as a special kind of array. However, whereas the
    variables in an array are called elements and are referenced by an offset,
    the variables in a structure are called "members" and are referenced by

    You declare a structure with the C keyword struct. The first step in
    setting up a structure is to declare a pattern, or template, for the
    variables it will contain and to give that pattern a name. A pattern for
    the structure that contains the address-book information in Figure 11-1,
    for example, appears at the top of the next page.

    To declare a structure pattern, follow the keyword struct with the name of
    the pattern (cardstruct). Next, list the variables, or members, of the
    structure between a set of braces. Note that although this list resembles
    a list of variable declarations, you are not allocating memory for storage
    of the structure's members──you are merely creating a template that
    reserves those names for future use.

        ┌───────────────────────────────────────────────────────── Keyword
        │        ┌─────────────────────────────────────────── Name of pattern
        │        │     ┌──────────────────────────── Variables between braces
    struct cardstruct {
        char *first, *last, *middle;────────────────────── Member list start
        long street_num;
        char *street, *city, *state;
        long zip;
        int area_code;
        long phone;───────────────────────────────────────── Member list end
    │└──────────────────────────────────────────────────── Closing semicolon
    └────────────────────────────────────────────── Variables between braces

Structure Variables

    To reserve memory for a structure's members, you must declare structure
    variables that follow the pattern you defined. The following declaration
    sets aside memory for two structure variables (card1 and card2) using the
    above cardstruct pattern:

    struct cardstruct card1, card2;

    This declaration starts with the keyword struct, as did the pattern, but
    this time struct is followed by the name of a previously declared pattern
    and then by the names of the structure variables. Remember, you manipulate
    card1 and card2 in the program──the pattern cardstruct merely declares new
    structures. This statement reserves memory (allocates enough storage) for
    the predefined members of those two structure variables, as shown in
    Figure 11-2.

    struct cardstruct card 1, card 2;
                    └────┘  └────┘
                        │        │
                    ┌────┘        └─────────────────┐
        ┌───────┬─▼─────┐                   ┌─────▼─┬───────┐
        │  *first       │                   │  *first       │
        ├───────┼───────┤                   ├───────┼───────┤
        │  *last        │                   │  *last        │
        ├───────┼───────┤                   ├───────┼───────┤
        │  *middle      │                   │  *middle      │
        ├───────┼───────┼───────┬───────┐   ├───────┼───────┼───────┬───────┐
        │          street_num           │   │          street_num           │
        ├───────┼───────┼───────┴───────┘   ├───────┼───────┼───────┴───────┘
        │  *street      │                   │  *street      │
        ├───────┼───────┤                   ├───────┼───────┤
        │  *city        │                   │  *city        │
        ├───────┼───────┤                   ├───────┼───────┤
        │  *state       │                   │  *state       │
        ├───────┼───────┼───────┬───────┐   ├───────┼───────┼───────┬───────┐
        │              zip              │   │              zip              │
        ├───────┼───────┼───────┴───────┘   ├───────┼───────┼───────┴───────┘
        │    area_code  │                   │    area_code  │
        ├───────┼───────┼───────┬───────┐   ├───────┼───────┼───────┬───────┐
        │            phone              │   │            phone              │
        └───────┴───────┴───────┴───────┘   └───────┴───────┴───────┴───────┘
        │1 byte │
        │       │

    Figure 11-2. Declaring structure variables sets aside enough memory for
    the variables defined by cardstruct.

Accessing Structure Members

    To access a member of a structure in C, specify the name of the structure
    variable that contains the member, then the . (pronounced "dot") operator,
    then the name of the member you need to access, as in the following

    printf("%d\n", card1.area_code);
                    │   │  └──────────────────── Name of member of structure
                    │   └─────────────────────────────────────────── A "dot"
                    └───────────────────────────────────── Name of structure

    This expression prints the value of the integer area_code, one of the
    member variables in the structure variable named card1.

    You can manipulate members of structures as you would any C variables: You
    can assign values to them, use them in computations, and so on. The only
    difference is that you must reference each member variable with the name
    of its structure (card1 or card2, for example), a dot, and then its own

    The CARD.C program (Listing 11-1) demonstrates structures by prompting
    you to fill out information for a fictional address-book card; then it
    prints out the information you entered.

    /* card.c  --  demonstrates how to declare structures  */
    /*             and how to use structure members        */

    #include <stdio.h>      /* for NULL and stdin */
    #include <string.h>     /* for strdup()       */

    #define MAXN 79

    struct cardstruct {                 /* global pattern */
        char *first, *last, *middle;
        long street_num;
        char *street, *city, *state;
        long zip;
        int  area_code;
        long phone;

        char *Str_Input();
        long Lint_Input();
        struct cardstruct card1;

        card1.first         = Str_Input("First Name");
        card1.last          = Str_Input("Last Name");
        card1.middle        = Str_Input("Middle Name");
        card1.street_num    = Lint_Input("Street Number");
        card1.street        = Str_Input("Street Name");
        card1.city          = Str_Input("City");
        card1.state         = Str_Input("State");
        card1.zip           = Lint_Input("Zip Code");
        card1.area_code     = (int)Lint_Input("Area Code");
        card1.phone         = Lint_Input("Phone Number");

        printf("%s %s %s\n", card1.first, card1.middle,
        printf("%ld %s, %s, %s %ld\n", card1.street_num,
                card1.street, card1.city, card1.state,
        printf("(%d) %ld\n", card1.area_code, card1.phone);

    char *Str_Input(char *prompt)
        char buf[MAXN+1], *ptr;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        buf[strlen(buf) - 1] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
        if ((ptr = strdup(buf)) == NULL)
        return (ptr);

    long Lint_Input(char *prompt)
        char buf[MAXN + 1];
        long num;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        if (sscanf(buf, "%ld", &num) != 1)
        return (num);

    Listing 11-1.  The CARD.C program.

    CARD.C uses the members of the structure card1 exactly as it would
    ordinary variables. It assigns values to them with the = operator and
    passes those values to printf() to be printed.

Shorthand Structure Declarations

    As a bit of shorthand, you can declare structure patterns and allocate
    storage for structure variables in a single statement, as follows:

    When you allocate storage for structure variables as a part of the
    declaration, the name of the pattern becomes optional and you can omit it:

            ┌───────────────────────────────────────── Name of pattern omitted
    struct {
        /* list of members here */
    } card1, card2;
        └──────┴─────────────────────────────── Structures allocated storage

    You must use the pattern name, however, if you intend to declare
    additional structure variables using that pattern name later in the

    struct cardstruct card3, card4;

Structure Assignment

    When you declare structure variables with the same pattern, you can assign
    one to another, as follows:

    card2 = card1;

    This assignment copies the values of all card1 members into the
    corresponding members of card2.

    If you try to assign one structure variable to another when those
    structures are declared with different pattern names (even if the members
    of both are identical), QuickC returns the following error message:

    error C2115:
    '=' : incompatible types

    Quick Tip
    One way to make a program such as CARD.C more robust, or user friendly, is
    to enable the program to handle telephone numbers that contain a hyphen
    (-) character. Consider the necessary revisions to CARD.C. Why is this
    enhancement difficult in a program that uses scanf() to parse user input?

    If you need to assign values from one structure to another of a different
    pattern, you must assign the members individually. For example, if card1
    uses the pattern cardstruct and memo uses another pattern, memostruct, you
    could assign the members of one to the other in the following way:

    card1.first  = memo.first_name;
    card1.last   = memo.last_name;
    card1.middle = memo.mid_name;

Passing Structures to Functions

    Passing a structure to a function passes a copy of its members. This
    prevents the called function from changing the original structure. To pass
    a structure to a function, simply state the structure's name, as follows:


    In this example, a copy of the structure variable card1──including copies
    of all its members──is passed to the function Showcard(). Remember,
    structures differ from arrays in this regard: When you pass a structure to
    a function, you pass only a copy of that structure; when you pass an
    array, you pass the address of that array, thus allowing the original
    array to be changed by the calling function.

    In the receiving function (such as Showcard() below), you must declare the
    type of the received argument with struct and the pattern name
    (cardstruct). This tells the compiler that Showcard() is receiving a
    structure as its argument, and that the pattern for that structure is
    named cardstruct:

                                    ┌────────────────────────── Receive copy of
    Showcard(struct cardstruct card)
    {                    └────────────────── structure based on this pattern
        /* body of function */

    The CARD2.C program (Listing 11-2 beginning on the following page) is a
    revised CARD.C program. In it, we fill out two cards and then print those
    cards using the Showcard() function.

    Quick Tip
    There are two drawbacks to passing structures to functions. First, not all
    compilers support the passing of structures, so if portability is
    important, you might want to avoid this technique. Second, as structures
    get larger, QuickC takes longer to copy them for each function call. This
    can become very time-consuming if it occurs in the middle of a loop. Thus,
    to speed the processing of your programs and enable the original to be
    changed, we advise you to use pointers to structures.

    /* card2.c --  demonstrates structure assignment and   */
    /*             how to pass a structure to a function   */

    #include <stdio.h>      /* for NULL  and stdin */
    #include <string.h>     /* for strdup()        */

    #define MAXN 79

    struct cardstruct {                 /* global pattern */
        char *first, *last, *middle;
        long street_num;
        char *street, *city, *state;
        long zip;
        int  area_code;
        long phone;

        int  i;
        char *Str_Input();
        long Lint_Input();
        struct cardstruct card1, card2;

        for (i = 0; i < 2; i++) /* do twice */
            printf("\nCard %d:\n\n", i + 1);

            card1.first         = Str_Input("First Name");
            card1.last          = Str_Input("Last Name");
            card1.middle        = Str_Input("Middle Name");
            card1.street_num    = Lint_Input("Street Number");
            card1.street        = Str_Input("Street Name");
            card1.city          = Str_Input("City");
            card1.state         = Str_Input("State");
            card1.zip           = Lint_Input("Zip Code");
            card1.area_code     = (int)Lint_Input("Area Code");
            card1.phone         = Lint_Input("Phone Number");

            if (i == 0)
                card2 = card1;      /* structure assignment */

    Showcard(struct cardstruct card)

        printf("%s %s %s\n", card.first, card.middle, card.last);
        printf("%ld %s, %s, %s %ld\n", card.street_num,
                card.street, card.city, card.state, card.zip);
        printf("(%d) %ld\n", card.area_code, card.phone);

    char *Str_Input(char *prompt)
        char buf[MAXN + 1], *ptr;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
        if ((ptr = strdup(buf)) == NULL)
        return (ptr);

    long Lint_Input(char *prompt)
        char buf[MAXN + 1];
        long num;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        if (sscanf(buf, "%ld", &num) != 1)
        return (num);

    Listing 11-2.  The CARD2.C program.

    In CARD2.C, Showcard() receives a copy of card1 from main(). Note that the
    members of the Showcard() structure, card, are accessed with the same
    "dot" notation as the originals in main().

Pointers to Structures

    Passing a pointer to a structure, rather than a copy of a structure, to a
    function has two advantages. It permits the function to modify the members
    of the original structure. Also, far fewer bytes must be copied when a
    pointer is passed than are copied when a structure is passed──the result
    is faster executing code.

    You declare a pointer to a structure the same way that you declare a
    pointer to any other type──by preceding its name with a *, as follows:

    struct cardstruct *cardptr;
                │     └────── A pointer to a structure of pattern cardstruct
                └─────────────────────────────────────────── Name of pattern

    This example declares a pointer variable, cardptr, whose contents will be
    an address. The struct cardstruct in the declaration tells the compiler
    that cardptr will point to a structure variable based on the pattern
    cardstruct. (See Figure 11-3.)

    Before you can use the pointer cardptr, it must be given a value. Because
    it is a pointer to a structure, we will assign it the address of the
    structure variable card1 from CARD.C:

    cardptr = &card1;

    The & operator fetches the address of a structure. (Note that this differs
    from arrays, where the array name itself yields the address.) To assign
    the address of a structure variable to a pointer to a structure, declare
    both the pointer and the structure with the same pattern name. If you
    declare them with different pattern names, QuickC returns the following
    warning message:

    warning C4049:
    '=' : indirection to different types

    The & operator can also pass the address of a structure directly to a

            └───────────────────────────── Pass address of card1 to a function

    struct cardstruct *cardptr;
            ┌───────┬───▼───┐ Points to   ┌───────┬───────┐
            │   address     │────────────►│  *first       │
            └───────┴───────┘             ├───────┼───────┤
                                            │  *last        │
                                            │  *middle      │
                                            │          street_num           │
                                            │  *street      │
                                            │  *city        │
                                            │  *state       │
                                            │              zip              │
                                            │    area_code  │
                                            │            phone              │
                                            Structure of pattern cardstruct

    Figure 11-3. A pointer to a structure contains the address of a structure

    We also must declare the received argument for the Enter() function as a
    pointer to a structure, as follows:

    Enter(struct cardstruct *item)
            {                  └───────────────── Pointer to receive an address

    Again, be sure that you declare the same pattern name for both the passed
    and the received structures.

Accessing Structure Members with a Pointer

    To access the members of a structure with a pointer, you need to use a new
    symbol, ->. Called "to", -> is actually two characters──a "minus"
    character followed by a "greater than" character. The following code
    illustrates the use of the -> operator. In it, the pointer cardptr
    accesses the phone member of the structure card1:

    struct cardstruct {──────────────────────────────────── Define a pattern
        char *first, *last, *middle;
        int age;

    struct cardstruct card1, *cardptr;
                │        │       └──────────────────── Declare a pointer and
                │        └───────────────────────────── a structure variable
                └────────────────────────────────────── both of that pattern
    cardptr = &card1;───────────────────── Assign card1's address to cardptr
    cardptr->phone = 5551212;──────────── Access member of card1 via cardptr
            └────────────────────────────── Points "to" member phone of card1

    The CARD3.C program (Listing 11-3) is another revision of CARD.C. This
    modification has Showcard() receiving the address of a structure. Rather
    than printing a copy, it prints the original via a pointer to the

    /* card3.c --  demonstrates pointers to structures     */

    #include <stdio.h>      /* for NULL  and stdin */
    #include <string.h>     /* for strdup()        */

    #define MAXN 79

    struct cardstruct {                 /* global pattern */
        char *first, *last, *middle;
        long street_num;
        char *street, *city, *state;
        long zip;
        int  area_code;
        long phone;
        int  i;
        char *Str_Input();
        long Lint_Input();
        struct cardstruct card1, card2;

        for (i = 0; i < 2; i++) /* do twice */
            printf("\nCard %d:\n\n", i + 1);

            card1.first         = Str_Input("First Name");
            card1.last          = Str_Input("Last Name");
            card1.middle        = Str_Input("Middle Name");
            card1.street_num    = Lint_Input("Street Number");
            card1.street        = Str_Input("Street Name");
            card1.city          = Str_Input("City");
            card1.state         = Str_Input("State");
            card1.zip           = Lint_Input("Zip Code");
            card1.area_code     = (int)Lint_Input("Area Code");
            card1.phone         = Lint_Input("Phone Number");

            if (i == 0)
                card2 = card1;
        Showcard(&card2);     /* pass addresses of structures */

        return (0);

    struct cardstruct *cardptr; /* pointer receives an address */

        printf("%s %s %s\n", cardptr->first, cardptr->middle,
        printf("%ld %s, %s, %s %ld\n", cardptr->street_num,
                cardptr->street, cardptr->city, cardptr->state,
                cardptr->zip );
        printf("(%d) %ld\n", cardptr->area_code, cardptr->phone);

    char *Str_Input(char *prompt)
        char buf[MAXN + 1], *ptr;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)        exit(0);
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
        if ((ptr = strdup(buf)) == NULL)
        return (ptr);

    long Lint_Input(char *prompt)
        char buf[MAXN + 1];
        long num;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        if (sscanf(buf, "%ld", &num) != 1)
        return (num);

    Listing 11-3.  The CARD3.C program.

Arrays of Structures

    Structures can be organized in arrays like any other type of variable. You
    declare an array of structures as follows:

    struct cardstruct {
        /* members declared here */
    } cards[3];
            └───────────────────────────── An array of three structures

    This example declares an array of three structures (cards[3]) and defines
    the pattern cardstruct at the same time. If you had already defined the
    pattern, you could declare the same array as follows:

    struct cardstruct cards[3];

    Use an array of structures the same way you use any other array. For
    example, the following statement prints the first member of the second

    printf("%s", cards[1].first);

    The expression cards[1] accesses the second structure of the array, and
    the .first yields the member named first from that structure.

    To pass the address of one of the structures in the array cards, use the &
    operator followed by the structure's offset in square brackets.

    ┌──────────────────────────────────────────────────────────── Address of
            └─────────────────── the <FI>i<FS>th structure in the array of str

    The ROLO.C program (Listing 11-4) is a complete address book built from
    the earlier CARD.C program. It asks you to fill out the three cards in our
    array of structures. Then it prints out the information in those cards. By
    combining this use of structures with the file-handling routines of
    PHONE.C (from the previous chapter), you have the basis for a truly
    useful phone-index program.

    /* rolo.c  --  demonstrates pointers to structures    */

    #include <stdio.h>      /* for NULL  and stdin */
    #include <string.h>     /* for strdup()        */

    #define MAXN 79
    #define MAXCARDS 3

    struct cardstruct {                 /* global pattern */
        char first[MAXN],
        unsigned long street_no;
        char street[MAXN],
        unsigned long zip;
        unsigned int area;
        unsigned long phone;
    struct cardstruct cards[MAXCARDS];

        int  i;

        for (i = 0; i < MAXCARDS; ++i)
            printf("\n<card %d of %d>\n", i + 1, MAXCARDS);
        for (i = 0; i < MAXCARDS; ++i)
            printf("\n<%d> ", i + 1);
    Input(struct cardstruct *cardp)
        char *Str_Input();
        long Lint_Input();

        strcpy(cardp->first,Str_Input("First Name"));
        strcpy(cardp->last,Str_Input("Last Name"));
        strcpy(cardp->middle,Str_Input("Middle Name"));
        cardp->street_no = Lint_Input("Street Number");
        cardp->zip = Lint_Input("Zip Code");
        cardp->area = (int)Lint_Input("Area Code");
        cardp->phone = Lint_Input("Phone Number");

    char *Str_Input(char *prompt)
        char buf[MAXN + 1], *ptr;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
        if ((ptr = strdup(buf)) == NULL)
        return (ptr);

    long Lint_Input(char *prompt)
        char buf[MAXN + 1];
        long  num;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        if (sscanf(buf, "%ld", &num) != 1)
        return (num);

    Showcard(struct cardstruct *cardptr)
        printf("%s %s %s\n", cardptr->first, cardptr->middle,
        printf("%ld %s, %s, %s %ld\n", cardptr->street_no,
                cardptr->street, cardptr->city, cardptr->state,
        printf("(%d) %ld\n", cardptr->area, cardptr->phone);

    Listing 11-4.  The ROLO.C program.

    ROLO.C uses an array of three structures. Notice that the cards[] array
    consists of structures that themselves contain arrays.

Arrays of Pointers to Structures

    Not only can you create arrays of structures, you can also create arrays
    of pointers to structures. These arrays of pointers offer the advantage of
    increased efficiency. For example, when sorting, it is faster to swap two
    pointers than it is to exchange the vastly greater number of bytes of the
    structures themselves.

    You declare an array of pointers to structures as follows:

    struct cardstruct *cardps[3]

    This example declares an array of three pointers in which each pointer
    points to a structure of the pattern cardstruct. Figure 11-4 illustrates
    such an arrangement.

    You can initialize cardps[] (an array of pointers to structures) to
    contain the address of the corresponding elements in the array of
    structures cards[] as follows:

    cardps[0] = &cards[0];
    cardps[1] = &cards[1];
    cardps[2] = &cards[2];

    This lets you use the -> operator to indirectly reference the members of
    each structure in cards[] with the pointers in cardps[]. For example, the
    street member of the second structure of the array of structures cards[]
    can be indirectly referenced through the array of pointers to structures
    in cardps[], as follows:

    strcpy(cardps[1]->street, "Any St.");
                    └───────────── Points "to" the member street of cards[1]

Structure Recursion and Linked Lists

    Structures are so versatile that they can hold every possible type in C,
    including themselves. This remarkable ability to be self-inclusive opens
    whole new sets of programming possibilities. The most common of these is
    the technique shown in Figure 11-5 (on p. 348) that uses "linked lists."

                ┌─►│   first []          │
                │  ├─────────────────────┤
                │  │   last []           │
                │  ├─────────────────────┤
                │  │   middle []         │
                │  ├───────────┬─────────┘
                │  │street_num │
                │  ├───────────┴─────────┐
                │  │   street []         │
                │  ├─────────────────────┤
                │  │   city []           │
                │  ├─────────────────────┤
                │  │   state []          │
                │  ├───────────┬─────────┘
                │  │   zip     │
                │  ├──────┬────┘
                │  │area  │
    ┌────┬────┐   │  ├──────┴────┐
    │ address │───┘  │   phone   │
    ├────┼────┤      ├───────────┴─────────┐
    │ address │─────►│   first []          │
    ├────┼────┤      ├─────────────────────┤
    │ address │───┐  │   last []           │
    └────┴────┘   │  ├─────────────────────┤
        cardps [3] │  │   middle []         │
                │  ├───────────┬─────────┘
                │  │street_num │
                │  ├───────────┴─────────┐
                │  │   street []         │
                │  ├─────────────────────┤
                │  │   city []           │
                │  ├─────────────────────┤
                │  │   state []          │
                │  ├───────────┬─────────┘
                │  │   zip     │
                │  ├──────┬────┘
                │  │area  │
                │  ├──────┴────┐
                │  │   phone   │
                │  ├───────────┴─────────┐
                └─►│   first []          │
                    │   last []           │
                    │   middle []         │
                    │street_num │
                    │   street []         │
                    │   city []           │
                    │   state []          │
                    │   zip     │
                    │area  │
                    │   phone   │
                            cards [3]

    Figure 11-4. An array of pointers to structures. Each element points to a
    structure in an array of structures.

    ┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
    │                 │  ┌─►│                 │  ┌─►│                 │
    ├─────────────────┤  │  ├─────────────────┤  │  ├─────────────────┤
    ├─────────────────┤  │  ├─────────────────┤  │  ├─────────────────┤
    ├─────────┬───────┘  │  ├─────────┬───────┘  │  ├─────────┬───────┘
    ├─────────┴───────┐  │  ├─────────┴───────┐  │  ├─────────┴───────┐
    ├─────────────────┤  │  ├─────────────────┤  │  ├─────────────────┤
    ├─────────────────┤  │  ├─────────────────┤  │  ├─────────────────┤
    ├─────────┬───────┘  │  ├─────────┬───────┘  │  ├─────────┬───────┘
    ├────┬────┘          │  ├────┬────┘          │  ├────┬────┘
    ├────┴────────────┐  │  ├────┴────────────┐  │  ├────┴────────────┐
    ├─────────────────┤  │  ├─────────────────┤  │  ├─────────────────┤
    │    nextcard     ├──┘  │    nextcard     ├──┘  │    nextcard     │
    └─────────────────┘     └─────────────────┘     └─────────────────┘

    Figure 11-5. In a linked list, each structure contains a pointer to
    another structure of the same type.

    A linked list is an arrangement of structures in which each structure
    contains a pointer to (the address of) its neighbor. For example, to
    declare such a linked list in ROLO.C, we must modify the structure pattern
    as follows:

    struct cardstruct {
        char first[MAXN],
        unsigned long street_no;
        char street[MAXN],
        unsigned long zip;
        unsigned int area;
        unsigned long phone;
        struct cardstruct *nextcard;────────────────────────────────── Added
                            └ Pointer to another structure of this same pattern

    The new member *nextcard is a pointer to a structure, but it points to a
    structure of its own pattern. By declaring several structures of this
    pattern with

    struct cardstruct card1, card2, card3, card4;

    and then initializing the nextcard member of each to contain the address
    of its neighbor, you create a linked list:

    card1.nextcard = &card2;
    card2.nextcard = &card3;
    card3.nextcard = &card4;

    The ROLO2.C program (Listing 11-5) uses malloc() to build a linked list
    of structures while the program is running. Using this approach, we can
    add as many cards to our address book as we want (subject to the limit of
    the computer's memory).

    /* rolo2.c --  demonstrates a linked list */

    #include <stdio.h>      /* for NULL  and stdin */
    #include <string.h>     /* for strdup()        */
    #include <malloc.h>     /* for malloc()        */

    #define MAXN 79

    struct cardstruct {                 /* global pattern */
        char first[MAXN],
        unsigned long street_no;
        char street[MAXN],
        unsigned long zip;
        unsigned int area;
        unsigned long phone;
        struct cardstruct *nextcard;

        int i;
        struct cardstruct card, *first, *current;

        first = (struct cardstruct *)malloc(sizeof(struct cardstruct));
        if (first == NULL)
        if (Input(&card) != 0)
        *first = card;
        current = first;

        while (Input(&card) == 0)
            current->nextcard =
                (struct cardstruct *)malloc(sizeof(struct cardstruct));
            if (current->nextcard == NULL)
            current = current->nextcard;
            *current = card;
        current->nextcard = NULL;

    Dumplist(struct cardstruct *head)
            } while ((head = head->nextcard) != NULL);

    Showcard(struct cardstruct *cardptr)

        printf("%s %s %s\n", cardptr->first, cardptr->middle,
        printf("%ld %s, %s, %s %ld\n", cardptr->street_no,
                cardptr->street, cardptr->city, cardptr->state,
                cardptr->zip );
        printf("(%d) %ld\n", cardptr->area, cardptr->phone);

    Input(struct cardstruct *cardp)
        char *Str_Input();
        long Lint_Input();

        printf("\n<new card> (Empty first name Quits)\n");
        strcpy(cardp->first,Str_Input("First Name"));
        if (*(cardp->first) == '\0')
            return (1);
        strcpy(cardp->last,Str_Input("Last Name"));
        strcpy(cardp->middle,Str_Input("Middle Name"));
        cardp->street_no = Lint_Input("Street Number");
        cardp->zip = Lint_Input("Zip Code");
        cardp->area = (int)Lint_Input("Area Code");
        cardp->phone = Lint_Input("Phone Number");
        return (0);

    char *Str_Input(char *prompt)
        char buf[MAXN + 1], *ptr;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if ((ptr = strdup(buf)) == NULL)
            exit(0);    return (ptr);

    long Lint_Input(char *prompt)
        char buf[MAXN + 1];
        long  num;

        printf("%s: ", prompt);
        if (fgets(buf, MAXN, stdin) == NULL)
        if (sscanf(buf, "%ld", &num) != 1)
            num = 0;
        return (num);

    Listing 11-5.  The ROLO2.C program.

    Notice that the last structure in the list always has its nextcard member
    set to NULL. That's how the program marks the end of the linked list.

    This program also illustrates two other interesting properties of
    structures. First, when you apply the sizeof operator to a structure or to
    a structure's pattern, it yields the total number of bytes for all the
    members of the structure:

    malloc(sizeof(struct cardstruct));

    Second, we had to type cast the value returned by malloc() to a type
    appropriate for the pointer to which the value is assigned:

    first = (struct cardstruct *)malloc(sizeof(struct cardstruct));
                        └─────────────── Type cast to a pointer to a structure

    Note that you must use the structure pattern name in the type cast, not
    the structure variable name. Had we omitted the type cast, QuickC would
    complain with:

    Warning C4049:
    '=' indirection to different types

Initializing Structures with Starting Values

    As in arrays, you can initialize structures that are static or global when
    you declare them. The type of the initializing value must, of course,
    match the type of the corresponding member. An attempt to initialize with
    the wrong type will yield the following QuickC warning:

    Warning C4047:
    "initializing": different levels of indirection

    The following structure is declared correctly:

    static struct cardstruct card = {
                "Bob",─────────────────────────────────────────────Member first
                "Roberts",──────────────────────────────────────────Member last
                "Mason",──────────────────────────────────────────Member middle
                42─────────────────────────────────────────────Member street_no
                "Willow Way",─────────────────────────────────────Member street
                "Tonopah",──────────────────────────────────────────Member city
                "Nevada",──────────────────────────────────────────Member state
                84521L,──────────────────────────────────────────────Member zip
                916,────────────────────────────────────────────────Member area
                5551212L───────────────────────────────────────────Member phone

    As with arrays, if you specify fewer initializers than members, QuickC
    gives the trailing uninitialized members the default value of zero.

Union──Multiple Types in the Same Space

    You can think of a "union" as the opposite of a structure. While struct is
    a collection of many types, each with its own location in memory, a union
    is a collection of many types that all share the same location in memory.
    Thus, a union can contain different types at various times, but it can
    contain only a single value of a single type at any given time.

    Although its uses are limited, a union is a blessing when you do encounter
    a need for one. For example, consider writing a function that needs to
    print either an int or a float, yet doesn't know ahead of time what type
    it will receive as its argument. Before we can show you how to write such
    a function, however, we need to cover the basics of declaring and using

    You declare a union as you would a structure, except you use the keyword
    union instead of struct:

                ┌────────────────────────────────────────────── Name of pattern
    union twotype {
        float ftype;──────────────────────────────────────────────── Members
        int   itype;
    } one_of_many ;
        └──────────────────────── Name of a union variable of pattern twotype

    This example tells the compiler to reserve memory for the variable
    one_of_many, which will hold either a float or an int. Because the float
    is larger, union reserves four bytes──enough space to hold either type.

    As a general rule, you should place the largest member first in a union
    declaration. Some compilers allocate memory based only on the first
    member, rather than searching all members for the largest. QuickC is well
    behaved in this regard, however. It allocates the correct number of bytes
    for a union, regardless of the order of the member declarations.

    As with structure members, you access the members of a union with the
    "dot" operator. However, the compiler interprets the type of the union as
    the type specified by the member name, as follows:

    one_of_many.ftype = 1.0;────────────────────────────Interpret as a float
    one_of_many.itype = 1;───────────────────────────────Interpret as an int

    The UDEMO.C program (Listing 11-6) is a simple demonstration of how a
    union works. After asking the user to enter a type, it uses scanf() to
    read that type and printf() to echo it to the screen.

    /* udemo.c  --  demonstrates a union at work */

    #include <stdio.h>

    char *Strings[6] = {
            "line of text",
            "floating-point double value",
            "long integer value",
            "floating-point value",
            "integer value"

    struct Unitstruct {
        union {
            char   wtype[BUFSIZ];
            double dtype;
            long   ltype;
            float  ftype;
            int    itype;
        } manyu;
        int type_in_union;

        struct Unitstruct one_of_many;

        while ((one_of_many.type_in_union = Menu()) != 0 )

    Inputval(struct Unitstruct *one_of_many)
        printf("\nEnter a %s: ", Strings[one_of_many->type_in_union]);
            case 1:
                fgets(one_of_many->manyu.wtype, BUFSIZ, stdin);
            case 2:
                scanf("%lf", &(one_of_many->manyu.dtype));
                while (getchar()!= '\n');
            case 3:
                scanf("%ld", &(one_of_many->manyu.ltype));
                while (getchar()!= '\n');
            case 4:
                scanf("%f", &(one_of_many->manyu.ftype));
                while (getchar()!= '\n');
            case 5:
                scanf("%i", &(one_of_many->manyu.itype));
                while (getchar()!= '\n');

    Printval(struct Unitstruct *one_of_many)
        printf("The %s you entered\nwas: ", Strings[one_of_many->type_in_union]
        switch (one_of_many->type_in_union)
            case 1:
                fputs(one_of_many->manyu.wtype, stdout);
            case 2:
                printf("%lf", one_of_many->manyu.dtype);
            case 3:
                printf("%ld", one_of_many->manyu.ltype);
            case 4:
                printf("%f", one_of_many->manyu.ftype);
            case 5:
                printf("%i", one_of_many->manyu.itype);

        int i;
        char ch;
        for (i = 0; i < 6; ++i)
            printf("%d) %s\n", i, Strings[i]);
        printf("Which: ");
            ch = getch();
            } while (ch < '0' || ch > '5');
        printf("%c\n", ch);
        return (ch - '0');

    Listing 11-6.  The UDEMO.C program.

Unions and Functions

    Unlike a structure, you cannot pass a union to a function. Instead, you
    must pass the value of the type currently stored in that union. For
    example, the statement

    printf("%f", one_of_many.ftype);
                │               └───────── Sends the float value in one_of_many
                └────────────────────────────────────────────── Expects a float

    sends printf() the float value in one_of_many, which matches the printf()
    %f format specifier. Note that it is meaningless in C to use a union
    variable (such as one_of_many) without a corresponding "dot" and member

    Quick Tip
    The UDEMO.C program illustrates a common technique for managing unions.
    Because a union contains no inherent indication of the type it contains,
    unions are often made members of structures, with another member used to
    store that indication:

    struct Unitstruct{
        union {
            char   wtype[BUFSIZE];
            double dtype;
            long   ltype;
            float  ftype;
            int    itype;
        } manyu;
        int type_in_union;

    By packaging a union and an int together in a structure like this, we are
    better able to keep track of the type stored in the union at any given

Unions Received by Functions

    C permits you to use a union as the type of an argument received by a
    function, but the procedure can be risky. The following statement
    illustrates one way to declare a received variable in a subroutine as a

    #define FLT 0  /* floating-point type */
    #define INT 1  /* integer type        */

    Printval(val, type)
    union twotype val;
    int type;
            switch (type)
                case FLT: printf("%f", val.ftype); break;
                case INT: printf("%d", val.itype); break;

    This function receives two arguments: a union of two possible types and an
    int that specifies which of the two possible types is in that union.

    But beware. Depending on how the compiler passes arguments to functions,
    this approach can fail. In QuickC, a float is four bytes and an int is two
    bytes; therefore, the stack (received arguments) resembles Figure 11-6a
    when passing a float and Figure 11-6b when passing an int. However,
    because the pattern for twotype reserves four bytes, passing an int to
    Printval() causes the type argument to appear in the wrong place.

    You can resolve this dilemma by constraining union members to types that
    use the same number of bytes. That is, if you declare twotype as follows:

    union twotype {
        float fval;
        long ival;

    it would contain either of two types, but each type requires four bytes. A
    better solution is to package a union and an int together inside a
    structure, as you saw earlier. That approach avoids the potential pitfalls
    of declaring a function that receives a bare-bones union.

Pointers to Unions

    Pointers to unions behave like pointers to structures. You retrieve the
    address of a union with the & operator and the union variable name, as


    Printval (float, int)
                │     │        High address
                │     │     ┌────────────────┐
                │     └────►├──    int     ──┤2-byte int
                │           ├────────────────┤
                │           ├──            ──┤
                └──────────►├──   float    ──┤4-byte float
                            ├──            ──┤
    Start of arguments ────►└────────────────┘
                                Low address


    Printval (int, int)
                │    │          High address
                │    │       ┌────────────────┐
                │    │       ├──            ──┤Second argument missing
                │    │       ├────────────────┤
                │    └──────►├──    int     ──┤2-byte int
                │            ├────────────────┤
                └───────────►├──    int     ──┤2-byte int
    Start of arguments ────►└────────────────┘
                                Low address


    Figure 11-6. Passing different-size data types to the same function can
    cause confusion.

    To fetch the address of a union member, specify the & operator, the union
    variable name, the "dot" operator, and the member name, as follows:


    Declaring pointers to unions and manipulating values via the addresses in
    those pointers is also identical to the form used by structure pointers.
    Declare a pointer to a union as follows:

    union manytype *up;
                    └───────────── Pointer to a union of the pattern manytype

    Place a value (an address) into that pointer in the following form:

    up = &one_of_many;

    To access the type of the value stored in the union whose address is in
    up, use the -> operator as follows:

    up->ftype = 1.0;

    Structures and unions are closely related. The main difference is that a
    structure holds many values simultaneously; a union holds only a single
    type of value at any one time. As you have seen, structures can include
    unions as members. It is also legal for unions to contain structures as
    members. We'll use this latter technique at the end of this chapter, when
    we discuss bit fields.

Enumerated Data with enum

    Many kinds of information are best represented by a finite list of
    discrete integer values──for example, the days of the week, the months of
    the year, or even the phases of the moon. Such kinds of information, in
    which every possibility is known in advance, lend themselves to
    enumeration──a listing of all possible values for a given topic or

    If you need to represent the days of the week in a program as discrete
    integers, you could make the following declarations and assignments:

    int monday  = 0, tuesday = 1, wednesday = 2, thursday = 3,
        friday = 4, saturday = 5, sunday = 6;

    and later use those values as follows:

    pay_day = friday;

    The previous approach, although reasonable, has a potential pitfall.
    Because the days of the week are int variables, the program might change
    their values, and so render them meaningless. To avoid this problem we can
    use the following directives:

    #define MONDAY 0
    #define TUESDAY 1

    The program can't change these values because they are integer constant
    aliases. But this is still not an ideal solution because you cannot group
    #define definitions under a single conceptual name.

    The best solution uses the C enumerated data type, enum, whose members are
    constants grouped under a single name. To represent the days of the week
    using enum, first declare a pattern similar to a structure or union

                ┌────────────────────────────────────────────── Name of pattern
    enum week_days {
        monday,───────────────────────────────────────────────────── Members
    } pay_day;
            └────────────────────────────────────────────── Enumerated variable

    This example declares a pattern called week_days, an enumerated data type,
    and the enumerated variable pay_day. Note that the members don't need to
    be preceded by a type keyword because the members of enum are always of
    type int. Also notice that you don't need to assign the members any
    values: The declaration itself gives the members constant integer values,
    starting with 0 for monday and counting through 6 for sunday.

    Another difference between enum and struct or union is that you access
    members of enum simply by stating the member's name without the "dot" or
    "->" notation:

    payday = monday;

    Any attempt to change the value of an enumerated member (monday = 5, for
    example) results in the following QuickC error message:

    error C2106:
    '=' : left operand must be lvalue

    This reminds you that the members of an enumerated data type, like all
    other constants, are rvalues and can appear only to the right of an
    assignment operator.

    Also note that you cannot use a pointer to indirectly change the value of
    an enumerated variable member. For example, the following assignment:

    int *p;

    p = &monday;────────────────────────────Can't take address of a constant
    *p = 5;

    fails because you can't retrieve the address of a constant. This attempt
    generates the following QuickC error message:

    error C2101:
    '&' on constant

    The TODAY.C program (Listing 11-7 on the following page) demonstrates one
    advantage to using enum──improved readability. The program asks you to
    specify the day on which you want to be paid. It then checks to make
    certain that you specified a legal day.

    The pattern week_day in TODAY.C shows that you can initialize an enum
    member to any integer value. Any uninitialized member, however, is
    assigned a value one higher than the member before it. For example, the

    enum folks {
        mo = -1,
        betsy = 0,
        joey = 1

    sets mo to a -1, roseann and betsy to 0, and kit and joey to 1. This also
    shows that enum members can have duplicate values.

    /* today.c  -- demonstrates using enum  */

        enum week_days {
            monday = 1,     /* start with 1 */
        } pay_day;

        static char *day_names[] = {

        printf("What day do you want to be paid on?\n");

        for (pay_day = monday; pay_day <= sunday; ++pay_day)
            printf("%d. %s\n", pay_day, day_names[pay_day]);

        printf("Which (%d-%d): ", monday, sunday);

            pay_day = getch();
            pay_day -= '0';
            } while (pay_day < monday || pay_day > sunday);

        printf("%d\n\n", pay_day);

        printf("You selected %s\n", day_names[pay_day]);


    Listing 11-7.  The TODAY.C program.

Bit Fields

    In Chapter 7, we discussed how to use bitwise operators to store data in
    the individual bits of bytes. Another, and simpler, way to store and
    access information in bits is with "bit fields."

    Bit fields offer two advantages over the bitwise operators. First, you can
    access bit fields by name (such as blink) rather than by an obscure mask
    (such as (1 << 7)). Second, the compiler generates code for bit fields
    that you normally would have to write yourself. Examine, for example, the
    following bit-field assignment:

    blink = 1;

    where blink is the name of the sixteenth bit of a 2-byte int. This
    statement is comparable to the following assignment using bitwise

    ch |= (1 << 15);

    C's bit fields are especially handy when you need to manipulate items with
    built-in bit information. The characters in your screen memory are
    examples of such items. Recall that each screen character is represented
    by a 2-byte int. One byte is the character itself; the other is the
    attribute byte. (See Figure 11-7.)

                                ┌────────Most significant bit
        Blinking (1 bit) ─────│   7   │ │
                            ┌─├─     ─┤ │
                            │ │   6   │ │
                            │ ├─     ─┤ │
    Background (3 bits) ───┤ │   5   │ │
                            │ ├─     ─┤ │
                            │ │   4   │ │
                            └─├─     ─┤ ├───Attributes (1 byte)─┐
    Intensity (1 bit) ─────│   3   │ │                       │
                            ┌─├─     ─┤ │                       │
                            │ │   2   │ │                       │
                            │ ├─     ─┤ │                       │
    Foreground (3 bits) ───┤ │   1   │ │                       │
                            │ ├─     ─┤ │                       │
                            │ │   0   │ │                       │
                            └─├───────┤─┤                       ├──1 int
                            │   7   │ │                       │
                            ├─     ─┤ │                       │
                            │   6   │ │                       │
                            ├─     ─┤ │                       │
                            │   5   │ │                       │
                            ├─     ─┤ │                       │
                            │   4   │ │                       │
                            ├─     ─┤ ├───Character (1 byte)──┘
                            │   3   │ │
                            ├─     ─┤ │
                            │   2   │ │
                            ├─     ─┤ │
                            │   1   │ │
                            ├─     ─┤ │
                            │   0   │ │
                                └────────Least significant bit

    Figure 11-7. One character in screen memory is represented by two
    consecutive bytes.

    The following is an example of one such screen int declared using bit

                ┌──────────────┬────────────── One integer...divided like this
    unsigned int character  :8,
                    foreground :3,
                    intensity  :1,
                    background :3,
                    blink      :1;
                    └─────────┴──────────────────── Name for...this many bits

    In this declaration, we tell QuickC to use the bits in one unsigned
    integer. Next, we specify the names for each group of bits in that
    integer, beginning with 8 bits, to which we give the name character, and
    continuing through all 16 bits until we end with blink as the name of the
    final bit.

    You may name as many bits as there are in the type declared (8 for a char,
    32 for a long, and so on). Only integer types can be used as bit fields,
    and only integer constants can be used to declare the number of bits.
    Always declare the bits from the bottom up (from the least significant to
    the most significant bits). A colon separates the name for each group of
    bits from the number of bits assigned to it; a comma separates each name
    :bits from the next; and, of course, a semicolon must end the entire

    If you declare fewer bits than there are in a type, the unused bits are
    simply ignored. If you declare more, an additional variable of the same
    type is allocated:

            ┌───────────────────────────────────────────────────── 8 bits
    unsigned char character :8,
                    foreground :3,
                    intensity  :1,
                    background :3,
                    blink      :1;
                                └─── 16 bits total allocates two char variables

    The name :bits combination is what defines a bit field as opposed to an
    ordinary variable. For example, the above declaration produces the same
    allocation as the following series of declarations:

    unsigned char character  :8;
    unsigned char foreground :3;
    unsigned char intensity  :1;
    unsigned char background :3;
    unsigned char blink      :1;

    In this example, the compiler gathers the bits from the declared bit
    fields into the most compact unit, regardless of how many bit fields you

    Because you are not permitted to retrieve the address of a bit field, you
    usually will declare bit fields inside structures, as follows:

    struct screen_char_struct {
        unsigned int character  :8,
                        foreground :3,
                        intensity  :1,
                        background :3,
                        blink      :1;
    } screen_ch ;
            └───────────────── Structure variable whose members are bit fields

    This approach has two advantages. First, you can access the individual bit
    fields with the usual structure/member notation. This improves

    screen_ch.blink = 1;─────────────────Retrieve the address of a structure

    Second, you can access the address of a structure, but you cannot retrieve
    the address of a bit field. This lets you manipulate bit fields with
    pointers, which can increase the speed of your program:


    The SCRMENU.C program (Listing 11-8) demonstrates how to use bit fields
    to modify text-screen display. It lets you select an attribute; then it
    toggles the setting for that attribute for every character on the screen.

    /* scrmenu.c  -- uses bit fields to modify your text   */
    /*               screen's attributes                   */

    char *Choice_Words[] = {
    enum Choices {

    /* use 0xB800000 for EGA or VGA */
    #define SCR_START (0xB0000000)
    #define SCR_SIZE (25 * 80)
        enum Choices choice;

        printf("Select from the following by number:\n");

        for (choice = Quit; choice <= Blinking; ++choice )
            printf("%d. %s\n", choice, Choice_Words[choice]);

        printf("\nWhich: ");
            choice = getch();
            choice -= '0';
            if (choice < Foreground || choice > Blinking)
            Redraw( choice );
            } while (choice != Quit);


    Redraw(enum Choices field)
        struct screen_char {
            unsigned int character  :8,
                        foreground :3,
                        intensity  :1,
                        background :3,
                        blink      :1;
        } scrchar, far *sp, far *ep;

        sp = (struct screen_char far *)SCR_START;
        ep = sp + SCR_SIZE;

        while (sp < ep)
            scrchar = *sp;
            switch (field)
                case Foreground:
                    scrchar.foreground = (scrchar.foreground)? 0 : 7;
                case Intensity:
                    scrchar.intensity = (scrchar.intensity)? 0 : 1;
                case Background:
                    scrchar.background = (scrchar.background)? 0 : 7;
                case Blinking:
                    scrchar.blink = (scrchar.blink)? 0 : 1;
            *(sp++) = scrchar;

    Listing 11-8.  The SCRMENU.C program.

    SCRMENU.C combines bit fields with enum and the #define preprocessor
    directive to virtually rid the body of the program of obscure constructs.
    Also, notice that we use a pointer to a structure to access the screen.

Advanced typedef

    So far, we've used the #define preprocessor directive to create aliases,
    both for increased program clarity and as a shorthand method of entering
    repetitive code. We have also seen, in Chapter 3, that new types can be
    defined by using typedef. Superficially, #define and typedef appear to be
    interchangeable. To create simple aliases, you can use either one.
    Situations arise, however, in which typedef is suitable, but #define is

    For example, suppose you need to create a new type called string, an array
    of type char. Now suppose you attempt to create this new type with
    #define, as follows:

    #define string char s[128]

    You later would not be permitted to make the declaration

    string str1, str2;

    because the preprocessor would expand it to be

    char s[128] str1, str2;

    which is illegal. (Note the missing comma, among other things.) In
    situations such as this one, typedef is ideal. Rather than beginning with
    a #define directive, suppose you use the following:

    typedef char string[128];

    This creates a new type called string, which you can use later to declare
    variables of that new type:

    string str1, str2;

    Because we used typedef to define string, the compiler correctly
    translates this into

    char str1[128], str2[128];

    which is what we intended in the first place.

    The secret to using typedef is to follow three simple steps. First,
    declare an ordinary variable of the type you want:

    char s[128];

    Second, place the word typedef at the front:

    typedef char s[128];

    Third, replace the variable's name with the new type name:

    typedef char string[128];

    You can now use the newly defined type string exactly as you would one of
    C's built-in types, such as int.

    In addition to doing what #define cannot, typedef also lends clarity to
    otherwise obscure constructs. For example, consider the following two
    pointers to functions:

    int (*quit_fun)(), (*restart_fun)();

    This could be confusing if it were to appear throughout your program.
    Using typedef, however, you can create a new type called funptr:

    typedef (*funptr)();

    Now you can use funptr throughout your program to declare variables of
    that new type, as follows:

    funptr quit_fun, restart_fun;

    Use typedef judiciously──it is the most easily abused concept in C. The
    indiscriminate use of typedef, rather than making your program more
    readable, can make it more obscure and (sometimes) indecipherable.

Chapter 12  Large Projects

    As your programs become larger and more complex, revising and maintaining
    them become less straightforward. Consequently, as your programming skills
    increase, you inevitably will find yourself looking for more efficient
    ways of handling programs. For example, you might want to:

    ■  Use one function in several programs without having to retype it every

    ■  Compile a program one way for testing and another for actual use──
        without having to rewrite it.

    ■  Combine several .C files into a single program, while recompiling only
        those files that need to be changed.

    ■  Transport one of your programs to another machine or compiler and
        compile it without needing to rewrite it.

    This chapter offers solutions for these and other common programming
    needs. We'll discuss how to use the C preprocessor for conditional
    compilation and for creating macros. Next, we'll show you how to create
    and manage QuickC's "program lists." Finally, we'll show you how to
    develop custom C libraries and how to access them from within QuickC.

Advanced C Preprocessor

    Although compiling under QuickC appears to be a single swift process, it
    is actually three processes combined into one. First, your C program is
    "preprocessed." In this phase, conditional compilation occurs, and other
    preprocessing directives are executed: For example, #define MAX 3 converts
    all instances of MAX to 3. Second, the QuickC compiler translates your
    preprocessed code into machine language, or code that the computer can
    understand. Finally, your compiled machine code is combined (linked) with
    the precompiled code in the standard C Library of functions (such as
    printf()) to form the finished, executable program.

    Conditional compilation occurs in the preprocessing stage. Using lines
    that begin with a # character (pronounced "pound" or "number"), you can
    write code that compiles one way for testing and another for actual use.
    You can also write code that compiles differently on different machines or
    different compilers.

    The C preprocessor recognizes only lines of text that begin with the #
    character, such as #define and #include. Table 12-1 lists the complete
    set of these "preprocessor directives."

    Table 12-1 The Preprocessor Directives
    Directive       Description
    #define x y     Uses x as an alias for y throughout the program.
    #include <file> Reads file from the INCLUDE subdirectory and inserts it
                    into your program at this point.
    #include "file" Reads file from the current working directory and inserts
                    it into your program at this point.
    #ifdef x        If x is defined, compiles all program code between this
                    and the next matching #endif, #elif, or #else.
    #if (x)         If the integer constant expression x is true (nonzero),
                    compiles all program code between this directive and the
                    next matching #endif, #elif, or #else.
    Directive       Description
                    next matching #endif, #elif, or #else.
    #ifndef x       If x is not defined, compiles all program code between
                    this and the next matching #endif, #elif, or #else.
    #else           The inverse of the above three if directives. If the if is
                    true, the code before the #else is compiled. If the if is
                    false, the code following the #else is compiled.
    #elif (x)       The else if extension for #if in a chain of conditions.
    #endif          Terminates the current matching #if, #ifdef, or #ifndef.
    #line lineno    Sets the current line number to lineno and the current
    "file"          file to file.
    #pragma         Sets "compiler-specific" options.
    #define x(y) z  Defines preprocessor macros.

Conditional Compilation

    Occasionally, you will need to compile only part of your code──for
    example, during debugging, or when you compile different versions for
    different users, or while compiling your program on a different computer
    or compiler. The C preprocessor offers an assortment of directives to
    facilitate this selective compiling process, called "conditional

    The #if and #endif Directives

    The most frequently used conditional directives are #if and #endif. The
    #if directive tests what is known as a restricted constant expression in
    your code to see if that expression is zero. If it is a nonzero (true)
    value, QuickC compiles all the code between that #if and its matching
    #endif. Use the directive as follows:

    #define BYTES 4

    #if (BYTES == 4)
    /* compile this code */

    In this example, the expression (BYTES == 4) is a "constant expression"
    because it becomes (4 == 4) (the logical comparison of two integer
    constants). It is also a "restricted" constant expression, which is a
    constant expression that cannot contain:

    ■  sizeof operations

    ■  enumerated constants

    ■  typecasts

    ■  floating-point constants

    Therefore, the following directives are legal:

    #if (BYTES < 8)
    #if ((6 * 9 / 3) != (2 % 1))

    and the following are not:

    #if (sizeof(int) == 4)────────────────────────────────────sizeof illegal

    enum {true, false} yorn;
    #if (true == 0)──────────────────────────────Enumerated constant illegal

    #if (NULL == (char *)0)─────────────────────────────────Typecast illegal

    #if (MIN < 4.2)───────────────────────────────────float constant illegal

    One common use for the #define directive is in debugging. The program in
    Listing 12-1 on the following page, BUG.C, illustrates one possible way
    to use #define to change the behavior of your program. By using #define to
    define DEBUG_LEVEL to one of the values 0, 1, or 2, then recompiling and
    running, you will cause the program to print one of three messages to your
    screen. For a #define value of 0, nothing is printed; for 1, the calls to
    the subroutine sub() are documented; and for 2, entry into and exit from
    main() are printed.

    /* bug.c  --  shows how different levels of debugging  */
    /*            output can be produced using #if         */

    #define DEBUG_LEVEL 2     /* 0 = none, 1-2 for debug   */
    #include <stdio.h>

        int ret;

    #if (DEBUG_LEVEL == 2)
        fprintf(stderr, "Entering main()\n");

    #if (DEBUG_LEVEL == 1)
        fprintf(stderr, "Calling sub()\n");

        ret = sub();

    #if (DEBUG_LEVEL == 1)
        fprintf(stderr, "sub() returned %d\n", ret);

    #if (DEBUG_LEVEL == 2)
        fprintf(stderr, "Leaving main()\n");


        return (5);

    Listing 12-1.  The BUG.C program.

    defined and #ifdef

    You can use the defined keyword with the #if directive to detect whether
    or not a name has been specified by #define:

    #if defined(name)

    If defined(name) determines that name was used in a #define directive, it
    evaluates to true. The keyword defined is used by the preprocessor only in
    this context; therefore, you can use it anywhere in your program without
    causing a conflict.

    The defined variation of #if replaces the pre-ANSI directive #ifdef. That
    is, although the following are equivalent:

    #if defined(name)
    #ifdef name

    the first form is preferable.

    You can use the same technique to see if a name has not been specified
    with #define, as follows:

    #if !defined(name)
    #ifndef name

    Again, the first form is preferable to the second.

    The defined variation of #if is especially useful for writing programs
    that will be compiled on another type of computer or a different compiler.
    The BITOUT.C program (Listing 12-2) is an adaptation of the Bitout()
    function used in the BITWISE.C program (Listing 7-12 on p. 218). After
    the user enters an integer, the program prints that integer in binary
    form. Note that it uses #if defined to print the bits one way on an
    80286-based computer and another way on a 68000-based machine.

    /* bitout.c  -- compiles one way on an IBM PC and      */
    /*              another on a 68000 chip─based machine  */

    #define CHIP_80286    /* don't define on a 68000 machine */
    #include <stdio.h>

        int num;

        printf("Enter an integer number and I will print"
                " it out in binary\nNumber: ");

        if (scanf("%d", &num) != 1)
            fprintf(stderr, "Not an integer\n");

    Bitout(unsigned int num)
        int i, j;
        unsigned char *cp;

        cp = (char *)&num;
    #if defined(CHIP_80286)    /* IBM PC */
        for (i = 1; i >= 0; --i)
    #if !defined(CHIP_80286)   /* otherwise 68000 machine */
        for (i = 0; i < 4; ++i)
            for (j = 7; j >= 0; --j)
                putchar((cp[i] & (1 << j)) ? '1' : '0');

    Listing 12-2.  The BITOUT.C program.

    #else and elif

    We can simplify the two #if directives in BITOUT.C by using the #else

    #if defined(CHIP_80286)
        for (i = 1; i >= 0; --i)
        for (i = 0; i < 4; ++i)

    In this example, the preprocessor compiles the first for statement if
    CHIP_80286 has been defined using #define; otherwise, it compiles the
    second for statement.

    By using the #elif (else if) directive, you can create a whole chain of
    conditions. The following series of directives, for example,

    #if defined(CHIP_8086)
        for (i = 1; i >= 0; --i)
    #elif defined(CHIP_80286)
        for (i = 1; i >= 0; --i)
    #elif defined(CHIP_68000)
        for (i = 0; i < 4; ++i)
        fprintf(stderr, "Unknown chip\n");

    tells the preprocessor to compile the first for statement if CHIP_8086 is
    defined, to compile the second for statement if CHIP_80286 is defined, or
    to compile the third for statement if CHIP_68000 is defined. If none of
    these is defined, the preprocessor compiles code to print an error and

    Logical Operators and #if

    Many of the preceding #if tests use similar code. You can take a coding
    shortcut by combining #if expressions using the C logical operators && and
    ||. For example, you can shorten the previous #elif sequence by using the
    logical OR operator as follows:

                            ┌──────────────────────────────────── Logical OR
    #if defined(CHIP_8086) || defined(CHIP_80286)
        for (i = 1; i >= 0; --i)
    #elif defined(CHIP_68000)
        for (i = 0; i < 4; ++i)
        fprint(stderr, "Unknown chip\n");

    The #if directives and their corresponding #endif and #elif directives can
    be nested. However, when you nest them, we recommend that you use indents
    to show the levels of nesting, as follows:

    #if defined(IBMPC)
        #if defined(CGA) || defined(EGA)
            sp = (int far *)0xB8000000;
            sp = (int far *)0xB0000000;
        fprintf(stderr, "No Screen Memory\n");

    In this example, if IBMPC is not defined, the last #else executes. If
    IBMPC is defined, the program checks to see if either CGA or EGA (for the
    corresponding graphic adapter cards) is defined. If either is, we assign
    the address value 0xB8000000 (the location of screen memory for those
    cards) to the pointer sp. Otherwise, we use the address 0xB0000000 (the
    location of screen memory for the regular monochrome adapter).

    You can avoid problems when using # preprocessor directives by remembering
    two general rules. First, the # must always begin a line. Second, each
    directive can occupy only one line unless you extend it by typing a
    backslash and pressing Enter:

    #if defined(EGA) \─────────────────────────────────────────Line extended
        ||    \────────────────────────────────────────────────Line extended

    Predefined Names

    QuickC always predefines two names: __FILE__ and __LINE__. (Note that both
    have two leading and two trailing underscore characters.) The name
    __FILE__ is always the name of the current C source file being compiled.
    It is a quoted string constant, so you can safely use it anywhere that
    strings are legal. The predefined name __LINE__ is an integer constant
    number that is always the current line number in the current file. You can
    use it anywhere as a legal integer constant.

    These two predefined names are generally used to print meaningful
    diagnostics during debugging. The ERR.C program (Listing 12-3)
    demonstrates their use for tracing the flow of a small program. By placing
    a #define ERR inside a #if directive, you can turn on and off custom
    tracing with a single change in code:

    #define TRACE 0      /* change to 1 to turn on */

    #if (TRACE > 0)
    #define ERR printf("Tracing: \"%s\" line %d\n",\
                        __FILE__, __LINE__ );
    #define ERR

    If TRACE is defined as a value greater than zero, QuickC traces the
    program. If, on the other hand, TRACE is 0, then tracing is disabled.

    /* err.c  --  illustrates __FILE__ and __LINE__ in */
    /*            tracing a small program              */

    #define ERR printf("Tracing: \"%s\" line %d\n",\
                        __FILE__, __LINE__);



    Listing 12-3.  The ERR.C program.

#pragma Instructions to the Compiler

    You can use the #pragma preprocessor directive to give compiler-specific
    instructions to the compiler (that is, instructions that usually must be
    given as part of the MS-DOS command line or by presetting QuickC's compile
    time options). Use it in the following way:

    #pragma instruction

    #pragma pack(1|2|4)

    The pack pragma tells the compiler to place structure members into memory
    on 1-byte, 2-byte, or 4-byte boundaries. Ordinarily, QuickC places
    structure members into memory so that int and long types always begin in
    an even address, which is equivalent to pack(2). (See Figure 12-1a.) By
    using the #pragma pack() preprocessor directive, you can tell the compiler
    to store structures in a smaller space (see Figure 12-1b) or to spread
    them out into a larger space with pack(4). (See Figure 12-1c.)

                            │ struct {
                            │   char a;
                            │   int b;
    For the declaration ──┤   char c;
                            │   long d;
                            │ };

                                                        400 │       │ ├─a
                                                        401 │       │ │
        ┌───────┐─┐                ┌───────┐─┐             ├─     ─┤ │
    402 │       │ ├─a          402 │       │ ├─a       402 │       │ ├─unused
        ├───────┤─┤                ├───────┤─┤             ├─     ─┤ │
    403 │       │ ├─unused     403 │       │ │         403 │       │ │
        ├───────┤─┤                ├─     ─┤ ├─b           ├───────┤─┤
    404 │       │ │            404 │       │ │         404 │       │ │
        ├─     ─┤ ├─b              ├───────┤─┤             ├─     ─┤ ├─b
    405 │       │ │            405 │       │ ├─c       405 │       │ │
        ├───────┤─┤                ├───────┤─┤             ├───────┤─┤
    406 │       │ ├─c          406 │       │ │         406 │       │ ├─c
        ├───────┤─┤                ├─     ─┤ │             ├───────┤─┤
    407 │       │ ├─unused     407 │       │ │         407 │       │ ├─unused
        ├───────┤─┤                ├─     ─┤ ├─d           ├───────┤─┤
    408 │       │ │            408 │       │ │         408 │       │ │
        ├─     ─┤ │                ├─     ─┤ │             ├─     ─┤ │
    409 │       │ │            409 │       │ │         409 │       │ │
        ├─     ─┤ ├─ d             └───────┘─┘             ├─     ─┤ ├─d
    410 │       │ │                                    410 │       │ │
        ├─     ─┤ │                                        ├─     ─┤ │
    411 │       │ │                                    411 │       │ │
        └───────┘─┘                                        └───────┘─┘

        #pragma pack (2)            #pragma pack (1)        #pragma pack (4)
        (QuickC default)

    Figure 12-1. The pack() pragma determines how structures are placed
    into memory.

    The PACK.C program (Listing 12-4) illustrates this structure packing.
    When you run the program, note the addresses it prints. Then change the 1
    in #pragma pack(1) to a 2 and recompile and run PACK.C again. Finally,
    change that 2 to a 4 and repeat the process.

    An extension to the #pragma pack() directive lets you turn packing on and

    #pragma pack(1)─────────────────────────────────────Set one-byte packing
    #pragma pack() no───────────────────────────────────────Turn packing off
    #pragma pack() yes──────────────────────────────────Turn packing back on

    The example first tells the compiler to pack all structure members to the
    nearest 1-byte boundary. Next, the no tells the compiler to stop packing
    and revert to its default even-byte boundary arrangement. Finally, the yes
    tells the compiler to resume packing on 1-byte boundaries.

    /* pack.c  --  demonstrates structure packing with */
    /*             the #pragma pack() directive        */

    #pragma pack(4)        /* 1, 2 or 4 */

        struct {
            char ch1;
            int  int1;
            char ch2;
            long int2;
        } s;

        printf("ch1  -> %lu\n", (unsigned long)(&s.ch1));
        printf("int1 -> %lu\n", (unsigned long)(&s.int1));
        printf("ch2  -> %lu\n", (unsigned long)(&s.ch2));
        printf("int2 -> %lu\n", (unsigned long)(&s.int2));

    Listing 12-4.  The PACK.C program.

    Quick Tip
    The Intel 80386 chip executes at its fastest if int and long types begin
    on modulo 4-byte address boundaries. The Intel 80286 and earlier chips
    execute fastest when those types begin on even addresses. If size is more
    important to you than speed, use the #pragma pack(1) directive.

Preprocessor Macros

    The #define preprocessor directive has a second form that is called a
    #define macro, or a preprocessor macro. The #define macro is an extremely
    powerful tool, used by programmers to place "in-line" code into a program
    in a manner that resembles a subroutine call. Take a moment to use
    QuickC's View Include feature to look at the <stdio.h> header file. Notice
    in line 105 of that file that the getc() function you have been using all
    along is not really a function at all. It is a #define macro. Because it
    is a macro, the preprocessor expands each occurrence of getc(stdin) in
    your program to the following:

    (────(stdin)->cnt >= 0 ? 0xFF & *(stdin)->_ptr++ : _filbuf(stdin))

    Certainly, it is easier to type getc(stdin) than to type this complex code

    The form for a #define macro is as follows:

    #define TRIPLE(x) (x*3)

    In this example, the defined name is TRIPLE and the (x) is its formal
    argument. The expression TRIPLE(x) is defined as an alias for the
    expression (x*3). This means that anywhere in the program that you use the
    following expression:


    the actual argument (here 2) replaces every occurrence of the formal
    argument, x, in the original definition. This produces the following

        ├───────────────────────────────────────────────────────── Expands to

    To illustrate further, examine the following macro definition for MAX, a
    macro that compares two values and yields a new value that is the higher
    of the two:

                ┌─┬──────────────────────────────────── Two formal arguments
    #define MAX(x,y) (((x) > (y)) ? (x) : (y))          separated by a comma
            └──┬───┘ └──────────┬───────────┘
                │                └────────────────────────── Macro definition
                └────────────────────────────────────────────────────── Macro

    This example shows that macros can take more than one formal argument──but
    arguments must be separated from one another by commas. The x in the macro
    replaces each x in the macro definition with its corresponding actual
    argument, and each y replaces its corresponding y. If you use the above
    macro definition in your code and then use the following expression:

    oldest = MAX(age1, age2);

    with int variables age1 and age2, the preprocessor expands the macro as

    oldest = (((age1) > (age2)) ? (age1) : (age2));

    Potential Problems with Macros

    Use preprocessor macros with care──actual arguments to macros can cause
    unexpected changes, such as reading an extra character. You should avoid
    using the following types of arguments because they can produce unwanted
    side effects:

    ■  function calls

    ■  other macros

    ■  the increment (++) and decrement (--) operators

    ■  the assignment operator (=)

    For example, consider the following ISQ macro:

    #define ISQ(letter) ((letter) == 'q' || (letter) == 'Q')

    This macro detects whether a letter is an uppercase or lowercase 'Q' and
    is useful for testing if a user is quitting a program. You correctly use
    this macro as follows:

    ch = getchar();

    if (ISQ(ch))

    In the preceding code, ch is a char variable; therefore, the if statement
    expands to

    if (((ch) == 'q' || (ch) == 'Q'))

    which is what you expect. However, if you use this macro incorrectly──for
    example, with a function call such as getchar(),

    if (ISQ(getchar()))

    it expands to an expression that doesn't do what you expect:

    if (((getchar()) == 'q' || (getchar()) == 'Q'))

    This example illustrates a common problem. The first call to getchar()
    reads a character and compares the value to 'q'. If that character is not
    a 'q', getchar() is called again to read a new character and to compare
    the new character to 'Q'. This is not what you intended, however. You want
    MAX to read only the first character and then to compare that character to
    both 'q' and 'Q'.

    Macros and Semicolons

    Never end a macro definition with a semicolon. For example, the following
    macro converts a printable character into a control character value:

    #define CTRL(x) ('x' - '@');

    The expression CTRL(A) expands to the expression ('A' - '@'); and yields
    the desired ASCII value 1 (Ctrl-A). However, the trailing semicolon causes
    a syntax error when you use the macro in an expression such as:

    printf("And 'A' prints as %c\n", CTRL(A));

    Note the syntax error that results when this expands to

        printf("And 'A' prints as %c\n", ('A'-'@'););
                                                └────────────────────── Wrong

    Macros and Quotes

    As in normal #define directives, preprocessor macros do not substitute
    actual arguments inside full quotation marks. For example, the following
    macro would be a useful tool for debugging:

    #define PERR(x) printf("The value of x is %d\n", x)

    Unfortunately it won't work. Because the first x is inside full quotation
    marks, it isn't expanded. However, the final x is expanded:

    int val = 5;
            └───────────┬─────────────────────────────────────────── Expands to
    printf("The value of x is %d\n", val);

    We can rectify this by using the preprocessor's "stringizing" operator #.
    When placed before a formal argument in a macro definition, the # causes
    that argument to be expanded and quoted. Thus, the correct way to define
    PERR is as follows:

    #define PERR(x) printf("The value of " #x " is %d\n", x)
                                            └─────────── Stringizing operator

    This correctly expands as:

    int val = 5;
        └───────────┬──────────────────────────────────────────── Expands to
    printf("The value of " "val" " is %d\n", val);

    The example works because the compiler joins adjacent quoted string
    constants into a single string. The result is that printf() correctly
    prints the following:

    The value of val is 5

Using QuickC for Large Projects

    Imagine you are writing a text editor program such as the one shown in
    Figure 12-2 on p. 381. With sufficient memory, QuickC can easily load and
    compile programs of this size. However, the larger a program is, the
    longer it takes to compile, load, and save. Therefore, you can manage
    large programs more easily when you break them into several smaller files
    by logically grouping the subroutines according to use. This approach has
    several advantages.

    ■  When a program consists of several files, you need to recompile only
        those files that change.

    ■  Grouping subroutines by usage lets you easily trace the logic of the
        program during debugging.

    ■  Perfected subroutines that no longer need to be recompiled can be
        shared by many programs.

QuickC Program Lists

    The QuickC "program list" feature compiles several small files or library
    modules and combines them into a single executable program. This lets you
    create complex, large programs from many small, easily maintained files.
    Before we examine this feature, enter and save the following three files:
    TEXED.C (Listing 12-5), KEYS.C (Listing 12-6), and FILE.C (Listing
    12-7 on the following page). These are three small pieces of our
    imaginary text editor in Figure 12-2. Although these modules don't do
    much, they demonstrate the basics of using QuickC program lists.

    /* texed.c  --  main entry point to the editor; the   */
    /*              menu and signal handlers are here     */

    main(argc, argv)
    int argc;
    char *argv[];
        char ch;

        while (1)
            printf("\nTexEd Main Menu\n");
            printf("Select from:\n");
            printf("0) Quit\n\n");
            printf("1) Load File\n");
            printf("2) Save File\n");
            printf("3) Edit File\n");
            printf("Which: ");
                ch = getch();
                ch -= '0';
                } while (ch < 0 || ch > 3);
            printf("%d\n\n", (int)ch);
                case 0: exit(0);
                case 1: Load_file(); break;
                case 2: Save_file(); break;
                case 3: Edit_file(); break;

    Listing 12-5.  The TEXED.C file.

    2000-line text editor                Broken into separate files
    ┌───────────────────┐               ┌─┌───────────────────┐
    │                   │               │ │ Main()            │
    │                   │               │ │ Signal handlers   │────texted.c
    │                   │               │ │ menu()            │
    │                   │               │ ├───────────────────┤
    │                   │               │ │                   │
    │                   │               │ │ Read and process  │────keys.c
    │                   │               │ │ typed keys        │
    │                   │               │ ├───────────────────┤
    │                   │               │ │                   │
    │                   │───texted.c───►│ │ Update the        │────screen.c
    │                   │               │ │ screen            │
    │                   │               │ ├───────────────────┤
    │                   │               │ │                   │
    │                   │               │ │ Special commands  │────cmds.c
    │                   │               │ │ like 'delete line'│
    │                   │               │ ├───────────────────┤
    │                   │               │ │                   │
    │                   │               │ │ File read and     │────file.c
    │                   │               │ │ write routines    │
    └───────────────────┘               └─└───────────────────┘

    Figure 12-2. A large program is often best split into several smaller and
    more manageable files.

    /* keys.c  --   The keyboard input-handling routines  */
    /*              for the texed editor                  */

        char ch;

        printf("\nYou are now in the editor.\n");
        printf("Press 'Q' to exit back to main menu.\n");

            ch = getch();
            } while (ch != 'Q');


    Listing 12-6.  The KEYS.C file.

    /* file.c  --  the file I/O routines for texed */

        printf("\nLoading ..... done.\n");

        printf("\nSaving ...... done.\n");

    Listing 12-7.  The FILE.C file.

    Next select Set Program List from the File menu and enter texed.mak in the
    File Name text box. This program list name is composed of two parts. The
    first, texed, is the name of your finished program. The second, the
    extension .mak, signifies that this program list file is a "make" file.
    (We'll explain make files in the next section.)

    After you enter the name texed.mak, QuickC prompts "`texed.mak' does not
    exist Create?". A Yes response displays the Edit Program List dialog box.
    This is where you specify the files in your program list. Enter the
    filenames TEXED.C, KEYS.C, and FILE.C. As you enter each, its name appears
    in the bottom window labeled Program List. After you enter all three
    files, your screen appears as in Figure 12-3. Now choose the Save List
    option to save your program list on disk and return to the main QuickC

    │ Figure 12-3 can be found on p.382 of the printed version of the book.  │

    Figure 12-3. The Edit Program List dialog box.

    At the bottom left of the QuickC screen you now see Program List: Texed.
    This message signals that you will build your program from several files,
    not only from the one currently loaded, and that those files are in the
    program list named Texed. TEXED.MAK has three files in it, and TEXED.C is
    one of them. Now, every time you open TEXED.C, QuickC reminds you that a
    .MAK file and various other files are connected to it.

    To compile a program from a program list, display the Compile dialog box,
    but this time, instead of selecting Compile File, select Build Program.
    Notice that each of your files is loaded in turn and compiled. After all
    three have been compiled, the Microsoft Overlay Loader executes. This link
    program combines your compiled files, along with any precompiled routines
    that you use from the standard C Library (such as printf()). This process
    creates a single, executable program that you can run from within QuickC.

Program List Files

    Program list files contain rules and instructions that tell QuickC how to
    build your program. They are composed of four elements: comment lines
    (lines that begin with a # character), production rules, dependencies, and
    link commands. Look inside the TEXED.MAK file that follows. This is a make
    file, a subset of the kind used by the MAKE program.

    # Program; Texed

    .c.obj:───────────────────────────Production rule--turn a .C into a .OBJ
        qcl  -c  -W1  -Ze  -AM $*.c─────────How to accomplish the above rule

    texed.obj : texed.c────────────┬────────────────────────────Dependencies
    keys.obj : keys.c──────────────┤
    file.obj : file.c──────────────┘

    Texed.exe : texed.obj keys.obj file.ob─More dependencies used to go from
            del Texed.lnk                     .OBJs to. EXEs
            echo texed.obj+ >>Texed.lnk
            echo keys.obj+ >>Texed.lnk
            echo file.obj >>Texed.lnk
            echo Texed.exe >>Texed.lnk
            echo Texed.map >>Texed.lnk
            link @Texed.lnk /NOI $(LDFLAGS);

    Production Rules in Program List Files

    A production rule is a description of how one file type is changed into
    another. For example, the production rule in our program list

        └──┴────────────────────────────────────Turn a .C file into a .OBJ file

    tells QuickC to change .C files (such as KEYS.C) into precompiled object
    files called .OBJ files (such as KEYS.OBJ). Microsoft's Overlay Loader
    later links these .OBJ files with functions from the standard C Library to
    produce the executable program.

    The line following the production rule is the command line:

    qcl -c -W1 -Ze -AM $*.c

    This tells QuickC how to make the transformation specified in the first
    line. It is the same command line you would enter at the MS-DOS prompt,
    except for the $*.c. The $*.c tells QuickC to use the name of a real .C
    file, such as KEYS.C, at this position in the command line. The other
    elements represent the following:

    Element  Description
    qcl      The command-line version of QuickC.
    -c       Compile to a .OBJ file and stop. That is, do not continue by
            calling LINK.
    -W1      Set the compile time warning level to level 1.
    -Ze      Handle language extensions, such as far, as reserved keywords.
    -AM      Use the medium-memory model.

    Production rules are one of the features of QuickC program lists that make
    building programs easy. For example, when you select the Compile menu to
    compile KEYS.C, you don't have to enter the command line. Instead, QuickC
    runs the following MS-DOS command:

    qcl -c -W1 -Ze -AM keys.c

    and compiles KEYS.C into KEYS.OBJ.

    Dependency Lines in the Program List File

    Dependency lines tell QuickC how to create one file from two or more
    files. A dependency has the following form:

    target : infile1 infile2 ...

    This tells QuickC to create a new "target" file (usually a .OBJ or .EXE)
    if any infiles have changed and to make that target by running the
    specified MS-DOS command line. Examine the following dependency lines in

    texed.obj : texed.c

    keys.obj : keys.c

    file.obj : file.c

    Texed.exe : texed.obj keys.obj file.obj
            del Texed.lnk

    Note that each of the first three lines is followed by a blank line (no
    MS-DOS command line). We'll discuss these first three dependencies first,
    then we'll cover the last in detail.

    When a dependency does not specify an MS-DOS command line, QuickC uses the
    previously defined production rule (.c.obj:) in place of the missing
    command line. For the first three lines, then, the command line derived
    from the production rule becomes the following:

    texed.obj : texed.c
        qcl -c -W1 -Ze -AM texed.c

    keys.obj : keys.c
        qcl -c -W1 -Ze -AM keys.c

    file.obj : file.c
        qcl -c -W1 -Ze -AM texed.c

    Running the Linker

    The dependency for TEXED.EXE, the executable file of your finished
    program, is as follows:

    Texed.exe : texed.obj keys.obj file.obj───────────────────────Dependency

    The dependency tells QuickC to create TEXED.EXE from TEXED.OBJ, KEYS.OBJ,
    and FILE.OBJ.

    The LINK program combines your .OBJ files with subroutines from the
    standard C Library and creates an executable (.EXE) program as the result.
    When you run the LINK program, it asks you the following series of

    Object Modules [.OBJ]:
    Run File [.EXE]:
    List File [NUL.MAP]:

    First, LINK asks for the names of the .OBJ files you want to combine to
    form your program. Add a + to each file that you specify to tell LINK that
    you will add more .OBJ files. The LINK program continues to prompt for
    object modules until you list one without a trailing +:

    Object Modules [.OBJ]:texed.obj+
    Object Modules [.OBJ]:keys.obj+
    Object Modules [.OBJ]:file.obj

    Next, LINK prompts for the name of your executable program (Run File). In
    our example, we enter the name texed.exe.

    Finally, LINK asks for the name of a "map" file. A map file merely
    contains a cross-referenced listing of your executable program. Therefore,
    you respond as follows:

    List File [NUL.MAP]:texed.map

    Using LINK from a Text File

    When you run LINK, you have the option to use a text file that contains
    the prewritten answers to its questions. To do this, when you run LINK,
    specify the file by preceding its name with an "at" character, @, as

    link @Texed.lnk
                └──────── File questions containing answers to LINK's questions

    To understand how this works, let's examine the MS-DOS commands that
    follow the dependency for TEXED.EXE in your TEXED.MAK program list file:

    Texed.exe : texed.obj keys.obj file.─────────────── Dependency
            del Texed.lnk──────────────────────┐
            echo texed.obj+ >>Texed.lnk        │
            echo keys.obj+ >>Texed.lnk         │
            echo file.obj >>Texed.lnk          ├─────── MS-DOS command lines
            echo Texed.exe >>Texed.lnk         │
            echo Texed.map >>Texed.lnk         │
            link @Texed.lnk /NOI $(LDFLAGS);───┘

    First, the file TEXED.LNK is deleted in case it already exists. Next, five
    "redirect and append" commands (>>) tell echo to place the text into the
    file TEXED.LNK. TEXED.LNK now contains the following lines:


    Finally, LINK executes using the file @TEXED.LNK as the file that contains
    the answers to its questions. This is equivalent to running LINK yourself
    and answering those questions as follows:

    Object Modules [.OBJ]:texed.obj+
    Object Modules [.OBJ]:keys.obj+
    Object Modules [.OBJ]:file.obj
    Run File [.EXE]:Texed.exe
    List File [NUL.MAP]:Texed.map

    Other Arguments to LINK

    The LINK command in our TEXED.MAK program list file has two arguments in
    addition to the @TEXED.LNK argument:

        link @Texed.lnk /NOI $(LDFLAGS);
                        │       └──────────────────────────────── Other flags
                        └────────────────────────────────── Don't ignore case

    The first, /NOI, tells LINK not to ignore case. That is, it tells LINK to
    treat uppercase letters as different from lowercase letters in variable
    and function names.

    The second, $(LDFLAGS), is a make macro definition that QuickC defines as
    nothing. To modify your .MAK program list file and add arguments to LINK,
    you must define LDFLAGS. See Chapter 11 in your Microsoft QuickC
    Programmer's Guide for information about this procedure. But beware, the
    LDFLAGS macro is the only macro that QuickC recognizes and preserves.

Keeping Track of Changes

    QuickC keeps track of which files have changed in a program list. To see
    how this works, select Build Program from the Run menu. Now load the
    KEYS.C file and change it by inserting a blank line anywhere. Save that
    file and select Build Program again. This time, QuickC recompiles the
    KEYS.C file, but it does not compile the other two .C files because they
    haven't changed.

    Before QuickC runs a command line to create the target file from the
    infiles (based on a dependency in its program list), it first checks the
    modification dates for the target file and for each of the infiles. If
    this target was created after the infiles, QuickC doesn't need to
    recompile because the infiles have not changed. Specifically, in the

    texed.obj : texed.c

    if TEXED.OBJ is newer than TEXED.C (its modification date and time is more
    recent), then QuickC does not recompile because TEXED.C has not changed
    since the last time it was compiled. But if TEXED.C is newer or if
    TEXED.OBJ doesn't yet exist, QuickC creates a new TEXED.OBJ by applying
    the .c.obj production rule and thus running the MS-DOS command line:

    qcl -c -W1 -Ze -AM texed.c

    This ability to know which files need to be recompiled makes QuickC a
    powerful tool for developing large and complex programs that are composed
    of many individual .C files.

Header Files

    Programs formed from separate .C files often share identical declarations.
    For example, examine the two files in Listings 12-8a and 12-8b on the
    following page. These parts of a larger text editor program both use
    structures of the same pattern, and both use the #define directive to
    define the values OK and ERROR. (These listings are not intended to be
    compiled and run independently.) If you need to change the structures (by
    adding a member, for example), or to change the definition of ERROR (from
    1 to -1, for example), you must make changes in both files (and possibly
    many other files if the text editor program uses those values throughout).

    #define OK 1
    #define ERROR 0
        struct key_struct {
            char key;
            unsigned char move;
        } *kp, *Read_kbd();
        int cur_key, cur_move;

        kp = Read_kbd();
        cur_key = kp->key;
        cur_move = kp->move;
        if (cur_key == ERROR)
            return (cur_move);
        return (cur_key);

    Listing 12-8a.  The TEXED.C file.

    #define OK 1
    #define ERROR 0
    struct key_struct {
        char key;
        unsigned char move;

    struct key_struct *Read_key()
        struct key_struct k;

        k.key = getch();
        if (k.key == ERROR)
            k.move = getch();
        return (&k);

    Listing 12-8b.  The KEYS.C file.

    Therefore, your program is easier to maintain if you gather such common
    definitions into a single, separate file called a "header file," or a .h
    file. Listing 12-9 shows one such header file for our text editor
    program. Now you can easily make changes that affect all files. Simply
    modify TEXED.C and KEYS.C to use the #include preprocessor directive, as
    shown in Listings 12-10a and 12-10b. Because we use full quotation marks
    with that directive (rather than angle brackets as with #include
    <stdio.h>), the compiler looks for the header file in our current working

    #define OK 1
    #define ERROR 0

    struct key_struct {
        char key;
        unsigned char move;

    Listing 12-9.  The texed.h header file.

    #include "texed.h"
        struct key_struct *kp, *Read_kbd();
        int cur_key, cur_move;

        kp = Read_kbd();
        cur_key = kp->key;
        cur_move = kp->move;
        if (cur_key == ERROR)
            return (cur_move);
        return (cur_key);

    Listing 12-10a.  The TEXED.C file (modified).

    #include "texed.h"
    struct key_struct *Read_key()
        struct key_struct k;

        k.key = getch();
        if (k.key == ERROR)
            k.move = getch();
        return (&k);

    Listing 12-10b.  The KEYS.C File (modified).

    Variables in Header Files

    You can also place declarations in header files that make variables global
    to all files. However, you cannot initialize variables in header files
    that are shared by more than one .C file. That is, in the header file

    char Last_key;─────────────────────────────────────────────────────Legal
    int  Upper_flag = 1;─────────────────────────────────────────────Illegal

    the declaration for Last_key is always legal, but the declaration for
    Upper_flag is illegal because this .h file is specified by #include in
    several .C files.

    You can declare and initialize a global variable only once in a program.
    If you want to declare and initialize a global variable in one file and
    access that variable from another file, you must make this an explicit
    operation by placing the extern keyword in the second file, as follows:

    /* First file */  /* Second file */  /* Third file */
    ...               ...                ...
    int Key = 1;      extern int Key;    extern int Key;
                └────────────────────────────────────── Initialized once among
                                                        several files

    The extern keyword tells QuickC that the integer Key is located in another

    If a global variable is not initialized as part of its declaration, you
    can declare it in all files without the extern keyword, as follows:

    /* First file */  /* Second file */  /* Third file */
    ...               ...                 ...
    int Key = 1;      extern int Key;    extern int Key;

    The extern keyword tells QuickC that the integer Key is located in another

    If a global variable is not initialized as part of its declaration, you
    can declare it in all files without the extern keyword, as follows:

    /* First file */  /* Second file */  /* Third file */
    ...               ...                ...
    int Key;          int Key;           int Key;

    This is the same as declaring it once in a header file and then specifying
    that header with #include, as follows:

    /* Header file "head.h" */
    int Key;

    /* First file */  /* Second file */  /* Third file */
    ...               ...                ...
    #include "head.h" #include "head.h"  #include "head.h"

    Dependencies in Header Files
    Because a change in a header file results in a change in a .C file, you
    might wonder if you can place header files into your QuickC program list
    as a dependency, as follows:

    texed.obj :  texed.c texed.h

    keys.obj : keys.c texed.h

    As this dependency is written, it tells QuickC (in our program list) to
    recompile TEXED.OBJ if either TEXED.C or texed.h changes and to recompile
    KEYS.OBJ if either KEYS.C or texed.h changes.

    QuickC does allow you to place header file dependencies into your program
    lists: It recognizes and maintains them, but it does not treat .h files as
    real dependencies. That means, for example, that TEXED.OBJ is not
    recompiled if only texed.h changes. This is intentional and not a bug.


    In addition to listing .C files in a QuickC program list file, you can
    also list library (.LIB) files. Libraries are files that contain
    precompiled .OBJ files that you can use as part of a program.

    During the course of your programming, you will develop many general
    subroutines that can be used in many programs. By placing those
    subroutines into a special library, you can access them through a QuickC
    program list without having to recompile them. For example, consider the
    following three subroutines: leftstr.c (Listing 12-11), midstr.c (Listing
    12-12), and rightstr.c (Listing 12-13). (These subroutines, shown on
    pages 392-93, are C analogs to the BASIC functions LEFT$, MID$, and

    To create a library for these three subroutines, enter them using the
    QuickC editor, and then save each as an individual .C file. Now exit
    QuickC and compile each with qcl and the following MS-DOS commands:

    qcl /c /AM leftstr.c
    qcl /c /AM midstr.c
    qcl /c /AM rightstr.c

    qcl is the command-line version of QuickC. The /c tells QuickC to create a
    .OBJ file from the .C file, and /AM tells QuickC to use the medium-memory

    After you generate the three .OBJ files, you create a library for them by
    running the LIB program and answering its questions, as follows:

    Library name:basic.lib
    Library does not exist. Create?y
    List file:

    The first and second lines tell LIB to create a library named BASIC.LIB.
    In the three Operations: lines, the + tells LIB that we are adding a .OBJ
    file to the library. The & following two of the lines is a signal that
    more files will be added. At the List file prompt we simply press Enter
    because our library is small, and we don't need a list of its contents.
    After a short wait, QuickC produces a library file named BASIC.LIB that we
    can place into any program list. To return to the QuickC menu, enter exit
    at the MS-DOS prompt.

    Now we'll create a program to test our library and demonstrate how to use
    a library from a program list. Enter the TEST.C program (Listing 12-14 on
    p. 393) and save it on disk. Next, choose Set Program List from the File
    menu and enter TEST.MAK as the name of the program list.

    After you press Enter and answer Yes to Test.mak doesn't exist. Create?,
    the Edit Program List dialog box appears. Select test.c as the first item
    in the list. Notice that the name of our library is not displayed. That's
    okay; simply type basic.lib. Finally, save this program list.

    /* leftstr.c -- a C version of BASIC's LEFT$ */

    #include <stdio.h>

    char *Leftstr(char *str, int cnt)
        static char *cp = NULL;
        char *malloc();

        if (cnt > strlen(str))
            cnt = strlen(str);
        if (cp != NULL)
        if ((cp = malloc(cnt + 1)) == NULL)
            return (NULL);
        strncpy(cp, str, cnt);
        return (cp);

    Listing 12-11.  The leftstr.c subroutine.

    /* midstr.c -- a C version of BASIC's MID$  */

    #include <stdio.h>

    char *Midstr(char *str, int where, int cnt)
        static char *cp = NULL;
        char *malloc();

        if (cnt > strlen(str + where))
            cnt = strlen(str + where);
        if (cp != NULL)
        if ((cp = malloc(cnt + 1)) == NULL)
            return (NULL);
        strncpy(cp, str+where, cnt);
        return (cp);

    Listing 12-12.  The midstr.c subroutine.

    /* rightstr.c -- a C version of BASIC's RIGHT$ */

    #include <stdio.h>

    char *Rightstr(char *str, int cnt)
        static char *cp = NULL;
        char *malloc();

        if (cnt > strlen(str))
            cnt = strlen(str);
        if (cp != NULL)
        if ((cp = malloc(cnt + 1)) == NULL)
            return (NULL);
        strcpy(cp, str + strlen(str) - cnt);
        return (cp);

    Listing 12-13.  The rightstr.c subroutine.

    /* test.c -- tests the routines in basic.lib */
    /* Program list: test.c and basic.lib        */

    #include <stdio.h>

        static char string[] = "This is a test.";
        char *cp, *Leftstr(), *Midstr(), *Rightstr();

        printf("Testing: \"%s\"\n", string);

        if ((cp = Leftstr(string, 4)) == NULL)
            printf("Error in Leftstr()\n");
        printf("Leftstr() returned: \"%s\"\n", cp);

        if ((cp = Midstr(string, 4, 5)) == NULL)
            printf("Error in Midstr()\n");
        printf("Midstr() returned: \"%s\"\n", cp);

        if ((cp = Rightstr(string, 5)) == NULL)
            printf("Error in Rightstr()\n");
        printf("Rightstr() returned: \"%s\"\n", cp);

    Listing 12-14.  The TEST.C program.

    At the QuickC editor, choose Compile from the Run menu. Because we are
    compiling from a program list, use Build Program to compile TEST.C and
    then combine it with the subroutines in BASIC.LIB.

    One additional advantage offered by .LIB files is that you can place them
    in your environmental LIB directory. From there, QuickC can find them no
    matter where you are in the directory hierarchy. The result of all this is
    that you need only one copy of common subroutines in a single library, and
    you can access those subroutines through a program list from any

Quick Libraries

    QuickC offers another kind of library, called a Quick Library. This
    alternative library can be loaded into memory when you first run QuickC.
    The advantage it offers is that you don't need to use a program list to
    access the subroutines in it.

    Let's build a Quick Library using the same subroutines that we used to
    build BASIC.LIB: leftstr.c (Listing 12-11), midstr.c (Listing 12-12),
    and rightstr.c (Listing 12-13). Begin by running qcl to create the .OBJ

    qcl /c /AM leftstr.c
    qcl /c /AM midstr.c
    qcl /c /AM rightstr.c

    Now run LINK to create the Quick Library:

    Object Modules [.OBJ]:c:\lib\quicklib.obj +
    Object Modules [.OBJ]:leftstr.obj +
    Object Modules [.OBJ]:midstr.obj +
    Object Modules [.OBJ]:rightstr.obj /Q
    Run File [C:QUICKLIB.QLB]:basic.qlb /NOI
    List File [NUL.MAP]:
    Libraries [.LIB]:

    In this example, c:\lib\quicklib.obj is the full pathname of a special
    object file that you must use as the first listing in your Quick Library.
    (We've stored the file in the C:\LIB directory, but you can use any
    directory. We suggest that you specify your environmental LIB directory.)
    The + characters tell LINK that we will list more object files. The /Q
    after the last .OBJ tells LINK that this is a Quick Library. Then we
    specify basic.qlb as the name of our Quick Library and follow that name
    with /NOI, which tells LINK not to ignore case. Finally, we press Enter to
    skip the final prompts, List File and Libraries.

    Now you can have QuickC load the BASIC.QLB Quick Library every time you
    run QuickC. To do this, use the QuickC /l command-line argument, as

    qc /lbasic.qlb test.c

    This tells QuickC first to load the BASIC.QLB Quick Library and then to
    load TEST.C in the editor. (Before you do this on your system, erase the
    TEST.MAK program list; otherwise, QuickC will try to use the .OBJ files
    from the disk rather than from your new Quick Library.)

    Now, when you compile TEST.C, QuickC always finds the functions Leftstr(),
    Midstr(), and Rightstr() in memory. Notice how much faster TEST.C compiles
    when you use this approach.


Chapter 13  Keyboard and Cursor Control

    Almost every PC program needs to get information from the keyboard and to
    display information on a monochrome or color screen. So far, our programs
    have used the standard C Library functions such as getchar(), scanf(),
    putchar(), and printf(), and occasionally we've used command-line
    arguments. Using these approaches produces portable code. However, it also
    produces a bland interface that fails to take advantage of many PC
    capabilities. If you want your programs to do more than display mere text
    on the screen, study this and the next two chapters, which explore PC I/O.
    You will learn how to use function keys and cursor control keys, how to
    control the location and appearance of text on the screen, how to use
    color in text and in graphics, and how to construct graphic figures.

    In this chapter, we examine the keyboard and cursor control. We look at
    QuickC's numerous I/O functions and provide a more detailed discussion of
    the generic getchar() and the PC-specific getche() and getch(). We
    describe scan codes, show how to use ANSI.SYS to redefine keys and to
    provide cursor control, and discuss BIOS routines. Finally, we use the
    int86() function to create a library of BIOS-based screen-control and
    cursor-control functions.

Keyboard Input Functions

    You use the standard C I/O functions to read and to display a variety of
    input: characters, strings, integers, and floating-point numbers. But the
    standard input functions don't detect non-ASCII keys, such as the function
    keys. And they don't provide many of the input control features typically
    required by programs such as word processors, spreadsheets, and games. To
    get that control, we need to process input at a "lower" level than that of
    standard I/O functions.

    Three QuickC functions read keyboard input character by character:
    getchar(), getche(), and getch(). Each reads one character at a time and
    reports its value to the calling program. (Actually, getchar() is not a
    true function; instead, it is defined as a macro in stdio.h.)

Input Examples

    The programs on the opposite page illustrate how the three input functions
    respond to the same input──in this case, the input is the word hat
    followed by Enter.

    The GETCHAR.C program (Listing 13-1) produces the following output:

    Please enter a word.
    1.. 2.. 3..───────────────────────Counting delayed until you press Enter
    3 characters altogether

    Counting doesn't start until you type the word and press Enter.

    Next, look at GETCHE.C (Listing 13-2), which generates the following

    Please enter a word.
    h1.. a2.. t3.. <Enter>───────────────────────────────────Immediate count
    3 characters altogether

    This time each letter is counted as it is typed.

    Finally, examine GETCH.C (Listing 13-3), which produces the following

    Please enter a word.
    1.. 2.. 3..──────────────────Input not displayed 3 characters altogether

    This time the input is invisible; only the output is displayed.

    The functions behave differently, and you use them for different purposes.
    The getchar() function buffers and echoes input; getche() does not buffer
    input but echoes it; getch() neither buffers nor echoes input. Buffered
    input goes into a temporary storage area before being transferred to the
    calling program. (Pressing Enter "empties" the buffer.) Echoed input is
    displayed on the screen.

    The getchar() function handles arrow keys or function keys inconsistently
    from one system to another. Try using GETCHAR.C with these keys as input
    and see how your system responds. The getche() and getch() functions do
    read these keys in a consistent manner, however. Try GETCHE.C, for
    example, with an arrow key or function key as input. Each of these keys,
    as you'll see, is counted as two keystrokes, and characters other than
    those you typed are echoed on the screen. This is perfectly proper and
    reasonable behavior, as you'll see when we discuss scan codes.

    /*      getchar.c -- using getchar()         */

    #include <stdio.h>
        int count = 1;

        printf("Please enter a word.\n");
        while (getchar() != '\n')       /* here it is */
            printf("%d.. ", count++);
        printf("\n%d characters altogether\n", count - 1);

    Listing 13-1.  The GETCHAR.C program.

    /*     getche.c -- using getche()             */
    #include <conio.h>     /* note different file included */
        int count = 1;

        printf("Please enter a word.\n");
        while (getche() != '\r')    /* changed comparison */
            printf("%d.. ", count++);
        printf("\n%d characters altogether\n", count - 1);

    Listing 13-2.  The GETCHE.C program.

    /*     getch.c -- using getch()             */
    #include <conio.h>
        int count = 1;

        printf("Please enter a word.\n");
        while (getch() != '\r')
            printf("%d.. ", count++);
        printf("\n%d characters altogether\n", count - 1);

    Listing 13-3.  The GETCH.C program.

The getchar() Buffer

    The program using getchar() doesn't receive the generated code until this
    buffer is flushed. This occurs when you press Enter or when the buffer is
    filled. Because the getchar() buffer is 512 bytes, normal keyboard input
    does not fill it. QuickC sets up this input buffer when any input function
    from the stdio.h family is called, and all the input functions of that
    family, such as scanf() and gets(), share it. Thus, when your program uses
    both scanf() and getchar(), they share the same input buffer.

Differences in Usage

    First, getchar() requires the stdio.h include file, while getch() and
    getche() use conio.h, the include file for console I/O functions. Second,
    getch() and getche use \r instead of \n to represent the action of Enter,
    and they do not interpret Ctrl-Z as an end-of-file indicator.

    The reason for these last two differences is that getchar(), by default,
    reads input in the text mode, and getch() and getche() read input in the
    binary mode. In the text mode, as you may recall, the carriage
    return/linefeed combination is converted to a linefeed on input, and the
    linefeed is converted to a carriage return/linefeed on output. The binary
    mode makes no conversions. As a result, getchar() uses \n to detect the
    Enter key, but getch() and getche() must use \r.

    The second difference is that getchar(), when used in the text mode,
    recognizes the Ctrl-Z character as marking the end of a file. This lets
    you simulate the end-of-file condition from the keyboard by entering
    Ctrl-Z. The binary mode used by getche() and getch() does not recognize
    Ctrl-Z (or any other character) to mark the end of a file. As a result,
    constructions such as

    while((ch = getche()) != EOF)     /* NO */

    do not work for keyboard input. When using getch() or getche() in such a
    loop, you must specify a keyboard character to indicate the end of input.
    We've used \r, and in many later examples we'll use the Esc key.

    Although the getchar() function uses text mode by default, you can call
    QuickC's setmode() function to place getchar() in binary mode. (See
    setmode() in the Microsoft QuickC Run-Time Library Reference for details.)
    However, you cannot switch getche() and getch() to text mode.

    Don't mix buffered functions such as getchar() and gets() with unbuffered
    functions such as getche() and getch(). The buffered functions transmit
    characters from the input buffer when it is flushed; the unbuffered
    functions read keys as they are pressed. Thus, a program mixing buffered
    and unbuffered input functions might not process the characters in the
    order they were typed.

    Table 13-1 summarizes the different behavior of the character input

    Table 13-1 Character Input Functions
                                        getchar()   getche()     getch()
    Buffered                             o
    Echoes                               o           o
    Uses \n                              o
    Uses \r                                          o            o
    Uses stdio.h                         o
    Uses conio.h                                     o            o
    Text mode (default)                  o
    Binary mode                                      o            o
    Backspace editing                    o
    Reads ASCII keys                     o           o            o
    Reads non-ASCII keys                             o            o

Typical Uses for Character Input Functions

    The primary advantage of using the buffered getchar() is that it lets
    users edit input with the Backspace key before they send it to the
    program. The nonbuffered form, on the other hand, requires users to type
    less because they needn't press Enter. For example, suppose your program
    uses the following prompt:

    Continue? <y/n>

    With getchar(), the user must type y and press Enter, while getche()
    requires only a y. Likewise, the getche() function is useful in programs
    that use a typed character to select a menu item. Consider the following

    while ((ch = getchar()) != 'q') /* oops example */
        switch (ch)
            case 'a': ...
            case 'b': ...
            case 'c': ...
            default:  printf("Not a valid choice\n");

    To choose case a, the user types a and presses Enter. The loop processes
    the a, recycles and processes the \n generated by the Enter key, and
    prints the default message. Replacing getchar() with getche() eliminates
    the need to press the Enter key and hence the need to add programming to
    process the extraneous \n.

    The non-echoed, nonbuffered getch() is useful, of course, when you don't
    want to display input on the screen. For example, you might use the k key
    to move an image on the screen. Also, a program that requires a user to
    type a secret password shouldn't display it on the screen.

    Let's use getch() to construct a simple password program. In a real
    application, we would ensure password security by also using encryption
    and periodic updating. In the PASSWORD.C program (Listing 13-4 on the
    following page), we'll build the password into the program and concentrate
    on processing the user's input.

    /*  password.c -- requires a password to complete the   */
    /*                program; illustrates a use of getch() */

    #include <stdio.h>
    #include <conio.h>
    #include <string.h>
    #define GUESS_LIMIT 4
    #define WORD_LIMIT 10  /* maximum length of password */
    #define TRUE 1
    #define FALSE 0
    char *Password = "I'mOk";
        int g_count = 0;           /* guesses taken    */
        int w_count;               /* letters accepted */
        int in_count;              /* letters entered  */
        char entry[WORD_LIMIT + 1];
        char ch;
        int correct, go_on;

            puts("Enter the secret password.");
            in_count = w_count = 0;
            /* the following loop accepts no more chars */
            /* than entry[] will hold, but keeps track  */
            /* of total number typed                    */
            while ((ch = getch()) != '\r')
                    if (w_count < WORD_LIMIT)
                        entry[w_count++] = ch;
            entry[w_count] = '\0';
            if (in_count != w_count)
                    correct = FALSE;    /* too many chars */
                    correct = (strcmp(entry, Password) == 0);
            go_on = !correct && g_count < GUESS_LIMIT;
            if (go_on)
                    puts("\nNo good; try again.");
            } while (go_on);
        if (!correct)
            puts("Sorry, no more guesses.  Bye.");
        puts("Welcome to Swiss bank account 2929100.");
        puts("Your current balance is $10,232,862.61.");

    Listing 13-4.  The PASSWORD.C program.

    Note the following loop:

    while ((ch = getch()) != '\r')
                if (w_count < WORD_LIMIT)
                    entry[w_count++] = ch;

    It uses an if statement to prevent overflowing the array, yet it continues
    to read additional characters if the limit is exceeded. We could have made
    this loop stop at the character limit, but that would tell the illicit
    user the number of characters in the actual password.

    The structure of the do while loop reflects the two conditions that
    terminate the loop: a correct password or too many attempts. If the loop
    ends and correct is still false, the program knows that the reason for
    termination was too many attempts.

    Character and String Input in BASIC and C
    If you are used to BASIC, you know that you can read a character from the
    keyboard (with no echo) using the INKEY$ function. This function is
    similar to C's getch(). C conveniently provides the alternative getche()
    function for character input with echo, while in BASIC you would need a
    separate PRINT statement to echo the input character. Note that neither
    the BASIC function nor the C functions mentioned recognize Ctrl-Z as a
    signal for the end of file.

    Both BASIC and C provide generalized input functions that can handle a
    series of numeric or string variables. In BASIC, the INPUT statement
    allows you to supply a prompt string and accept input into one or more
    variables. For example:


    The scanf() function in C is similar in that it allows you to receive
    input for a series of variables of different types. The scanf() function,
    however, allows you a much greater degree of control over the format of
    each input value, the interpretation of white space, and the characters
    used to separate input values. Unlike the INPUT function, scanf() makes no
    provision for a prompt string, so it is normally preceded by a separate
    printf() statement with the desired string.

    In a typical trade-off for these two languages, BASIC's INPUT statement
    provides very rudimentary error checking and editing of the input line.
    While scanf() will reject any input that does not match the
    specifications, it does not terminate or restart when bad input is
    encountered. The C programmer is responsible for error checking to
    determine whether the values entered are actually reasonable and complete.

Reading Non-ASCII Keys

    Some keys, such as the function keys, the cursor control keys, and Alt-key
    combinations, have no ASCII code. How can a QuickC program read them?
    Before answering this question, we need to discuss how the keyboard
    actually works.

The Keyboard Processor and Scan Codes

    Information does not flow directly from the keyboard to a C program.
    Instead, pressing (or closing) a given key generates a "closure" code that
    indicates the physical location of the key. A microprocessor within the
    keyboard reads this code and then generates a new code, called a "system
    code." It also reports if the user is holding down the Shift, Ctrl, or Alt
    key. Finally, it generates a third code (two bytes called the "extended
    scan code") for the keystroke (or keystroke combination) and places it in
    a storage area called the "keyboard buffer." If the key is still "closed"
    after a predetermined period of time elapses, another keystroke is placed
    in the buffer. Thus, you can generate a string of characters by holding
    down a key. Releasing the key generates an "opening" code that tells the
    keyboard microprocessor that you are finished with that key. By default,
    the keyboard buffer holds a maximum of 16 extended scan codes.

    The purpose of the keyboard buffer is to hold characters that are typed
    faster than an application can process them. It is distinct from the
    buffer created for the stdio.h input functions.

    The getch() and getche() functions do not read the keyboard directly.
    Instead, they read the extended scan codes in the keyboard buffer. Because
    this code is more extensive than the standard ASCII code, programs can use
    it to identify function keys, cursor keys, and other keys lacking an ASCII
    code. (The only difference between getch() and getche() is that getche()
    echoes input; therefore, our next discussions about getch() actually apply
    to both functions.)

Using Scan Codes

    Each extended scan code is two bytes. The first byte, which we call the
    "ASCII byte," contains the ASCII code, if any, for the keystroke. The
    second byte, which we call the "scan byte," contains a scan code for the
    key. This code is based on the physical position of the key on the
    keyboard, and, in some cases, on whether the Shift, Ctrl, or Alt key is

    The contents of an extended scan code reveal whether or not it represents
    an ASCII character. If it does, the ASCII byte is nonzero. If it does not,
    the ASCII byte is set to zero, and the numeric value of the scan byte
    encodes the keystroke or keystroke combination. For example, in Figure
    13-1, the uppercase Q character is represented by an ASCII byte of 81
    because that is its ASCII code. The scan code of 16 means the Q key is the
    sixteenth key in the keyboard numbering scheme. The F1 key has no ASCII
    representation, so the ASCII byte is 0. However, because it is the 59th
    key on the keyboard, the scan byte is 59.

            ┌────────┬────────┐               ┌────────┬────────┐
    Press Q │   81   │   16   │      Press F1 │   00   │   59   │
            └────────┴────────┘               └────────┴────────┘
            ASCII     Scan                  └────────┬────────┘
            byte      byte                   Extended scancode

    Figure 13-1. Scan codes.

    How does getch() use these extended codes? First, it looks at the ASCII
    byte. If the byte is nonzero, getch() knows it has found an ASCII
    character. It returns that value and then skips the scan byte and moves to
    the next ASCII byte. For example, it returns 0x41 for Shift-A, 0x61 for a,
    and 0x01 for Ctrl-A.

    When the ASCII byte is 0, getch() lets the program know it has found a
    non-ASCII keystroke by returning a value of 0. Because getch() needs to
    know which non-ASCII character was pressed, it does not skip to the next
    ASCII byte; instead, it goes to the scan byte. Thus, the next call to
    getch() results in it reading the scan code that goes with the 0 ASCII
    byte. In other words, only one call of getch() is needed to read an ASCII
    keystroke, but two calls are needed to read a non-ASCII keystroke. Also,
    the scan codes are returned only for the non-ASCII keystrokes.

    Suppose, for example, that you type the Shift-Q combination and then press
    the F1 key. The codes 81 16 00 59 are placed in the keyboard buffer. The
    first call to getch() returns the 81. The next call to the function skips
    to the 00 and returns that value, and the third call returns the 59. Thus,
    a program that plans to use the F1 key must look for return values of 0.
    When it encounters one, the program should check to see if the next call
    returns 59. If so, F1 was pressed. The return value of 0 is a flag that
    says, "Special processing required here."

    Now, how does getchar() process non-ASCII characters? It copies ASCII
    values into the program buffer created by the standard I/O buffer. When it
    finds a 0 ASCII byte in the buffer, it skips to the next input character.
    The 0 ASCII bytes and the scan codes never make it to the I/O buffer, let
    alone to the program.

A Scan Code Example

    The SCANCODE.C program (Listing 13-5 on the following page) demonstrates
    these functions by reading input. If the input is ASCII, the program
    prints the ASCII code. If the input is non-ASCII, the program prints the
    scan codes.

    Following is some sample output:

    Press keys and see the codes!
    Press the Esc key to quit.

    Q has ASCII code 81──────────────────────────────────────────────Shift-Q
    Scan code is 59───────────────────────────────────────────────────────F1
    t has ASCII code 116────────────────────────────────t has ASCII code 116
    ^T has ASCII code 20──────────────────────────────────────────────Ctrl-T

    /*   scancode.c -- displays ASCII or scan code         */
    /*   This program illustrates using getch() to detect  */
    /*   special keys such as function keys.               */

    #include <conio.h>
    #define ESC '\033'     /* ESC key */
        int ch;

        printf("Press keys and see the codes!\n");
        printf("Press the Esc key to quit.\n");

        while ((ch = getch()) != ESC)
            if (ch != 0)
                    if (ch <= 32)    /* control characters */
                        printf("^%c has ASCII code %d\n",
                                ch + 64, ch);
                        printf("%c has ASCII code %d\n", ch, ch);
            else              /* ch IS 0 */
                    ch = getch();  /* get scan code */
                    printf("Scan code is %d\n", ch);

    Listing 13-5.  The SCANCODE.C program.

    What happens if you use getch() and getche() without checking for the zero
    value? They would interpret the ASCII byte and scan byte as two ASCII
    bytes, thus interpreting 00 59 as code for Ctrl-@ and for the semicolon
    character, instead of F1.

Scan Code Values

    In this book we will use only those codes listed below in an include file
    called keys.h. When we need to use these keys, you can include that file,
    which is shown in Listing 13-6.

    Not all keystrokes produce scan codes. For example, Shift, Ctrl, and Alt
    modify the scan codes produced when other keys are pressed. The SCANCODE.C
    program demonstrates this. For example, press Alt. Nothing happens until
    you simultaneously press a second key.

    The operating system normally intercepts the Ctrl-Break combination as the
    code for terminating a program. Thus, getch(), getche(), and getchar()
    never read Ctrl-Break. (We will discuss how to handle Ctrl-Break later in
    this chapter.)

    /*  keys.h -- scan codes for several keys */

    #define F1 59   /* function key F1 */
    #define F2 60   /* function key F2 */
    #define F3 61   /* and so on       */
    #define F4 62
    #define F5 63
    #define F6 64
    #define F7 65
    #define F8 66
    #define F9 67
    #define F10 68
    #define HM 71   /* Home key    */
    #define UP 72   /* Up Arrow    */
    #define PU 73   /* Page Up     */
    #define LT 75   /* Left Arrow  */
    #define RT 77   /* Right Arrow */
    #define END 79  /* End key     */
    #define DN 80   /* Down Arrow  */
    #define PD 81   /* Page Down   */

    Listing 13-6.  The keys.h include file.

Console I/O Functions

    The getch() and getche() functions belong to the console I/O family of
    functions. These functions communicate with the console (the keyboard and
    screen) more directly than do the I/O functions of the stdio.h family.
    However, unlike the stdio.h family, console I/O functions are not in the
    standard C Library and are therefore not necessarily portable. They are
    important because they provide special services not offered by the
    standard I/O package. The console I/O functions declared in the conio.h
    header file are:



    The first eight functions in this list closely resemble the stdio.h
    functions with corresponding names. For example, cgets() resembles gets(),
    cprintf() is similar to printf(), and so on. We've already seen the
    kbhit() function. We'll discuss the inp() and outp() functions in Chapter

Character Output Functions

    Now that we've used the character input functions, let's look at the
    console character output functions. The putch() function works much like
    putchar(). One difference is that putchar() is buffered and putch() is
    not. This means that putch() output goes to the screen directly; putchar()
    output goes to an intermediate storage area first. The second difference
    is that putchar() works in text mode by default, while putch() works in
    binary mode. The main practical consequence of this is in how newlines are
    handled. The C newline character (\n) represents going to the beginning of
    the next line. This actually consists of two operations: a linefeed (LF)
    and a carriage return (CR). In QuickC, the newline character is
    represented by the LF character, ^J. The text mode produces the desired
    effect by mapping an LF to a CR-LF combination on the screen. In the
    binary mode, no such mapping takes place, so you must explicitly generate
    both an LF and a CR character (\n and \r).

    A third difference is that the text mode used by putchar() interprets a
    tab character (\t) as a tabbing instruction; the binary mode used by
    putch() interprets it as an ASCII value to be displayed. With the IBM
    character set, using putch() to generate a tab character results in a
    small circle on the screen.

    The REKEY.C program (Listing 13-7) demonstrates how to use the console
    I/O functions getch() and putch() to map the characters you type to a
    different set of characters on the screen.

    Note that we initialize the Newchars[] array to 26 letters. The
    construction Newchars[ch - 'a'] causes the array index to be zero when ch
    is a, corresponding to the array value q. Similarly, if ch is b, the index
    is 1; and the array value is the next letter in the initialization string,
    w. The initialization continues in this fashion, as shown in Figure 13-2.

    The toupper() and tolower() QuickC macros (defined in ctype.h) convert
    cases; thus, we don't need to use another 26-element array for uppercase
    letters. Note the way in which the program explicitly translates Enter
    (read by getch() as \r) to an output of \r and \n.

            │    q   │    w   │    e   │    r   │    t   │        │
                'a'-'a'   'b'-'a'  'c'-'a'  'd'-'a'  'e'-'a'

    Newchars      [0]      [1]      [2]      [3]      [4]

    Figure 13-2. The Newchars[ch - a] array.

    /* rekey.c -- transliterates typed input             */
    /*    This program illustrates getch() and putch().  */

    #include <stdio.h>
    #include <conio.h>
    #include <ctype.h>
    #define ESC '\033'   /* the escape key */
    char Newchars[] = "qwertyuiopasdfghjklzxcvbnm";
    /* values to be assigned to the a,b,c keys, etc. */
        char ch;

        printf("Type characters and see them transformed;\n");
        printf("Press the Esc key to terminate.\n");
        while ((ch = getch()) != ESC)
            if (islower(ch))
                putch(Newchars[ch - 'a']);
            else if (isupper(ch))
                ch = tolower(ch);
                putch(toupper(Newchars[ch - 'a']));
            else if (ch == '\r')

    Listing 13-7.  The REKEY.C program.

Console String I/O

    Often we want a program to read a string──for example, the name of a file.
    Or we want to generate a string. These activities can be done character by
    character, but it is more convenient to use functions designed to handle
    strings. The console functions cgets() and cputs() perform these tasks. In
    action, these functions are similar to gets() and puts(), but there are
    some differences.

    Like gets(), cgets() reads an input string into an array. However, the
    first element of the array holds the maximum allowable size of the input
    string, including a terminating null character. You must initialize this
    element correctly. The second element holds the actual number of bytes
    used, and it is set by cgets() after it reads the input. The string itself
    starts at the third element. Thus, the array must be two bytes longer than
    the maximum string size, including a null character, as shown in Figure
    13-3 on the following page.

                ┌─────────Available space───────────┐
        0     1  │  2     3     4     5     6     7  │
    │  6  │  4  │  L  │  A  │  R  │  A  │ \O  │     │
        │     │     └──cgets() puts string here
        │     └──cgets() puts string size here
        └──You put available space here

    Figure 13-3. Storage of an array read by cputs().

    The cgets() function reads input until the maximum length of the string
    (not counting the null character) is reached or until the user presses
    Enter. The console beeps if you try to read beyond the limit, and you
    can't enter additional characters. The function will, however, let you use
    the Backspace key to correct input. This function returns a pointer to the
    beginning of the stored string; that is, if the array name is str, cgets()
    returns a pointer to str[2].

    The cputs() function takes a pointer to a string as its argument and
    displays that string on the console. Unlike puts(), cputs() does not
    append a newline character; therefore, you must explicitly include the
    \r\n combination to generate a new line. The return value is the last
    character written. The function returns 0 if the string is a null string
    and -1 if there is an error.

    The short STRIO.C program (Listing 13-8) illustrates how the two
    functions work. Notice how we use store + 2 as an argument for cputs(). We
    do this because the string starts at the location pointed to by store + 2.
    We kept the character limit small to make it easy to see what happens when
    you try to exceed it.

    /* strio.c -- uses cgets() and cputs()                   */
    /* program list -- strio.c (cgets() not in core library) */
    #include <conio.h>
    #define MAXSIZE 6
        char store[MAXSIZE + 2];

        store[0] = MAXSIZE; /* puts limit in first element */
        cputs("What's your name?\n\r");
        cputs("\n\rI'll remember you, ");
        cputs(store + 2);

    Listing 13-8.  The STRIO.C program.

    The following is a sample run of the program:

    What's your name?
    I'll remember you, Steph!

    Note the \n\r at the beginning of the second cputs() statement. This
    prevents the message from being printed over the input line.

    Instead of using cputs(store + 2), we could have used cputs(&store[2]).
    Or, because the return value of cgets() points to the start of the
    string──not to the start of store[]──we could have declared a pointer and
    used it as follows:

    char *start;
    start = cgets(store);

Formatted I/O

    Finally, the cscanf() and cprintf() functions provide console analogues to
    the standard I/O functions scanf() and printf(). The main differences are
    that cscanf() and cprintf() work directly with the console, that cprintf()
    requires you to use the \r\n combination instead of \n, and, of course,
    that they are less portable.

Keyboard Control with ANSI.SYS

    Using getch() or getche() and the scan codes, a QuickC program can detect
    a function key or a cursor control key. But how can you turn that
    information into action? How, for example, can pressing the Left Arrow key
    be made to move the cursor one space to the left? There are three common
    techniques: One uses the ANSI.SYS driver provided with MS-DOS and PC-DOS;
    the second uses BIOS calls; and the third directly accesses video memory.
    Table 13-2 compares the three methods.

    The first method is the simplest, so we begin with it. Many terminals have
    internal hardware that lets you control cursor position and other screen
    attributes by sending "escape sequences" from your program to the
    terminal. These all begin with the ESC character, followed by different
    sequences corresponding to different actions. For example, the sequence
    ESC[2B moves the cursor down two lines in the same column. By using
    printf() to generate such a string, you can move the cursor around. The
    original IBM PC hardware design omitted this convenient feature.

    Table 13-2 Cursor and Screen Control Methods
    Method             ANSI.SYS         BIOS            Direct Memory Access
    Speed ranking      3                2               1
    Ease-of-use        1                2               3
    Portability        ANSI-compatible  BIOS-compatible Display-specific

    MS-DOS version 2.0 came to the rescue, however, by providing the ANSI.SYS
    "driver" as a software fix. (A driver is software designed to handle
    specific hardware I/O devices.) The ANSI.SYS software intercepts output,
    examining it for escape sequences. When it finds a valid sequence, it
    performs the requested action. To use this method, you need ANSI.SYS up
    and running, and you need to know the proper escape sequences.

Starting ANSI.SYS

    Running ANSI.SYS is not like running an ordinary program. You don't, for
    example, type ansi. Instead, you place this line in your CONFIG.SYS file:


    If the ANSI.SYS file is in a different directory from the CONFIG.SYS file,
    give the full pathname, as in the following example:


    Now, when you boot your computer, ANSI.SYS is installed as part of MS-DOS.

Using ANSI.SYS Escape Sequences

    One handy escape sequence lets you assign a string to a particular key.
    That is, it makes typing a single key have the same effect as typing the
    string. First, let's examine the format of the escape sequence required by


    Here ESC represents the escape character (ASCII 033). The first ASCIIcode
    represents the ASCII number of the key to which you assign the string. For
    non-ASCII keys, such as F1, use 0ancode, where the number following the 0;
    is the scan code for the key. Next, string represents, in string form, the
    characters you want to assign to the key. For example, the string could be
    dir/w. The final ASCIIcode lets you represent an assigned character in
    ASCII form instead of as a string. For example, you can use 13 instead of
    a carriage return. Finally, the character p terminates the escape
    sequence. You can use as many strings and ASCII codes as you like as long
    as you separate them with semicolons. For example, you can represent CD by
    "CD", by "C";68, or by 67;68, where 67 and 68 are ASCII codes for C and D.
    The ASGNKEY.C program (Listing 13-9), for example, assigns meanings to
    the F5 through F10 keys. These meanings remain in effect until you reboot.

    Because all the key assignments follow the same form, we use a macro to
    represent the general form. In the macro, printf() displays the escape
    sequence. First comes \033, the octal code for ESC. Then the left bracket
    and the 0; indicate a scan code. (The scan code itself is the variable K
    of the macro.) Next comes another semicolon and an open quote. (You must
    escape these with a \ when you use them within a string.) Next, the string
    itself is represented by the variable S. Then come the closing quote,
    another semicolon, a 13 (to represent a carriage return), and the
    closing p.

    /*   asgnkey.c -- uses ansi.sys to assign meanings   */
    /*                to several function keys           */
    /*  Note: This requires ANSI.SYS to be installed.    */

    #define KASSIGN(K, S) printf("\033[0;%d;\"%s\";13p", K, S)
    /* This macro assigns string S to key K */
    #define F5 63
    #define F6 64
    #define F7 65
    #define F8 66
    #define F9 67
    #define F10 68
        KASSIGN(F5, "DIR *.C");
        KASSIGN(F6, "DIR *.H");
        KASSIGN(F7, "DIR *.OBJ");
        KASSIGN(F8, "DIR *.EXE");
        KASSIGN(F9, "DIR /W");
        KASSIGN(F10,"CD \\");

    Listing 13-9.  The ASGNKEY.C program.

    Running this program changes the function key assignments, but you have to
    go to MS-DOS before you can see the effects. Once you exit to MS-DOS,
    pressing F5 through F8 causes MS-DOS to list the specified types of files
    (*.c, *.h, and so on). The F9 function key lists your directories in the
    compact form (the /W option). Also, F10 switches to the root directory.
    The defining string uses \\ for root because that is how you express a
    single \ in a C string. Because the code itself includes 13 for Return,
    you don't press Enter when using these function keys.

    You can easily modify this program to read in the desired function key
    number and the string interactively. But bear in mind that these
    assignments supersede existing ones and that they hold until you reboot.
    If, for example, you assign a function to F1, you override the editing
    function given to it by MS-DOS.

    Note that QuickC uses its own routines to read the keyboard, and it
    bypasses these function key definitions. So, while in QuickC, you still
    can use F5 to run a program. But if you call up an MS-DOS shell from
    QuickC, the new assignments apply.

Cursor and Screen Control

    Now let's apply the ANSI.SYS method to a simple menu model. The goal is to
    write a program that clears the screen and displays a simple menu with one
    choice highlighted. The Up Arrow and Down Arrow keys move the cursor and
    highlighting to a different choice, and pressing Enter selects the
    highlighted choice. To do this, we need more escape codes. Table 13-3 on
    the following page lists some representative examples from which we'll
    select the ones we need. Our program will use the various cursor control
    sequences to move the cursor. The highlighting of a choice is handled
    using the SGR (Set Graphics Rendition) escape sequence, which lets you
    specify certain "attributes." Each character to be displayed can be
    assigned an attribute that controls its presentation: color, reverse
    video, blinking, and so on. In Table 13-3, ESC[ represents the Escape
    character, and num is a numeric parameter for which you substitute a
    specific number. Numbering of rows and columns starts with 1. For all but
    the last code sequence, any omitted num is assumed to be 1.

    Table 13-3 ANSI.SYS Escape Sequences
    Name           Mnemonic       Escape Code   Description
    Cursor         CUP            ESC[num;numH  Moves the cursor to the
    Position                                    position specified by the
                                                numeric parameters. The first
                                                num is the line number; the
                                                second is the column number.
    Cursor Up      CUU            ESC[numA      Moves the cursor up num lines
                                                in the same column.
    Cursor Down    CUD            ESC[num       Moves the cursor down num
                                                lines in the same column.
    Cursor Forward CUF            ESC[numC      Moves the cursor right num
    Name           Mnemonic       Escape Code   Description
    Cursor Forward CUF            ESC[numC      Moves the cursor right num
    Cursor Back    CUB            ESC[numD      Moves the cursor left num
    Erase Display  ED             ESC[u         Erases the entire display and
                                                homes the cursor.
    Set Graphics   SGR            ESC[numm      Sets character attributes as
    Rendition                                   indicated by num. Possible
                                                values include 0 for normal, 1
                                                for high intensity, 5 for
                                                blink, and 7 for reverse

    To highlight a line of text, we must first print the escape code for
    highlighting on that line and then print the text. To confine highlighting
    to the menu line, we turn off highlighting at the end of the menu output.

    To move the cursor and highlighting, we use getch() and the scan codes to
    detect when the arrow keys are pressed. If the Down Arrow key is pressed,
    for example, the program moves the cursor and reprints the menu, changing
    which line is highlighted. Listing 13-10 shows the completed MENU.C
    program, and Figure 13-4 on p. 419 shows the menu at work.

    /*  menu.c -- uses ANSI.SYS for cursor control and   */
    /*            for video reverse in a sample menu     */
    /*  Note: Requires that ANSI.SYS be installed.       */

    #include <conio.h>
    #define ITEMS 5           /* number of menu items     */
    #define UP 72             /* scan code for up arrow   */
    #define DOWN 80           /* scan code for down arrow */
    #define VIDREV "\033[7m"  /* reverse video attribute  */
    #define ATTOFF "\033[0m"  /* turn attributes off      */
    #define ED()   printf("\033[2J")  /* erase display     */
    #define HOME() printf("\033[H")   /* home the cursor   */
    #define CUU(Y) printf("\033[%dA", Y);   /* cursor up   */
    #define CUD(Y) printf("\033[%dB", Y);   /* cursor down */

    char *Menu[ITEMS] = {"Add a number to the list",
                        "Delete a number from the list",
                        "Clear the list",
                        "Sum the list",
    char *Heading =
    "Use arrow keys to highlight choice. "
    "Use Enter key to select choice.";

    void showmenu(int);
    int getmesg(int);
        int messno = 0; /* message to be highlighted */
        while (messno != ITEMS - 1)
            messno = getmesg(messno);
            switch (messno)
                case 0 :
                case 1 :
                case 2 :
                case 3 : printf("...pretending to work...");
                        printf("Hit any key to continue\n");
                case 4 : printf("Quitting!\n");
                default: printf("Programming error!\n");

    /* showmenu() displays the menu  */
    void showmenu(highlite)
    int highlite;   /* message number to be highlighted */
        int n;
        char *start;
        printf("%s", Heading);
        for (n = 0; n < ITEMS; n++)
            if (n == highlite)
                start = VIDREV; /* turn on reverse video */
                start = ATTOFF;
            printf("\n\n%s%s%s", start, Menu[n], ATTOFF);
        CUD(2 + 2 * highlite);

    /*  getmesg() selects a menu item */
    int getmesg(mnum)
    int mnum; /* current message number */
        char ch;
        while ((ch = getch()) != '\r')
            if (ch == 0)
                ch = getch();
                switch (ch)
                    case UP   : if (mnum > 0)
                                    showmenu (--mnum);
                                    CUD(2 * ITEMS - 2);
                                    showmenu(mnum = ITEMS - 1);
                    case DOWN : if (mnum < ITEMS - 1)
                                    CUU(2 * ITEMS - 2);
                                    showmenu(mnum = 0);
                return mnum;

    Listing 13-10.  The MENU.C program.

    │ Figure 13-4 can be found on p.419 of the printed version of the book.  │

    Figure 13-4. The MENU.C program at work.

    Watching the Keyboard in BASIC and C
    The ANSI.SYS techniques discussed here allow you to achieve the
    functionality of the KEY statement in BASIC. The BASIC KEY n, string
    statement allows you to assign a string to the PC function key Fn──that
    is, to create a simple "keyboard macro." The ANSI method is more general
    (it can be used with any key, not just a function key) and is also not
    limited to assigning short strings.

    BASIC statements such as ON KEY provide a very useful facility called
    "event-driven programming." After you use the KEY statement to assign a
    key number to one of the keyboard keys, a press of that key while the
    program is running will be "trapped." The ON KEY(n) subroutine statement
    causes subroutine to be executed whenever the key that's assigned number n
    is pressed. This allows programs to respond to input immediately.

    C has no such built-in facilities. You can, however, put the program in an
    outer loop that calls the kbhit() function to see if a key has been
    pressed. If a key has been pressed, you can use getch() to read the key.
    After assigning the key to a variable of type char, you can use it in a
    switch statement that calls the appropriate function to handle the command
    received. This isn't true event-driven programming, because the response
    to a key comes only when the program is at the top of the loop, but QuickC
    programs run fast enough that the effect is often the same. The use of a
    special device driver or an environment such as Microsoft Windows can
    allow for true event-driven programming.

    The MENU.C program first defines several macros using printf() and the
    escape codes to represent some of the ANSI.SYS sequences from Table 13-3
    on p. 416. If you plan to use such macros often, you should create an
    include file for the macro definitions.

    Because we are illustrating ANSI.SYS and not numeric analysis, the program
    does no actual calculation. However, the switch statement in main()
    provides the skeleton for controlling program flow. The getmesg() function
    returns the array index of the selected message, and the switch selects a
    response based on that value. The switch is in a loop, so you can
    repeatedly make choices until you select Quit.

    In main(), the HOME() macro uses the CUP escape code to home the cursor.
    Because we omitted the two numeric parameters, the default values of 1 are
    used, which effectively home the cursor.

    The showmenu() function displays the menu. It receives the array index of
    the element to be highlighted. That message then starts with highlighting
    turned on; the other messages have it turned off.

    The getmesg() function, as we mentioned, returns the array index of the
    selected item. It also handles the cursor movement. In this function,
    getch() checks for the Up Arrow and the Down Arrow keys. If, for example,
    the Down Arrow key is pressed, CUD moves the cursor down two lines to the
    next message. The array index is also incremented to tell showmenu() which
    message to highlight. To keep the cursor inside the menu, we compare its
    position to the menu limits. If the cursor is on the bottom line of the
    menu, then pressing the Down Arrow key moves it to the top line.

    This program works as designed, but it runs slowly, and the redrawing of
    the screen is not very smooth. The ANSI.SYS approach to cursor and screen
    control is relatively simple, but using BIOS calls or direct memory access
    gives better performance.

Using QuickC to Access the BIOS

    One way to create programs that take advantage of the special capabilities
    of an IBM PC/XT, PC/AT, or compatible without getting too involved in the
    hardware is to use BIOS calls.

Background for the IBM BIOS

    BIOS is an acronym for Basic Input/Output System. It consists of a set of
    assembly-language routines permanently stored in what is called Read-Only
    Memory, or ROM, of the IBM PC. The computer can read and utilize
    information in ROM, but it cannot alter ROM. That preserves the integrity
    of the routines. The BIOS includes routines to read the keyboard, to
    control the video display, and to read from and write to disk drives. Most
    higher-level programming ultimately makes use of these routines. For
    instance, QuickC's getch() uses one of the keyboard routines, and many
    MS-DOS commands ultimately use the BIOS routines to do low-level work.

    In short, you can think of the BIOS as a built-in library of functions.
    All you need to do is find out what services are offered and how to use

    The ultimate source of information about the BIOS is the IBM Personal
    Computer Technical Reference Manual. This manual includes
    assembly-language listings of all the routines. We'll describe those
    routines as we use them.

Using the BIOS

    Two problems face the QuickC programmer who wants to use the BIOS. One is
    that the routines, which are written in assembly language, don't work the
    same as C functions, so you have to learn a little about assembly language
    and about the hardware to understand them. The second is that these BIOS
    routines are accessed not by function calls but by "interrupt signals."
    For this reason, these routines are commonly called "interrupt routines,"
    or simply "interrupts." Let's clarify this topic first.

    Interrupt Routines

    The heart of a PC is its central processing unit, or CPU, but a PC
    contains other processors, too. For example, the keyboard processor
    handles keyboard input, and another processor handles data flow between
    the CPU and memory. To enable the CPU to keep in touch with its
    environment, an interrupt system was developed. Certain devices and
    assembly-language instructions can generate signals that take control of
    the microprocessor. The Intel 8086 family of microprocessors permits as
    many as 256 distinct interrupt signals, but fewer are actually used. When
    the CPU detects an interrupt signal, it "interrupts" its current activity
    and executes the set of assembly-language instructions identified with
    that particular signal.

    How Interrupts Work
    When you boot a PC, it sets up a table of addresses known as the
    "interrupt vectors." At the first address is the routine to be executed if
    interrupt signal 0 is detected. At the second address is the routine to be
    executed if interrupt signal 1 is detected, and so on. When an interrupt
    is detected, the corresponding address is found in the table, and the
    instructions beginning at that address are executed. At the end of those
    instructions, a "return from interrupt" instruction tells the
    microprocessor to resume its interrupted activity.

    The operating system also uses the interrupt table. When MS-DOS or PC-DOS
    is first loaded, it adds its own batch of interrupt routines. Memory
    resident programs also work by storing their addresses in the interrupt
    vector table. Incidentally, MS-DOS can substitute its own version of a
    ROM-based BIOS routine by overwriting the appropriate interrupt vector
    with a new address. The ROM itself is unchanged, but the computer is
    directed to the new address instead when the interrupt is issued. This
    method is sometimes used as a software "fix" for faulty BIOS routines.
    (The only way to update the actual BIOS is to get a newer version of the
    ROM chip.)

    Software Interrupts

    In assembly language, generating interrupts is simple. For example, to
    generate interrupt signal 0x10 (the video I/O interrupt), you use the
    following instruction:

    int 10h

    What if one interrupt arrives while another interrupt routine is
    executing? This situation is handled by a priority ranking. A
    higher-priority interrupt can interrupt a lower-priority routine, but not
    vice versa.

    C, as a general, portable language, doesn't have a built-in interrupt
    instruction. But the QuickC library offers several non-ANSI C functions
    designed to serve the same purpose. Seven functions make specific BIOS
    calls; they all have names beginning with _bios_ and are declared in the
    bios.h file. The dos.h file declares another 40 functions, most of which
    call specific MS-DOS functions. (Interrupt number 0x21 can be used to
    access many functions loaded into the system by MS-DOS; these are the
    MS-DOS system calls.) Five of the dos.h functions, however, are more
    general and can invoke a choice of interrupts. (See Table 13-4 for a
    summary of these functions.)

    We will use the int86() function because it is generally applicable. As
    its name suggests, it generates a specified interrupt for the 8086 family
    of microprocessors. However, before we can use this function, we have to
    see how interrupt routines use registers to transfer data.

    Table 13-4 Interrupt-accessing Functions in Order of Decreasing Generality
    Name               Use
    intx86()           Invokes interrupts requiring the use of segment
    int86()            Invokes interrupts not requiring use of the segment
    intxdos()          Invokes MS-DOS system calls requiring the use of
                        segment registers.
    intdos()           Invokes MS-DOS system calls not requiring use of the
                        segment registers.
    bdos()             Invokes MS-DOS system calls that use only the DX and AL
    _bios_...() family Invokes specific BIOS interrupts.
    _dos_...() family  Invokes specific MS-DOS calls.

    Interrupts, Assembly Language, and Registers

    Like C functions, interrupts pass information back and forth between the
    routine and the calling program. Instead of using arguments, however,
    interrupts use the microprocessor registers. The int86() function gets
    around this difference by using unions to pass the register information to
    and from the calling C program.

    Registers are small work and storage areas built into the CPU. For
    example, the 8088 chip, the most commonly used member of the 8086 family,
    has 13 registers, each capable of holding 16 bits. Four of the registers
    are general-purpose registers used for arithmetic and logical operations;
    they are called AX, BX, CX, and DX. Four "segment" registers store the
    addresses of various memory segments; these registers are called CS, DS,
    SS, and ES. Four more "pointer/index" registers keep track of addresses
    used in a program; they are called SP, BP, SI, and DI. Finally, the
    instruction pointer (IP) keeps track of the address of the next
    instruction to be executed. Also, the processor has nine "flags" that can
    be turned on or off. The flags can be considered to be individual bits in
    a flag register. These, then, are the resources open to an interrupt

    There is one further complication. Each of the general-purpose registers
    can be considered to be two 8-bit registers. The AX register, for example,
    can be divided into the AH (H for high byte) and the AL (L for low byte)
    registers. Assigning a value to the AX register affects the whole
    register, but assigning a value to AL or AH affects only half of the
    register. Similarly, the BX register is divided into the BH and BL
    registers, and so on.

    Now that we have some background about registers, let's see how int86()

The int86() Function

    The int86() function will be our tool for initiating interrupt routines,
    initializing registers, and reading registers. Its library description
    begins with the following:

    #include <dos.h>
    int int86(intno, inregs, outregs);
    int intno;           /* Interrupt number */
    union REGS *inregs;  /* Register values on call */
    union REGS *outregs; /* Register values on return */

    This syntax summary says to include the dos.h header file when using this
    function. Also, int86() takes three arguments. The first is the number of
    the desired interrupt. The second is the address of a union containing the
    register values passed to the interrupt. The third is the address of the
    union into which the post-interrupt register values are copied.

    To use int86(), you need to know how the type union REGS is defined. That
    information resides in the dos.h file, as follows:

    /* word registers */

    struct WORDREGS {
            unsigned int ax;
            unsigned int bx;
            unsigned int cx;
            unsigned int dx;
            unsigned int si;
            unsigned int di;
            unsigned int cflag;

    /* byte registers */

    struct BYTEREGS {
            unsigned char al, ah;
            unsigned char bl, bh;
            unsigned char cl, ch;
            unsigned char dl, dh;

    /* general-purpose registers union -- overlays the
    corresponding word and byte registers. */

    union REGS {
            struct WORDREGS x;
            struct BYTEREGS h;

    Together, these definitions give two views of the registers.

    The WORDREGS structure provides the 16-bit view. This structure has seven
    members representing the four general-purpose registers, two of the
    pointer registers, and the "carry" flag (which we won't use). These are
    the registers most commonly used by the interrupts. (The int86x() function
    uses an additional structure to give access to more registers.) In this
    structure, for example, the ax member represents the AX register.

    The BYTEREGS structure gives the 8-bit view. This structure represents the
    four general-purpose registers, with each register split into two 1-byte
    registers. Thus, the ch member of this structure represents the CH
    register, the high byte of CX.

    The unusual part of this function is the definition of the union REGS. It
    superimposes the word view and the byte view. For example, suppose you use
    the following declaration:

    union REGS myreg;

    To assign a value to the AX register, use a statement such as the

    myreg.x.ax = 1026;

    The .x notation specifies the WORDREGS member of myreg; therefore,
    myreg.x.ax is the AX member of that structure.

    To assign a value to the BL register (the low byte of the BX register),
    use the following .h notation:

    myreg.h.bl = 22;

    This specifies the BYTEREGS member of the union.

    Recall that a union uses the same storage area for all its members. This
    means that myreg.h.al and myreg.h.ah overlie myreg.x.ax. To get the high
    byte of the 1026 that was assigned to myreg.x.ax, refer to myreg.h.ah (see
    Figure 13-5).

    union REGS myreg;
                    / │                   │                   │ \  AX register
                /   │              myreg.x.ax               │   \
                /     └───────────────────┴───────────────────┘     \
            /     / ┌─────────── /───/──┬──\───\────────────┐ \     \
            ┌───────────────────┐/   /    │    \   \┌───────────────────┐
            │     one byte      │  /      │      \  │     one byte      │
            │    myreg.h.ah     │/────────┴────────\│    myreg.h.al     │
    AH    └───────────────────┘─────────┬─────────└───────────────────┘  AL
    register         │                   │                   │        register
                    │                   │                   │
                    │                   │                   │ DX
                    │                   │                   │

    For a REGS union: .x means the 16-bit version
                    .h means the 8-bit version

    Figure 13-5. The REGS union.

    You now know enough theory to use int86(). However, you still need to know
    what values to pass as arguments to the BIOS routines. Let's look at a
    simple example.

Interrupt 0x16──The Keyboard I/O Interrupt

    Because we have been discussing the keyboard, let's look at interrupt
    routine 0x16, which reads the keyboard. The QuickC library provides the
    _bios_kbrd() function to access this specific routine. However, we will
    use int86() in order to demonstrate a more general approach to using

    Like many interrupts, 0x16 includes more than one subroutine. It has three
    subroutines; each is termed a "function" or "service." To select a
    particular function, you must place the "function code" number in the AH
    register before calling the interrupt. Let's take a look at what each
    function does.

    Interrupt 0x16, Function Code 0──Get Character

    This function reads the keyboard buffer if a character is present;
    otherwise, it waits until a keystroke is placed in the buffer. When it
    reads a key, it places the ASCII byte in the AL register and the scan byte
    in the AH register. (Note that the return values are written over the
    values we originally placed in AH and AL.) The code is removed from the
    keyboard buffer once it is read. The getch() function is based on this

    Interrupt 0x16, Function Code 1──Check Keyboard Buffer

    This function checks to see if the keyboard buffer is empty or not. If it
    is empty, the "zero flag" (ZF) is set to 1; otherwise, the flag is cleared
    (set to zero). If a character is present, the ASCII byte is placed in AL
    and the scan byte in AH, but the code in the buffer is left there. The
    kbhit() function is based on this function.

    Interrupt 0x16, Function Code 2──Get Keyboard Status

    This function reports on the status of the Shift and Ctrl keys. Each of
    eight keys is assigned a particular bit in the AL register. If one of the
    eight keys is closed, the corresponding bit is set to 1. Table 13-5 shows
    the corresponding bits and keys.

    Table 13-5 Keyboard Status Bits
    Bit         Set to 1 If
    0           Right Shift is closed
    1           Left Shift is closed
    2           Ctrl is closed
    3           Alt is closed
    4           Scroll Lock is active
    5           Num Lock is active
    6           Caps Lock is active
    7           Insert mode is active

Reading ASCII and Scan Codes

    Let's use function code 0 to construct a more general version of getch()
    that we'll call Readkey(). It will return both the ASCII and the scan
    bytes. Using it will give you a better picture of how the keyboard codes
    work and show you that using interrupts from QuickC is not all that
    difficult. The readkey.c program (Listing 13-11) contains the Readkey()

    The dos.h file defines the union REGS type and declares the int86()
    function. We define symbolic constants to represent the interrupt number
    and the function code number. Finally, we define a two-member structure
    called SCANCODE for holding the two keyboard codes. Readkey() uses the reg
    structure to set the AH register to the proper function code and then it
    calls int86(). Because preserving the original register values is
    unnecessary, the same structure stores both the input values and the
    returned values of the registers. Finally, the program copies the two
    relevant register values into the structure that the function returns.

    The int86() syntax calls for two pointers to union REGS as arguments. In
    practice this usually calls for using the address operator applied to the
    appropriate union, as we have done here.

    Having developed the Readkey() function, let's use it in the next program,
    SHOWCODE.C, (Listing 13-12). To run this program within the QuickC
    environment, be sure that Screen Swapping On is active (on the Debug

    This program reads a key, prints it (if it is printable), and displays
    both the ASCII and scan codes. Using it can be instructive. Following, for
    example, is the output for m, Shift-M, Ctrl-M, Alt-M, Enter, and Esc:

        m: ascii = 109, scan =  50────────────────────────────────────────────m
        M: ascii =  77, scan =  50──────────────────────────────────────Shift-M
    ^M: ascii =  13, scan =  50───────────────────────────────────────Ctrl-M
        : ascii =   0, scan =  50────────────────────────────────────────Alt-M
    ^M: ascii =  13, scan =  28────────────────────────────────────────Enter
    ^[: ascii =  27, scan =   1──────────────────────────────────────────Esc

    /* readkey.c -- contains the Readkey() function     */
    #include <dos.h>
    #define KEYINTR 0x16  /* keyboard read interrupt */
    #define GETCHAR 0     /* read scancode function  */
    struct SCANCODE {
                    unsigned char ascii;  /* ascii code */
                    unsigned char scan;   /* scan code  */

    struct SCANCODE Readkey()
        union REGS reg;
        struct SCANCODE scancode;

        reg.h.ah = GETCHAR;         /* specify function   */
        int86(KEYINTR, &reg, &reg); /* note use of & oper.*/
        scancode.ascii = reg.h.al;
        scancode.scan = reg.h.ah;
        return (scancode);

    Listing 13-11.  The readkey.c function.

    /*  showcode.c -- shows ASCII and scan codes for    */
    /*                keystrokes                        */
    /* Note: Set Screen Swapping On in the Debug menu.  */
    #include <stdio.h>
    #include <dos.h>
    #define KEYINTR 0x16  /* keyboard read interrupt */