PCjs Machines

Home of the original IBM PC emulator for browsers.

Logo

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


════════════════════════════════════════════════════════════════════════════


PUBLISHED BY
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
────────────────────────────────────────────────────────────────────────────



────────────────────────────────────────────────────────────────────────────
Contents


Preface

Acknowledgments

PART 1  INTRODUCTION TO C

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

PART 2  CORE OF C

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
            Recursion
            Noninteger Functions
            Function Prototypes
            Putting It All Together: A Larger Program

PART 3  ADVANCED C TOPICS

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

PART 4  C AND THE HARDWARE

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
            Paging
            Ports
            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

Index



────────────────────────────────────────────────────────────────────────────
Preface

    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
    hackers.

    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



────────────────────────────────────────────────────────────────────────────
Acknowledgments

    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.





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



────────────────────────────────────────────────────────────────────────────
PART 1  INTRODUCTION TO C
────────────────────────────────────────────────────────────────────────────



────────────────────────────────────────────────────────────────────────────
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          │
    └────────────────────────────┘

    (A)  BASIC--A SNUG FIT BUT NOT PORTABLE


            ANSI/UNIX
    ┌────────────────────────────┐  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          │
    └────────────────────────────┘

    (B)  C--A PORTABLE CORE

    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          │  └──────►├────────────────┤
                                    │          ├────────────────┤
                                    └─────────►├────────────────┤
                                                └────────────────┘
                                                        Memory

    (A)                         POINTERS


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

    (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
    definition.

    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
        message.

    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
        program.

    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
        again.

    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
        compiled.

    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
    programmers.

    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:
        \LIB\GRAPHICS.LIB, SCREEN.DAT

    ■  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?
    3
    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

    function_name()
    {
    <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
    itself.


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
    routines.

    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
    guidance.

    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
                operations
    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,
                etc.
    File       Main Purpose
    ──────────────────────────────────────────────────────────────────────────
                etc.
    stdarg.h   Allows a function to use a variable number of arguments (ANSI
                style)
    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
                (XENIX-style)

    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
                directory
    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,
    GRAPHICS.LIB.

    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
    programs.

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:\
                                                ──┬──
                                                    │
                                                    │
                                                c:\qc
                    ┌─────────────────────┬─────────────────────┬─────────────
                    │                     │                     │
                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
    graphically.

                    Editor
                ┌───────────────────┐
    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
        directories

    ■  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
    settings.

    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
    this:

    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
    programs.

    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:

    C>SETUP H C:\QC M EM GR

    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
                                programs
    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
    files.

    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
    problem.

    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
    graphics).

    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
    variables.)

    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:

    files=15
    buffers=20

    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

    qc

    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
        untitled.c.)

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

    ■  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
    combinations.)

    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 */

    main()
    {
        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 QCHELLO.C

    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
    it.

    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
    instantaneously.

    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:

    C>qchello

    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
    level.)

    Now type:

    C>exit

    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
    2-12.)

    ┌────────────────────────────────────────────────────────────────────────┐
    │ 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
    semicolon.

    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
        file.

    ■  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.



────────────────────────────────────────────────────────────────────────────
PART 2  CORE OF C
────────────────────────────────────────────────────────────────────────────



────────────────────────────────────────────────────────────────────────────
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
    elements:

    ■  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)
                                function
                                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
    vendors.

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 */

    main
    (
    )
    {
    printf
    ("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 */

    or

    /* Comment line one
    comment line two */

    or

    /* 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
    write:

    ITEM$="WIDGET"
    SERIAL=32767

    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
    double.

                                        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       */

    main()
    {
        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   */

    main()
    {
        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
    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
    lines:

    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        */

    main()
    {
        /* 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",
                length);
        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    */
    main()
    {
        /* 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.",
                total_attendance);
    }
    ──────────────────────────────────────────────────────────────────────────

    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           */

    main()
    {
        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

    340,000,000,000,000,000,000,000,000,000,000,000,000

    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
    astronomer.

    ──────────────────────────────────────────────────────────────────────────
    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);

    999999995904.000000

    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
    constant.)

    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   */

    main()
    {
        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
    mnemonic:

    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
    types.

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

    main()
    {
        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
    strings.

Using Escape Sequences

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

    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
    "unless."

    ──────────────────────────────────────────────────────────────────────────
    /* 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
    output:

    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()  */

    main()
    {
        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          */

    main()
    {
        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 */

    main()
    {
        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
    program:

    122
    7a
    93000000
    192450.875000
    1.924509e+005
    2000000000000000000000000000000.000000
    2.000000e+030

    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)
    specifiers.

    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
    specifier.)

    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
    types.

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

    main()
    {
        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
    work.

    The program prints a single variable with varying field specifiers:

        123.456001────────────────────────────────────12.6f (field specifier)
    123.4560────────────────────────────────────────────────────────────8.4f
        123.456────────────────────────────────────────────────────────────8.3f
        123.46────────────────────────────────────────────────────────────8.2f

    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  */

    main()
    {
        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 */

    main()
    {
        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
    follows:

    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        */

    main()
    {
        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
    example.

    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 */

    main()
    {
        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:

    60.500000
    525.000000
    1000050
    10500.052500
    10500.052734
    0
    8519680
    0.000000
    0.000000
    0.000050

    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
    calculation.

    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
    example:

    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
    time:

    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 */

    main()
    {
        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              */

    main()
    {
        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 */
    main()
    {
        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  */

    main()
    {
        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
    assigned.

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

    main()
    {
        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 */

    main()
    {
        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
    ──────────────────────────────────────────────────────────────────────
    C
    =
    ==
    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
    follows:

    (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 */
    main()
    {
        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
    variables.

    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
    variations.


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
    year.

    The anatomy of a for loop is as follows:

    for (start; condition; update)
        {
        statements;
        }

    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
    statements.

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

    main()
    {
        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
        11).

    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++) {
        printf("%d\n",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
    loop.

    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
    functions.)

    ──────────────────────────────────────────────────────────────────────────
    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
    semicolon.

    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       */

    main()
    {
        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  */

    main()
    {
        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:

    open_file(name);
    for (line = 1; line <= last_line; line++)
        {
        for (field = 1; field <= 4; field++)
            {
            process_field;
            }
        }
    save_file(name);

    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:

    putch(201);

    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
    idea.

    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

    main()
    {
        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 */
        putch(UPLEFT);
        for (i = 0; i < (width - 2); i++)
            putch(LINE);
        putch(UPRIGHT);
        putch(NL);
        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(BLANK);
                    }
            putch(SIDE); /* right side */
            putch(NL);
            putch(CR); /* move to next line */
            }

        /* draw bottom of box */
        putch(LOWLEFT);
        for (i = 0; i < (width - 2); i++)
            putch(LINE);
        putch(LOWRIGHT);
        putch(NL);
        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
    screen.

    ╔═══════════════════╗
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ║                   ║
    ╚═══════════════════╝

    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
    characters.)

    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)
        {
        statements;
        }

    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:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Done!

    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
    loop.

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

    main()
    {
        int count = 1;

        while (count < 11)  /* loop condition */
            /* body of loop */
            {
            printf("%d\n", count);
            count++;
            }
        printf("Done!\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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"
    !───────────────────────────────────────────────────────────────────"no"
    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
    screen.

    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

    main()
    {
        int pos, i, j = 1;
        while (!kbhit())
            {
            pos = 1;
            while (pos < 79)
                {
                putch(RTARROW);
                i = 1;
                while (i < 1000)
                    {
                    j = i + 10;
                    i++;
                    }
                putch(BACKSPACE);
                putch(BLANK);
                pos++;
                }
            while (pos > 1)
                {
                putch(LFTARROW);
                i = 1;
                while (i < 1000)
                    {
                    j = i + 10;
                    i++;
                    }
                putch(BACKSPACE);
                putch(BLANK);
                putch(BACKSPACE);
                putch(BACKSPACE);
                pos--;
                }
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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>

    main()
    {
        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
    loop).


The do Loop

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

    do
        {
        statements;
        }
    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 */

    main()
    {
        int i = 1;
        do
            {
            printf("%d\n", i);
            i++;
            }
        while (i < 11);
        printf("Done!\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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>

    main()
    {
        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 */

        do
            {;}                       /* 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
    model.)

    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 */

    main()
    {
        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:
    E^C

    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)
        statement(s);

    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;
    main()
    {
        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
    box.

                                ┌────────────────►────────────────┐
                            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)
        printf("Excellent!");

    and

    while (question <= total_questions)
        ask_question();

    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
    repeatedly.

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
    statements.

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)
            {
            print_footer;
            putch(FF);
            ++page_number;
            print_header;
            }

    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)
        statement(s);
    else
        statement(s);

    Consider the following example:

    if (age >= 18)
        {
        printf("To vote, enter number of candidate: ");
        scanf("%d", &candidate);
        }
    else
        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
    if.

    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;
    main()
    {
        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");
            }
        else
            /* 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");
    else
        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");
        }
    else
        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;
    else
        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
    follows:

    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 */

    main()
    {
        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");
        else
            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)
    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 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
    manner:

    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':
            statement(s);
            break;
        case 'constant2':
            statement(s);
            break;
        case 'constant_n':
            statement(s);
            break;
        default:
            statement(s);
        }

    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
    statement.

    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
    following:

    switch (ch)
        {
        case 'q':
        case 'Q':
            show_score();
            end_game();
            break;
        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)
            break;
        case 'h':
            printf("Humidity is %4.2f\n", humidity);
            break;
        case 'p':
            printf("Air pressure is %5.2f\n", pressure);
            break;
        case 'w':
            printf("Wind velocity is %d\n", wind);
            break;
        default:
            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
    statement

    if (number < 32000)
        break;

    terminates the while loop if the program generates a random number greater
    than 32,000. The output might look something like the following:

    41
    18467
    28145
    16827
    491
    2995
    11942
    5436
    32391
    Broken out of WHILE loop.

    ──────────────────────────────────────────────────────────────────────────
    /* break.c -- shows how to get out of loop with BREAK */

    #include <stdio.h>
    #define TRUE 1

    main()
    {
        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
    main()
    {
        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);
                    break;

                case 's':   /* square */
                    printf("Result: %d\n", number * number);
                    break;

                case 'r':   /* square root */
                    printf("Result: %f\n", sqrt(number));
                    break;

                default:
                    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
    ends.

    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
    executed.

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");
            break;
        case < 100.00:
            printf("see office manager");
            break;
        case < 500.00:
            printf("see district manager");
            break;
        default:
            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)
            continue;
        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 */
    main()
    {
        int sw = 0;
        char ch;
        while (1) /* endless loop */
        {
            /* print current status */
            if (sw)
                printf("\nSwitch is ON\n");
            else
                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 */
    main()
    {
        int pos, line;
        /* space to left side */
        for (line = 1; line <= BOTTOM; line++)
            {
            for (pos = 1; pos < LEFT; pos++)
                {
                putch(BLANK);
                }
            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++)
                    {
                    putch(BLANK);
                    }
                putch(CH);
                putch(NL);
                putch(CR);
                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(CH);
                else
                    putch(BLANK);
                }
            putch(CH);
            putch(NL);
            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
    directives.)

    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;
    printf("Goodbye\n")
        goto end;
    yes: printf("Let's continue ...\n");
    end:

    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"
    branch.

    ──────────────────────────────────────────────────────────────────────────
    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");
    else
        printf("Goodbye\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
    4.)

    ──────────────────────────────────────────────────────────────────────────
    /* pixels.c -- creates shapes       */
    /*             from random pixels   */
    #include <graph.h> /* for graphics  */

    main()
    {
        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 */
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    external.

    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() */
    main()
    {
        int pixels, radius = 50;
        double center_x = 320, center_y = 100,
            xpos, ypos;
        srand(0);
        _setvideomode(_HRESBW);
        _setcolor(1);
        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 */
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    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:

    main()
    {
    data_menu();
    while (more_data)
        {
        get_data();
        check_data();
        store_data();
        }
    process_data();
    report_menu();
    do_report();
    }

    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.

    do_report()
        {
        switch (choice)
            {
            case 'b' :
                bar();
                break;
            case 'p' :
                pie();
                break;
            case 'l' :
                line();
                break;
            case 't' :
                table();
                break
            }
        }

    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:

    pie()
        {
        printf("executing pie()\n");
        }

        Function
        declarations
    ┌────────────────┐                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
    comments).

    main()
    {
        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()
    proper.

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()
        {
        printf("Error!\a\n");
        }

    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:

    main()
    {
    ...
        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

    main()
    {
        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++)
            putch(DOUBLE_BAR);
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    becomes.


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
    main().

    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
    address.

    ──────────────────────────────────────────────────────────────────────────
    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        */

    main()
        {
        int n = 12;
        int func1(), func2();
        printf("n in main(): val %d ", n);
        printf("address %d\n", &n);

        printf("Calling func1()\n");
        func1();
        printf("Calling func2()\n");
        func2();
        }

    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
    example).


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          */

    main()
        {
        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')
            {
            ++chars;
            if (ch == ' ')
                ++words;
            }
            ++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          */

    main()
        {
        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()    */

    main()
        {
        void square(), triangle(), circle();

        printf("What length do you want to use? ");
        scanf("%d", &length);

        square();   /* calculate areas */
        triangle();
        circle();
        }

    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
    application.

    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++)
            putch(DOUBLE_BAR);
        }

    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:

    main()
    {
    void line (length);
    ...
    }

    or:

    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
    programs.

    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 */

    main()
    {
        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",
                times);
            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.

            Caller
    ┌──────────────────────┐
    │  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      */

    main()
    {
        /* 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");
        getch();

        tick = 0;                /* run "clock" for */
        while (tick < seconds)   /* time specified  */
            {
            delay(interval); /* wait interval seconds */
            tick += interval;
            printf("%d\n", tick);
            beep(1);
            }
        beep(3);
    }

    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 */

        do
            {;}                       /* 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",
                times);
            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
    10
    15
    20
    25
    30

    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>

    main()
    {
        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   */
        _setcolor(color);
        _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
    continues.

    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       */

    main()
    {
        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");
            return(0);
            }

        if (power == 0) /* any number to 0 power is 1 */
            return(1);

        if (power == 1) /* any number to 1 power is itself */
            return(number);

        /* calculate for power > 1 */
        for (count = 1; count <= power; count++)
            total *= number;
        return(total);
    }
    ──────────────────────────────────────────────────────────────────────────

    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.


Recursion

    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!
                / \
                4 * 3!
                    / \
                    3 * 2!
                    / \
                    2 * 1!
                        / \
                        1 * 0!
                        │
                        │
                        1
                    4! = 24

    (A)           BREAKING DOWN 4!


                    n!
                /  \
                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 */
    main()
    {
        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;
        else
            result = number * factorial(number - 1);

        printf("exiting : ");
        printf("level %d. number = %d. &number = %d. ",
                --level, number, &number);
        printf("result = %d\n", result);

        return(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
    each.

    The printf() statements in the program record the above process, as
    follows:

    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
    main():

    main()
        {
        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

    main()
    {
        char ch;
        char getyn();

        printf("Do you want to continue? ");
        if ((ch = getyn()) == 'y')
            printf("Answer was y\n");
        else
            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();
            printf("\n");
            if ((ch == 'y') || (ch == 'n'))
            /* valid response, break out of loop */
                break;
            /* give error message and loop again */
            printf("please enter ");
            }
        return(ch);
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    string.)

    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 */

    main()
    {
        /* 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);
        return(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
    acceptable.)

    ──────────────────────────────────────────────────────────────────────────
    /* proto.c -- demonstrates function prototyping */
    /*            and parameter checking           */

    main()
    {
        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);
        return(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         */
    main()
    {
        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();
            printf("\n");
            if ((ch == 'y') || (ch == 'n'))
            /* valid response, break out of loop */
                break;
            /* give error message and loop again */
            printf("please enter ");
            }
        return(ch);
    }

    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",
            target);
    }

    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);
                break;
            case 'b' :
                num = random(10);
                break;
            case 'c' :
                num = random(25);
                break;
            case 'd' :
                num = random(50);
                break;
            case 'e' :
                num = random(100);
                break;
            }
        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");
            losses++;
            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;
            wins++;
            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)? ",
                temp);
            if (getyn() == 'y')
                {
                score = temp;
                total += score;
                wins++;
                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
    function.)

    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
    possibilities:

    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.



────────────────────────────────────────────────────────────────────────────
PART 3  ADVANCED C TOPICS
────────────────────────────────────────────────────────────────────────────



────────────────────────────────────────────────────────────────────────────
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
    location

    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
    executes.

    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

    main()
    {
        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
    example:

    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      */

    main()
    {
        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)
            {
            putch(character);
            }
        putch('\r');
        putch('\n');
    }
    ──────────────────────────────────────────────────────────────────────────

    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:

    1
    2
    3
    x
    ----------
    6

    ──────────────────────────────────────────────────────────────────────────
    /* sadd.c -- a small adding machine that illustrates  */
    /*            the need for array bounds checking      */

    main()
    {
        int offset = 0, i, result = 0;
        int stack[3];

        while (scanf("%d", &stack[offset]) == 1)
            {
            ++offset;
            }
        for (i = 0; i < offset; ++i)
            {
            result += stack[i];
            }
        printf("----------\n");
        printf("%d\n", result);

    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 7-3.  The SADD.C program.

    Now run the program again, but this time enter four numbers:

    1
    2
    3
    4
    x
    ----------
    20

    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──────────────────────────┘

    (A)


    ┌────────┬────────┬────────┬────────┬────────┬────────┐
    │    1   │    2   │    3   │    4   │    ?   │    4   │
    └────────┴────────┴────────────────┴────────┴────────┘
    stack[0] stack[1] stack[2]│ result      i     offset
                                │    
            Array ends here───┘    │
                                    │
                            Position of stack[3]

    (B)

    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
    array.

    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

    main()
    {
        int offset = 0, i, result = 0;
        int stack[MAXSTAK];

        while (scanf("%d", &stack[offset]) == 1)
            {
            if (++offset >= MAXSTAK)
                {
                printf("Stack Full\n");
                break;
                }
            }
        for (i = 0; i < offset; ++i)
            {
            result += stack[i];
            }
        printf("----------\n");
        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

    main()
    {
        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', };

    main()
    {
        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");
                continue;
                }
            for (i = 1; i <= num; ++i)
                {
                printf("%c", Letters[(i * num) % MAXL]);
                }
            printf("\n");
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    expression

    #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
    bytes).

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))

    main()
    {
        int i;

        printf("The first %d primes are: ", NUMP);
        for (i = 0; i < NUMP; ++i)
            {
            printf("%d ", Primes[i]);
            }
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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:

    Bub_sort(list);

    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:

    Bub_sort(vals)
    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();
    main()
    {
        int num = 2, i;
        static int list[NUMINTS] = { 6, 5, 4, 3, 2, 1 };

        printf("num before Triple = %d\n", num);
        Triple(num);
        printf("num after Triple = %d\n", num);
        printf("list[0] before Triple = %d\n", list[0]);
        Triple(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("\n");

        Bub_sort(list);
        printf("After sorting ->  ");
        for (i = 0; i < NUMINTS; ++i)
            {
            printf("%d ", list[i]);
            }
        printf("\n");

    }
    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.

Variations

    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:

    Bub_sort(vals)
    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:

    Bub_sort(vals)
    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();
    main()
    {
        float fary[1];

        printf("Enter a floating-point number\n");
        printf("(Any non-numeric entry quits)\n\n");
        while (scanf("%f", &fary[0]) == 1)
            {
            Hexout(fary);
            }
    }

    void Hexout(unsigned char chary[])
    {
        int i;

        for (i = 0; i < sizeof(float); ++i)
            {
            printf("%02X ", chary[i]);
            }
        printf("\n\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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

    fary[1]

    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
    bytes.

    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
    array.


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
    expression

    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.

                                    Columns
                                        │
                            ┌───────────┴───────────┐
                            │                       │
                            ┌───────┬───────┬───────┐ ───┐
                            │   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                      */

    main()
    {
        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]);
                    }
                printf("\n");
                }

            /* Enter the user number. */
            if ((num = getch()) == 'Q')
                exit(0);
            num -= '0';
            if (num < 1 || num > 9)
                {
                printf("Not a legal number.\n\n");
                continue;
                }
            /* Find that square. */
            for (row = 0; row < 3; ++row)
                {
                for (col = 0; col < 3; ++col)
                    {
                    if (num == square[row][col])
                        {
                        goto GOTIT;
                        }
                    }
                }
    GOTIT:
            /* Check for a legal move. */
            if (row > 2 || col > 2)
                {
                printf("Bad Box Specification\n\n");
                continue;
                }
            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");
                continue;
                }

            /* 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))
                        {
                        break;
                        }
                    }
                }
            if ((i * j) == 9)
                break;
            }
        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
    braces:

    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
    0.

    Two-dimensional Arrays and Functions

    As with one-dimensional arrays, you pass a two-dimensional array to a
    function by merely stating its name:

    Make_move(board);

    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:

    Make_move(field)
    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                     */

    main()
    {
        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. */
            Draw_field(board);

            /* Enter the user's coordinates. */
            if ((row = getch()) == 'Q')
                exit(0);
            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");
                continue;
                }
            if (board[row][col] != '-')
                {
                printf("Sorry, Square Occupied\n\n");
                continue;
                }

            /* Make the move. */
            board[row][col] = 'X';
            if ((ch = Check_winner(board)) != '-' || ch == 't')
                break;
            Make_move(board);
            if ((ch = Check_winner(board)) != '-' || ch == 't')
                break;
            }
        Draw_field(board);
        if (ch == 't')
            printf("It's a tie!\n");
        else if (ch == 'X')
            printf("You win!\n");
        else
            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])
                {
                return(field[row][0]);
                }
            if (field[0][col] != '-'             /* vertical */
                    && field[0][col] == field[1][col]
                    && field[1][col] == field[2][col])
                {
                return(field[0][col]);
                }
            }
        if (field[0][0] != '-'         /* right diagonal */
                && field[0][0] == field[1][1]
                && field[1][1] == field[2][2])
            {
            return(field[0][0]);
            }
        if (field[0][2] != '-'         /* left diagonal */
                && field[0][2] == field[1][1]
                && field[1][1] == field[2][0])
            {
            return(field[0][2]);
            }

        for (row = 0; row < 3; ++row)        /* any moves left */
            {
            for (col = 0; col < 3; ++col)
                {
                if (field[row][col] == '-')
                    {
                    return('-');
                    }
                }
            }
        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';
                    return;
                    }
                }
            }
    }

    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]);
                }
            printf("\n");
            }
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    follows:

    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   │  │
            └─────────────────────────────────\└───────┴───────┴───────┘──┘
                                            │                       │
                                            └───────────┬───────────┘
                                                        Columns

    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:

    Draw_planes(cube);

    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
    following:

    int
    Draw_planes(box);
    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                */

    main()
    {
        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]);
                }
            printf("\n");
            }
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 7-11.  The BOX.C program.


Advanced Topics and Tricks

    In this section we discuss three advanced techniques that can be very
    handy:

    ■  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
    demonstrates.

    If you declare three consecutive arrays such as the following:

    int first[3], second[3], third[3];

    and then reference with a negative subscript:

    second[-1]

    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
    technique.

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
    dimension:

    Print_row(cube[1]);

    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:

    Print_row(row)
    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>

    main()
    {
        unsigned int val1, val2, result;
        int ch;
        extern void show();

        while(1)
            {
            printf("\nval1: ");
            if (scanf("%d", &val1) != 1)
                break;

            printf("val2: ");
            if (scanf("%d", &val2) != 1)
                break;
            printf("\tval1   = ");
            show(val1);
            printf("\tval2   = ");
            show(val2);

            printf("Bitwise Operator: ");
            while ((ch = getchar()) == '\n')
                {
                continue;
                }
            if (ch == EOF)
                break;
            switch (ch)
                {
                case '&':
                    result = val1 & val2;
                    printf("Executing: result = val1 & val2;\n");
                    break;
                case '|':
                    result = val1 |= val2;
                    printf("Executing: result = val1 | val2;\n");
                    break;
                case '^':
                    result = val1 ^= val2;
                    printf("Executing: result = val1 ^ val2;\n");
                    break;
                case '~':
                    result = ~val1;
                    printf("Executing: result = ~val1;\n");
                    printf("\tresult = ");
                    show(result);
                    result = ~val2;
                    printf("Executing: result = ~val2;\n");
                    break;
                case '<':
                    result = val1 <<= val2;
                    printf("Executing: result = val1 <<val2;\n");
                    break;
                case '>':
                    result = val1 >>= val2;
                    printf("Executing: result = val1 >>val2;\n");
                    break;
                case 'q':
                case 'Q':
                    return(0);
                default:
                    continue;
                }
            printf("\tresult = ");
            show(result);
            }
    }
    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)
                {
                putchar((num[i]&(1<<j))?'1':'0');
                }
            }
    }

    void show(unsigned int val)
    {
        extern void bitout();

        printf("(%05u decimal)", val);
        bitout(&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
    printer:

    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
    follows:

    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'};

    Get_move(choices);

    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
    num.

    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
        data

    ■  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

    (A)


            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

    (B)

    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(); \
                printf("\n\n")

    main()
    {
        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);
        WAIT;
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);
        WAIT;
        printf("To verify this, let's store 3 in\n");
        printf("\"*address_var\", then print out ");
        printf("\"num\" and \"*address_var\"\n");
        printf("again.\n");
        WAIT;
        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);
        WAIT;
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);
        WAIT;

        printf("Now we will add 15 to \"num\" and print\n");
        printf("\"num\" and \"*address_var\" again.\n");
        WAIT;

        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);
        WAIT;
        printf("Since \"address_var\" points to \"num\"\n");
        printf("the value in ");
        printf("\"*address_var\" is: %d\n", *address_var);
        WAIT;

        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"
    again.
    (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

    main()
    {
        int val = 5;

        square(val);
    }

    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:

    main()
    {
        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:

    main()
    {
        int val = 5;

        square(&val);────────────────────────────────────────Pass an address
    }

    Or we can pass a pointer:

    main()
    {
        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  */

    main()
    {
        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");
                exit(1);
                }
            if (val != guess)
                printf("Wrong. It is %d.\n", val);
            else
                printf("Right!\n");
            printf("Continue? ");
            if (getche() != 'y')
                break;
            }
    }
    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
    expression

    &coins[0]

    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;
    again

    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")

    main()
    {
        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");
            exit(1);
            }
        pennies2 = pennies1 = (int)(amount * 100.0);

        coin_ptr = coins;
        for (i = 0; i < NCOINS; ++i)
            {
            WAIT;
            count = 0;
            while ((pennies1 -= coins[i]) >= -1)
                ++count;
            if (count > 0)
                {
                printf("%4d %2d%c", count, coins[i], CENT);
                printf(" coins by array offset.\n");
                }
            if (pennies1 == 0)
                break;
            pennies1 += coins[i];

            count = 0;
            while ((pennies2 -= *coin_ptr) >= 0)
                ++count;
            if (count > -1)
                {
                printf("%4d %2d%c", count, *coin_ptr, CENT);
                printf(" coins by pointer indirection.\n");
                }
            if (pennies2 == 0)
                break;
            pennies2 += *coin_ptr;
            ++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:

    coin_ptr
    *coin_ptr
    i
    coins[i]

    (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
    COMMAND.COM).

    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 */

    main()
    {
        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");
            exit(1);
            }
        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)
                {
                ++count;
                }
            if (count > 0)
                printf("%4d %2d%c\n", count, *amts, CENT);
            if (due == 0)
                break;
            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
    error.


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

        ++Amounts;─────────────────────────────────────────────────────Legal
        ++Bills;─────────────────────────────────Illegal operation on rvalue
        ++amts;────────────────────────────────────────────────────────Legal
        ++address_var;─────────────────────────────────────────────────Legal
        ++old_coins;───────────────────────────────────────────────────Legal
        }

    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
    point.

    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                               */

    main()
    {
        char *mem_ptr;
        unsigned int address;

        while (1)
            {
            printf("Examine what memory location?\n");
            printf("Enter location in decimal: ");
            if (scanf("%u", &address) != 1)
                break;

            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
    adapter.

    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
    screenp.

    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
    0xB800000.

    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

    main()
    {
        int far *screenp;
        int temp, i;

        do
            {
            /* 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
    program.

    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():

    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 */

    main()
    {
        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): ");
        do
            {
            digit = getch() - '0';
            if (digit == ('-' - '0'))
                continue;
            if (digit < 0 || digit > 9)
                {
                printf("\nAborted: Nondigit\n");
                return(1);
                }
            digits[ndigits++] = digit;
            printf("%d", digit);
            } while (ndigits < 7);
        printf("\n");

        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]);
                printf("\n");
                if (++line == 20)
                    {
                    printf("Press any key for more");
                    printf(" (or q to quit): ");
                    if (getch() == 'q')
                        return (0);
                    printf("\n");
                    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)
    follows:

    #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
    legal:

    iptr[5]

    ──────────────────────────────────────────────────────────────────────────
    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 */

    main()
    {
        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");
            exit(1);
            }

        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)
            {
            ++count;
            /* Enlarge the array. */
            if ((iptr = realloc(iptr,bytes*(count+1))) == NULL)
                {
                printf("Oops, realloc failed\n");
                exit(1);
                }
            }
        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
    memory.

    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 */

    main()
    {
        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");
            exit(1);
            }

        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)
            {
            ++count;
            /* Enlarge the array. */
            if (sbrk(bytes) == (int *)-1)
                {
                printf("Oops, sbrk failed\n");
                exit(1);
                }
            }
        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
    variables.

    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
    order.

                            cptrs[]
                            ┌─────┐
    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
                            pointers

    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

    main()
    {
        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();
                ++count;
                }
            *cp = '\0';
            if (count == 0)        /* all done if blank line */
                break;
            }
        printf("---------<reversed>---------\n");
        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");
            exit(1);
            }
        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
    "handles."

    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:

    *pp

    Because pp points to p, *pp yields the address stored in p, that of num.
    Placing another * in front of pp:

    **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:

    **pp
    *p
    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

    main()
    {
        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();
                ++count;
                }
            *cp = '\0';
            if (count == 0)        /* all done if blank line */
                break;
            }
        printf("---------<reversed>---------\n");
        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");
            exit(1);
            }
        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).
    ──────────────────────────────────────────────────────────────────────────

                            cptrs[]
                            ┌─────┐
                ┌───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
                        pointers

    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:

    *funptr();

    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};

    main()
    {
        int ch;

        printf("Select 1, 2 or 3: ");
        ch = getch(); putch(ch);
        ch -= '1';
        if (ch < 0 || ch > 2)
            printf("\nNo such choice.\n");
        else
            Dochoice[ch]();

    }

    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
    following:

    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
    roles.

    Contrast this flexible form of programming with an inflexible switch
    statement, such as the following:

    switch(key)
        {
        ...
        case 'l':
            Go_left();
            break;

        case 'm':
            Mark_line();
            break;

        case 'n':
            Next_search();
            break;
        ...
        }

    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
    declarations.

    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
    array.

    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
    value.


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 */

    main()
    {
        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
    string.";

    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
    process.)

    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
    strings.

            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";

    main()
    {
        static char local_phrase[] = "This is local";
        char *cp;

        printf("Dump of the string pool:\n");
        printf("-----------------------\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 */
                printf("\"\n\"");
            else if (*cp == '\n' )  /* print '\n' as '\' 'n' */
                printf("\\n");
            else
                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:

    ++str;

    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";

    main()
    {
        char *cp;
        int pass;

        for (pass = 0; pass < 2; ++pass)
            {
            printf("My name is FRED\n");

            cp = Start;

            while (*cp != 'F')
                ++cp;

            *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
    example:

    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
    justification.

    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>>";

    main()
    {
        char ch;

        while (1)
            {
            printf("Select l)eft r)ight or q)uit: ");
            ch = getch();
            putch(ch);

            printf("\n\n");
            switch((int) ch)
                {
                case 'l':
                case 'L':
                    printf(Left_control, Some_text);
                    break;
                case 'r':
                case 'R':
                    printf(Right_control, Some_text);
                    break;
                case 'q':
                case 'Q':
                    exit (0);
                default:
                    printf("Huh?");
                    break;
                }
            printf("\n\n");
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    form

    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"

    main()
    {
        char buf[512];    /* should be big enough */

        printf(INTRO);

        /*
        * 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
    tool.

    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                    */

    main()
    {
        char buf[512],    /* should be big enough */
            dummy[2];    /* for \n and \0        */
        int  num;

        do
            {
            printf("Running:\n");
            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);
            printf("\n\n");

            } 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
    screen.

    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?"

    main()
    {
        char name[BUFSIZ],
            buf[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);
            puts(THE_QUESTION);
            /*
            * force an extra <enter> before replying
            */
            do
                {
                if (gets(buf) == NULL)
                    break;

                } while (*buf != '\0');        /* wait for empty line */

            puts("Sorry. I needed to think about that.");
            printf("Nice talking to you, %s.\n", name);
            }
        else
            puts("How rude!");

        puts("Goodbye.");
    }
    ──────────────────────────────────────────────────────────────────────────

    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\
    first<space>middle<space>last"

    #define ADDRESS_PATTERN \
    "number<space>street<comma><space>city"

    char Buf[BUFSIZ];        /* global I/O buffer */
    main()
    {
        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);
        exit(1);
    }

    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);
                continue;
                }
            break;
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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 *
    strdup(str)
    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
    assignments

    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
    void:

    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
                        buf
    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
                        NULL
    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
    follows:

    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"};

                                    Columns
                                        │
                ┌───────────────────────┴───────────────────────┐
                │                                      ┌────────┼─┐
            ┌─┌───────┬───────┬───────┬───────┬──────▼┬───────┐ │
            │ │  '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-
                                                                    terminating
                                                                    zeros

    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 */

    main()
    {
        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)
            exit(1);

        list = Line2words(buf, &count);

        for (i = 0; i < count; i++)
            {
            quote_flag = 0;
            printf("<");
            if (list[i] != buf)
                {
                if( list[i][-1] == '"')    /* negative subscript */
                    {
                    ++quote_flag;
                    printf("\"");
                    }
                }
            printf("%s", list[i]);

            if (quote_flag)
                printf("\"");

            printf(">\n");
            }
    }
    #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';
                continue;
                }
            words[index] = line++;    /* found a word */

            /* is it quoted? */
            if ( *(words[index]) == '"')
                {
                /* Yes, advance pointer to just past quote. */
                ++words[index];

                /* find next quote. */
                while (*line && *line != '"')
                    {
                    ++line;
                    }

                /* and turn it into a '\0'. */
                if (*line)
                    *(line++) = '\0';
                }
            else
                {
                /* otherwise skip to next space */
                while (*line && *line != ' ' && *line != '\t')
                    {
                    ++line;
                    }
                }
            if (++index == MAXW)
                break;
            }
        *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
    word.

    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);
        printf("\n");

        for (i = 0; i < argc; ++i)
            {
            printf("argv[%d] -> \"%s\"\n", i, argv[i]);
            }
        printf("argv[%d] -> NULL\n", i);
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    interchangeable.

    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);
        printf("\n");

        for (i = 0; i < argc; ++i)
            {
            printf("argv[%d] -> \"%s\"\n", i, argv[i]);
            }
        printf("argv[%d] -> NULL\n", i);
        printf("\n");

        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
    program.

    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
    false.

    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
    screen.

    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

    main()
    {
        char buf[BUFSIZ];
        int i;

        printf("Enter a line of text (20 chars max):\n");
        if (gets(buf) == NULL)
            exit(1);

        for (i = 0; i < MAXL; ++i)
            {
            if (buf[i] == '\0')
                break;
            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");
            printf("\n");
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    example:

    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
    character.

    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. */

    main()
    {
        char buf[BUFSIZ];
        int i;

        printf("Type in a line of text and I will invert it.\n");

        if (gets(buf) == NULL)
            exit(1);
        /* Print the string backward. */
        for (i = (strlen(buf) - 1); i >= 0; --i)
            {
            if (isupper(buf[i]))            /* upper to lower */
                putchar(tolower(buf[i]));
            else if (islower(buf[i]))       /* lower to upper */
                putchar(toupper(buf[i]));
            else
                putchar(buf[i]);
            }
        putchar('\n');

    }
    ──────────────────────────────────────────────────────────────────────────

    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:

    OPEN "C:\ACCT\TRANS" FOR RANDOM AS #1 LEN = 256

    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");
            exit(1);
            }
        if ((fp = fopen(argv[1], "rb")) == NULL)
            {
            fprintf(stderr, "Can't open %s\n", argv[1]);
            exit(1);
            }

        count = 0;
        while ((ch = fgetc(fp)) != EOF)
            {
            if (! isprint(ch) || count >= (BUFSIZ - 1))
                {
                if (count > 5)
                    {
                    buf[count] = 0;
                    puts(buf);
                    }
                count = 0;
                continue;
                }
            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
    line.

    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
    follows:

    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
    follows:

    ──────────────────────────────────────────────────────────────────────────
    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");
            exit(1);
            }
        if ((fp_in = fopen(argv[1], "r")) == NULL)
            {
            printf("Can't open %s for reading.\n", argv[1]);
            exit(1);
            }
        if ((fp_out = fopen(argv[2], "w")) == NULL)
            {
            printf("Can't open %s for writing.\n", argv[2]);
            exit(1);
            }

        printf("Copying and Crushing: %s->%s ...",
                    argv[1], argv[2]);

        while (fgets(buf, BUFSIZ, fp_in) != NULL)
            {
            cp = buf;
            if (*cp == '\n')    /* blank line */
                continue;
            while (isspace(*cp))
                {
                ++cp;
                }
            if (*cp == '\0')    /* empty line */
                continue;
            if (fputs(cp, fp_out) == EOF)
                {
                printf("\nError writing %s.\n", argv[2]);
                exit(1);
                }
            }
        printf("Done\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    file:

    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");
            exit(1);
            }

        if ((fp = fopen(argv[1], "rb")) == NULL)
            {
            printf("\"%s\": Can't open.\n", argv[1]);
            exit(1);
            }
        if ((cp = malloc(HUNK)) == NULL)
            {
            printf("Malloc Failed.\n");
            exit(1);
            }

        while ((bytes = fread(cp + (HUNK * hunks), 1, HUNK, fp)) == HUNK)
            {
            totbytes += bytes;
            ++hunks;
            if ((cp = realloc(cp, HUNK + (HUNK * hunks))) == NULL)
                {
                printf("Realloc Failed.\n");
                exit(1);
                }
            }
        if (bytes < 0)
            {
            printf("\"%s\": Error Reading.\n", argv[1]);
            exit(1);
            }
        totbytes += bytes;

        for (i = 0; i < totbytes; ++i)
            if (islower(cp[i]))
                cp[i] = toupper(cp[i]);

        (void)fclose(fp);

        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]);
            exit(1);
            }

        if (fwrite(cp, 1, totbytes, fp) != totbytes)
            {
            printf("\"%s\": Error writing.\n", argv[1]);
            exit(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
    screen).

    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
                line.
    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");
            exit(1);
            }
        if ((fp_in = fopen(argv[1], "r")) == NULL)
            {
            fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);
            exit(1);
            }
        if (argc == 3)
            {
                if ((fp_out = fopen(argv[2], "w")) == NULL)
                    {
                    fprintf(stderr, "\"%s\": Can't open.\n", argv[2]);
                    exit(1);
                    }
            }
        else
            fp_out = stdout;

        while (fgets(buf, BUFSIZ, fp_in) != NULL)
            {
            cp = buf;
            if (*cp == '\n')    /* blank line */
                continue;
            while (isspace(*cp))
                {
                ++cp;
                }
            if (*cp == '\0')    /* empty line */
                continue;
            if (fputs(cp, fp_out) == EOF)
                {
                fprintf(stderr, "Error writing.\n");
                exit(1);
                }
            }
        if (! feof(fp_in))        /* error reading? */
            {
            fprintf(stderr, "\"%s\": Error reading.\n", argv[1]);
            exit(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
    follows:

    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
                    positive.
    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)
            Ask();
        else
            Find(argv[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);
                return;
                }
            }
        fprintf(stderr, "\"%s\": Not in database.\n", str);
        return;
    }

    Ask()
    {
        if ((Fp = fopen(File, "r+")) == NULL)
            Make();
        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')
            return;
        printf("Number: ");
        if (gets(Number) == NULL || *Number == '\0')
            return;
        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);
            }
        ++Count;
        if (fwrite(&Count, 1, sizeof(int), Fp) != sizeof(int))
            {
            fprintf(stderr, "\"%s\": Error Writing\n", File);
            exit (1);
            }
        return;
    }
    Make()
    {
        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(fp);

    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
                                        length.
    ──────────────────────────────────────────────────────────────────────────

    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;

    fd = open("TEST.EXE", O_RDWR|O_BINARY|O_CREAT|O_TRUNC,S_IREAD|S_IWRITE);

    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
    does.

    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
    follows:

    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");
            exit(0);
            }
        if (strlen(argv[1]) > 8)
            {
            fprintf(stderr, "\"%s\": Filename too long.\n", argv[1]);
            exit(1);
            }
        strcpy(fname, argv[1]);
        strcat(fname, ".SCR");
        if (access(fname, 0) == 0)
            {
            fprintf(stderr, "\"%s\": Won't overwrite.\n", fname);
            exit(1);
            }
        if ((fd_out = open(fname, O_WRONLY | O_CREAT | O_BINARY,
                            S_IREAD | S_IWRITE)) < 0)
            {
            fprintf(stderr, "\"%s\": Can't create.\n", fname);
            exit(1);
            }
        /* 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);
            exit(1);
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    pointer.

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");
            exit(0);
            }
        if ((fd_in = open(argv[1], O_RDONLY | O_BINARY)) < 0)
            {
            fprintf(stderr, "\"%s\": Can't open to read.\n", argv[1]);
            exit(1);
            }
        /* Read it. */
        bytes = read(fd_in, Buf, SCRCHARS * 2);
        if (bytes < 0)
            {
            fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
            exit(1);
            }
        if (bytes == 0)
            {
            fprintf(stderr, "\"%s\": Empty File.\n", argv[1]);
            exit(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");
            exit(0);
            }

        if ((fd_in = open(argv[1], O_RDONLY)) < 0)
            {
            fprintf(stderr, "\"%s\": Can't open.\n", argv[1]);
            exit(1);
            }

        for (;;)
            {
            bytes = read(fd_in, buf, HUNK);
            if (bytes == 0)
                {
                if (! eofflag)
                    {
                    fprintf(stderr, "\n<<at end of file>>\n");
                    ++eofflag;
                    }
                else
                    exit(0);
                }
            else if (bytes < 0)
                {
                fprintf(stderr, "\"%s\": Error Reading.\n", argv[1]);
                exit(1);
                }
            else
                {
                eofflag = 0;
                position = lseek(fd_in, 0L, SEEK_CUR);
                if (position == -1L)
                    {
                    fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);
                    exit(1);
                    }
                Print(buf, bytes);
                do
                    {
                    ch = getch();
                    if (ch == 'q' || ch == 'Q')
                        exit(0);
                    } while (ch != '+' && ch != '-');

                if (ch == '-')
                    {
                    position = lseek(fd_in, -2 * MOVE, SEEK_CUR);
                    if (position == -1L)
                        {
                        fprintf(stderr, "\"%s\": Error Seeking.\n", argv[1]);
                        exit(1);
                        }
                    }
                }
            }
    }

    Print(char *buf, int cnt)
    {
        int i;

        for (i = 0; i < cnt; ++i, ++buf)
            {
            if (*buf < ' ' && *buf != '\n' && *buf != '\t')
                printf("^%c", *buf + '@');
            else
                putchar(*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
    following:

    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.

Directories

    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
                    successful.
    mkdir(path)    Creates a new directory named path. Returns 0 if
                    successful.
    rmdir(path)    Removes the directory whose name is path. Returns 0 if
                    successful.
    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
    fails.

    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"

    main()
    {
        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],
        To_name[MAXPATH];

    int Input(char *prompt, char buf[])
    {
        printf("%s: ", prompt);
        if (gets(buf) == NULL || *buf == '\0')
            return (0);
        return (1);
    }
    void Rename(void)
        {
        printf("->Rename/move\n");
        if (!Input("From", From_name)) return;
        if (!Input("To", To_name)) return;
        if (rename(From_name, To_name) != 0)
            perror("RENAME");
        else
            printf("Renamed: \"%s\" -> \"%s\"\n",
                    From_name, To_name);
    }
    void Remove(void)
    {
        printf("->Remove\n");
        if (!Input("Remove", From_name)) return;
        if (!Input("Are You Sure", To_name)) return;
        if (*To_name != 'y' && *To_name != 'Y')
            return;
        if (remove(From_name) != 0)
            perror(From_name);
        else
            printf("Removed: \"%s\"\n", From_name);
    }
    void Maketemp(void)
    {
        printf("->Maketemp\n");
        if (!Input("In What Directory", From_name))
            return;
        (void)strcat(From_name, "\\XXXXXX");
        if (mktemp(From_name) == NULL)
            printf("Can't create a unique name.\n");
        else
            printf("Created: \"%s\"\n", From_name);
    }
    void Quit(void)
    {
        printf("->Quit\n");
        if (!Input("Are You Sure", From_name))
            return;
        if (*From_name != 'y' && *From_name != 'Y')
            return;
        exit(0);
    }

    main()
    {
        static void (*doit[])() = {Rename, Remove, Maketemp, Quit};
        int ch;
        while (1)
            {
            printf("--------------------------------------------\n");
            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("--------------------------------------------\n");
            printf("Select: ");

            do
                {
                ch = getchar();
                } while (ch < '1' || ch > '4');
            getchar();    /* gobble trailing newline */
            printf("%c\n\n", ch);
            ch -= '1';
            doit[ch]();
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    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

    perror("JUNK");

    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");
        perror(fname);
        exit(1);
    }

    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>
        main()
        {
            rename();
            mktemp();
            perror();
        }

    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
        Guide.

Signals

    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
    functions.

    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
    longjmp().

    setjmp() prepares the program for an eventual jump to an earlier state, as
    follows:

    #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
    relationship.)

        #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
    integers.

    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
    name.

    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
    example:

    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
    name.

    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;
    };

    main()
    {
        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("\n\n");
        printf("%s %s %s\n", card1.first, card1.middle,
                card1.last);
        printf("%ld %s, %s, %s %ld\n", card1.street_num,
                card1.street, card1.city, card1.state,
                card1.zip);
        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)
            exit(0);
        buf[strlen(buf) - 1] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
            exit(0);
        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)
            exit(0);
        if (sscanf(buf, "%ld", &num) != 1)
            exit(0);
        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
    program:

    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:

    Showcard(card1);

    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;
    };

    main()
    {
        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(card2);
        Showcard(card1);

    }
    Showcard(struct cardstruct card)
    {
        printf("\n\n");

        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)
            exit(0);
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
            exit(0);
        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)
            exit(0);
        if (sscanf(buf, "%ld", &num) != 1)
            exit(0);
        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
    function:

    Enter(&card1);
            └───────────────────────────── 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
    variable.

    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
    structure.

    ──────────────────────────────────────────────────────────────────────────
    /* 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;
    };
    main()
    {
        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 */
        Showcard(&card1);

        return (0);
    }

    Showcard(cardptr)
    struct cardstruct *cardptr; /* pointer receives an address */
    {
        printf("\n\n");

        printf("%s %s %s\n", cardptr->first, cardptr->middle,
                cardptr->last);
        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)
            exit(0);
        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)
            exit(0);
        if (sscanf(buf, "%ld", &num) != 1)
            exit(0);
        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
    card:

    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
    &cards[i]
            └─────────────────── 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],
            last[MAXN],
            middle[MAXN];
        unsigned long street_no;
        char street[MAXN],
            city[MAXN],
            state[MAXN];
        unsigned long zip;
        unsigned int area;
        unsigned long phone;
    };
    struct cardstruct cards[MAXCARDS];

    main()
    {
        int  i;

        for (i = 0; i < MAXCARDS; ++i)
            {
            printf("\n<card %d of %d>\n", i + 1, MAXCARDS);
            Input(&cards[i]);
            }
        for (i = 0; i < MAXCARDS; ++i)
            {
            printf("\n<%d> ", i + 1);
            Showcard(&cards[i]);
            }
    }
    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");
        strcpy(cardp->street,Str_Input("Street"));
        strcpy(cardp->city,Str_Input("City"));
        strcpy(cardp->state,Str_Input("State"));
        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)
            exit(0);
        buf[strlen(buf) - 1 ] = '\0'; /* strip '\n' */
        if (strlen(buf) == 0)
            exit(0);
        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)
            exit(0);
        if (sscanf(buf, "%ld", &num) != 1)
            exit(0);
        return (num);
    }

    Showcard(struct cardstruct *cardptr)
    {
        printf("\n\n");
        printf("%s %s %s\n", cardptr->first, cardptr->middle,
                cardptr->last);
        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);
    }
    ──────────────────────────────────────────────────────────────────────────

    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],
                last[MAXN],
                middle[MAXN];
        unsigned long street_no;
        char street[MAXN],
                city[MAXN],
                state[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],
            last[MAXN],
            middle[MAXN];
        unsigned long street_no;
        char street[MAXN],
            city[MAXN],
            state[MAXN];
        unsigned long zip;
        unsigned int area;
        unsigned long phone;
        struct cardstruct *nextcard;
    };

    main()
    {
        int i;
        struct cardstruct card, *first, *current;

        first = (struct cardstruct *)malloc(sizeof(struct cardstruct));
        if (first == NULL)
            exit(1);
        if (Input(&card) != 0)
            exit(1);
        *first = card;
        current = first;

        while (Input(&card) == 0)
            {
            current->nextcard =
                (struct cardstruct *)malloc(sizeof(struct cardstruct));
            if (current->nextcard == NULL)
                exit(1);
            current = current->nextcard;
            *current = card;
            }
        current->nextcard = NULL;

        Dumplist(first);
    }
    Dumplist(struct cardstruct *head)
    {
        do
            {
            Showcard(head);
            } while ((head = head->nextcard) != NULL);
    }

    Showcard(struct cardstruct *cardptr)
    {
        printf("\n\n");

        printf("%s %s %s\n", cardptr->first, cardptr->middle,
                cardptr->last);
        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");
        strcpy(cardp->street,Str_Input("Street"));
        strcpy(cardp->city,Str_Input("City"));
        strcpy(cardp->state,Str_Input("State"));
        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)
            exit(0);
        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)
            exit(0);
        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
    unions.

    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] = {
            "Quit",
            "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;
    };

    main()
    {
        struct Unitstruct one_of_many;

        while ((one_of_many.type_in_union = Menu()) != 0 )
            {
            Inputval(&one_of_many);
            Printval(&one_of_many);
            }
    }

    Inputval(struct Unitstruct *one_of_many)
    {
        printf("\nEnter a %s: ", Strings[one_of_many->type_in_union]);
        switch(one_of_many->type_in_union)
            {
            case 1:
                fgets(one_of_many->manyu.wtype, BUFSIZ, stdin);
                break;
            case 2:
                scanf("%lf", &(one_of_many->manyu.dtype));
                while (getchar()!= '\n');
                break;
            case 3:
                scanf("%ld", &(one_of_many->manyu.ltype));
                while (getchar()!= '\n');
                break;
            case 4:
                scanf("%f", &(one_of_many->manyu.ftype));
                while (getchar()!= '\n');
                break;
            case 5:
                scanf("%i", &(one_of_many->manyu.itype));
                while (getchar()!= '\n');
                break;
            }
    }

    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);
                break;
            case 2:
                printf("%lf", one_of_many->manyu.dtype);
                break;
            case 3:
                printf("%ld", one_of_many->manyu.ltype);
                break;
            case 4:
                printf("%f", one_of_many->manyu.ftype);
                break;
            case 5:
                printf("%i", one_of_many->manyu.itype);
                break;
            }
        printf("\n\n");
    }

    Menu()
    {
        int i;
        char ch;
        for (i = 0; i < 6; ++i)
            {
            printf("%d) %s\n", i, Strings[i]);
            }
        printf("Which: ");
        do
            {
            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
    name.

    ──────────────────────────────────────────────────────────────────────────
    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
    time.
    ──────────────────────────────────────────────────────────────────────────

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
    union:

    #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
    follows:

    &one_of_many

    Printval (float, int)
                │     │        High address
                │     │     ┌────────────────┐
                │     └────►├──    int     ──┤2-byte int
                │           ├────────────────┤
                │           ├──            ──┤
                └──────────►├──   float    ──┤4-byte float
                            ├──            ──┤
    Start of arguments ────►└────────────────┘
                                Low address

    (A)


    Printval (int, int)
                │    │          High address
                │    │       ┌────────────────┐
                │    │       ├──            ──┤Second argument missing
                │    │       ├────────────────┤
                │    └──────►├──    int     ──┤2-byte int
                │            ├────────────────┤
                └───────────►├──    int     ──┤2-byte int
    Start of arguments ────►└────────────────┘
                                Low address

    (B)

    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:

    &one_of_many.ftype

    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
    concept.

    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
    [etc.]

    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
    pattern:

                ┌────────────────────────────────────────────── Name of pattern
    enum week_days {
        monday,───────────────────────────────────────────────────── Members
        tuesday,
        wednesday,
        thursday,
        friday,
        saturday,
        sunday
    } 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
    declaration

    enum folks {
        mo = -1,
        roseann,
        betsy = 0,
        kit,
        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  */

    main()
    {
        enum week_days {
            monday = 1,     /* start with 1 */
            tuesday,
            wednesday,
            thursday,
            friday,
            saturday,
            sunday
        } pay_day;

        static char *day_names[] = {
            "",
            "monday",
            "tuesday",
            "wednesday",
            "thursday",
            "friday",
            "saturday",
            "sunday"
        };

        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);

        do
            {
            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
    operators:

    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
    fields:

                ┌──────────────┬────────────── 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
    declaration.

    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
    declare.

    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
    readability:

    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:

    &screen_ch

    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[] = {
        "Quit",
        "Foreground",
        "Intensity",
        "Background",
        "Blinking"
    };
    enum Choices {
        Quit,
        Foreground,
        Intensity,
        Background,
        Blinking
    };

    /* use 0xB800000 for EGA or VGA */
    #define SCR_START (0xB0000000)
    #define SCR_SIZE (25 * 80)
    main()
    {
        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: ");
        do
            {
            choice = getch();
            choice -= '0';
            if (choice < Foreground || choice > Blinking)
                continue;
            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;
                    break;
                case Intensity:
                    scrchar.intensity = (scrchar.intensity)? 0 : 1;
                    break;
                case Background:
                    scrchar.background = (scrchar.background)? 0 : 7;
                    break;
                case Blinking:
                    scrchar.blink = (scrchar.blink)? 0 : 1;
                    break;
                }
            *(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
    not.

    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
        time.

    ■  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
    compilation."

    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 */
    #endif

    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>

    main()
    {
        int ret;

    #if (DEBUG_LEVEL == 2)
        fprintf(stderr, "Entering main()\n");
    #endif

    #if (DEBUG_LEVEL == 1)
        fprintf(stderr, "Calling sub()\n");
    #endif

        ret = sub();

    #if (DEBUG_LEVEL == 1)
        fprintf(stderr, "sub() returned %d\n", ret);
    #endif

    #if (DEBUG_LEVEL == 2)
        fprintf(stderr, "Leaving main()\n");
    #endif

    }

    sub()
    {
        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>

    main()
    {
        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");
            exit(1);
            }
        Bitout(num);
    }

    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)
    #endif
    #if !defined(CHIP_80286)   /* otherwise 68000 machine */
        for (i = 0; i < 4; ++i)
    #endif
            {
            for (j = 7; j >= 0; --j)
                putchar((cp[i] & (1 << j)) ? '1' : '0');
            }
        putchar('\n');
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 12-2.  The BITOUT.C program.

    #else and elif

    We can simplify the two #if directives in BITOUT.C by using the #else
    directive:

    #if defined(CHIP_80286)
        for (i = 1; i >= 0; --i)
    #else
        for (i = 0; i < 4; ++i)
    #endif

    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)
    #else
        fprintf(stderr, "Unknown chip\n");
        return;
    #endif

    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
    return.

    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)
    #else
        fprint(stderr, "Unknown chip\n");
        return;
    #endif

    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;
        #else
            sp = (int far *)0xB0000000;
        #endif
    #else
        fprintf(stderr, "No Screen Memory\n");
        return;
    #endif

    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
        defined(CGA)

    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__ );
    #else
    #define ERR
    #endif

    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__);
    main()
    {
        ERR
        err1();
        ERR
        err2();
        ERR
    }

    err1()
    {
        ERR
        err2();
    }

    err2()
    {
        ERR
    }
    ──────────────────────────────────────────────────────────────────────────

    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 │       │ │
        └───────┘─┘                                        └───────┘─┘

    (A) RESULT OF USING         (B) RESULT OF US
    ING     (C) RESULT OF USING
        #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
    off:

    #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 */

    main()
    {
        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
    sequence.

    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:

    TRIPLE(2)

    the actual argument (here 2) replaces every occurrence of the formal
    argument, x, in the original definition. This produces the following
    expansion:

    TRIPLE(2)
        │
        ├───────────────────────────────────────────────────────── Expands to
        │
        ▼
    (2*3)

    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
    follows:

    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))
        exit(0);

    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;
    PERR(val);
    └────┬───┘
            └───────────┬─────────────────────────────────────────── 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;
    PERR(val);
    └───┬───┘
        └───────────┬──────────────────────────────────────────── 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: ");
            do
                {
                ch = getch();
                ch -= '0';
                } while (ch < 0 || ch > 3);
            printf("%d\n\n", (int)ch);
            switch(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                  */

    Edit_file()
    {
        char ch;

        printf("\nYou are now in the editor.\n");
        printf("Press 'Q' to exit back to main menu.\n");

        do
            {
            ch = getch();
            putch(ch);
            } while (ch != 'Q');

        printf("\n\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 12-6.  The KEYS.C file.

    ──────────────────────────────────────────────────────────────────────────
    /* file.c  --  the file I/O routines for texed */

    Load_file()
    {
        printf("\nLoading ..... done.\n");
    }

    Save_file()
    {
        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
    screen.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ 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

    .c.obj:
        └──┴────────────────────────────────────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.MAK:

    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
    questions:

    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
    follows:

    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:

    texed.obj+
    keys.obj+
    file.obj
    Texed.exe
    Texed.map

    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
    dependency

    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
    menu()
    {
        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
    directory.

    ──────────────────────────────────────────────────────────────────────────
    #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"
    menu()
    {
        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
    texed.h,

    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
    file.

    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
    file.

    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.
    ──────────────────────────────────────────────────────────────────────────

Libraries

    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
    RIGHT$.)

    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
    model.

    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
    Operations:+leftstr.obj&
    Operations:+midstr.obj&
    Operations:+rightstr.obj
    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)
            free(cp);
        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)
            free(cp);
        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)
            free(cp);
        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>

    main()
    {
        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");
            exit(1);
            }
        printf("Leftstr() returned: \"%s\"\n", cp);

        if ((cp = Midstr(string, 4, 5)) == NULL)
            {
            printf("Error in Midstr()\n");
            exit(1);
            }
        printf("Midstr() returned: \"%s\"\n", cp);

        if ((cp = Rightstr(string, 5)) == NULL)
            {
            printf("Error in Rightstr()\n");
            exit(1);
            }
        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
    directory.

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
    files:

    qcl /c /AM leftstr.c
    qcl /c /AM midstr.c
    qcl /c /AM rightstr.c

    Now run LINK to create the Quick Library:

    link
    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
    follows:

    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.



────────────────────────────────────────────────────────────────────────────
PART 4  C AND THE HARDWARE
────────────────────────────────────────────────────────────────────────────



────────────────────────────────────────────────────────────────────────────
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.
    hat<Enter>
    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
    output:

    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
    output:

    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>
    main()
    {
        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 */
    main()
    {
        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>
    main()
    {
        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.

    ──────────────────────────────────────────────────────────────────────────
    Reminder
    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
    functions.

    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
    fragment:

    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";
    main()
    {
        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;

        do
            {
            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;
                    in_count++;
                    }
            entry[w_count] = '\0';
            if (in_count != w_count)
                    correct = FALSE;    /* too many chars */
            else
                    correct = (strcmp(entry, Password) == 0);
            g_count++;
            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.");
            return(1);
            }
        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;
                in_count++;
            }

    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:

    INPUT "ENTER NAME AND AGE: ",NAME$,AGE

    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
    pressed.

    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 */
    main()
    {
        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);
                    else
                        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:

    cgets()
    cprintf()
    cputs()
    cscanf()
    getch()
    getche()
    putch()
    ungetch()

    kbhit()
    inp()
    outp()

    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
    14.

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. */
    main()
    {
        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')
                {
                putch('\n');
                putch('\r');
                }
            else
                putch(ch);
    }
    ──────────────────────────────────────────────────────────────────────────

    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
    main()
    {
        char store[MAXSIZE + 2];

        store[0] = MAXSIZE; /* puts limit in first element */
        cputs("What's your name?\n\r");
        cgets(store);
        cputs("\n\rI'll remember you, ");
        cputs(store + 2);
        cputs("!\n\r");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-8.  The STRIO.C program.

    The following is a sample run of the program:

    What's your name?
    Steph
    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);
        ...
    cputs(start);

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
    ranking
    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:

    DEVICE=ANSI.SYS

    If the ANSI.SYS file is in a different directory from the CONFIG.SYS file,
    give the full pathname, as in the following example:

    DEVICE=C:\DOS\ANSI.SYS

    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
    ANSI.SYS:

    ESC[ASCIIcode;"string";ASCIIcodep

    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
    main()
    {
        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
                                                columns.
    Cursor Back    CUB            ESC[numD      Moves the cursor left num
                                                columns.
    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
                                                video.
    ──────────────────────────────────────────────────────────────────────────


    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",
                        "Quit"};
    char *Heading =
    "Use arrow keys to highlight choice. "
    "Use Enter key to select choice.";

    void showmenu(int);
    int getmesg(int);
    main()
    {
        int messno = 0; /* message to be highlighted */
        ED();
        showmenu(messno);
        while (messno != ITEMS - 1)
            {
            messno = getmesg(messno);
            ED();
            switch (messno)
                {
                case 0 :
                case 1 :
                case 2 :
                case 3 : printf("...pretending to work...");
                        printf("Hit any key to continue\n");
                        getch();
                        ED();
                        showmenu(messno);
                        break;
                case 4 : printf("Quitting!\n");
                        break;
                default: printf("Programming error!\n");
                        break;
                }
            }
    }

    /* showmenu() displays the menu  */
    void showmenu(highlite)
    int highlite;   /* message number to be highlighted */
    {
        int n;
        char *start;
        HOME();
        printf("%s", Heading);
        for (n = 0; n < ITEMS; n++)
            {
            if (n == highlite)
                start = VIDREV; /* turn on reverse video */
            else
                start = ATTOFF;
            printf("\n\n%s%s%s", start, Menu[n], ATTOFF);
            }
        HOME();
        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)
                                    {
                                    CUU(2);
                                    showmenu (--mnum);
                                    }
                                else
                                    {
                                    CUD(2 * ITEMS - 2);
                                    showmenu(mnum = ITEMS - 1);
                                    }
                                break;
                    case DOWN : if (mnum < ITEMS - 1)
                                    {
                                    CUD(2);
                                    showmenu(++mnum);
                                    }
                                else
                                    {
                                    CUU(2 * ITEMS - 2);
                                    showmenu(mnum = 0);
                                    }
                                break;
                    }
                }
                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
    them.

    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
                        registers.
    int86()            Invokes interrupts not requiring use of the segment
                        registers.
    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
                        registers.
    _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
    routine.

    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()
    works.

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
    following:

    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;
                                        word
                    ┌───────────────────┬───────────────────┐
                    / │                   │                   │ \  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
    interrupts.

    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
    function.

    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()
    function.

    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
    menu).

    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 */
    #define GETCHAR 0     /* read scancode function  */
    #define ESC '\033'    /* escape key              */
    struct SCANCODE {
                    unsigned char ascii;  /* ascii code */
                    unsigned char scan;   /* scan code  */
                    };
    struct SCANCODE Readkey();

    main()
    {
        struct SCANCODE keys;

        printf("Press keys to see their scancodes. ");
        printf("Press the Esc key to quit.\n");
        do  {
            keys = Readkey();
            if (keys.ascii > 0 && keys.ascii < 040)
                printf("^%c: ascii = %3d, scan = %3d\n",
                        keys.ascii + 0100, keys.ascii,
                        keys.scan);
            else if (keys.ascii >= 40)
                printf(" %c: ascii = %3d, scan = %3d\n",
                        keys.ascii, keys.ascii, keys.scan);
            else
                printf("  : ascii = %3d, scan = %3d\n",
                        keys.ascii, keys.scan);
            } while (keys.ascii != ESC);
    }

    struct SCANCODE Readkey()
    {
        union REGS reg;
        struct SCANCODE scancode;

        reg.h.ah = GETCHAR;
        int86(KEYINTR, &reg, &reg);
        scancode.ascii = reg.h.al;
        scancode.scan = reg.h.ah;
        return (scancode);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-12.  The SHOWCODE.C program.

    The scan code for the first four characters is the same (50) because the
    same primary key (the M key) was used in each case. The modifying key, if
    any, then caused the ASCII part of the code to be changed. Note how the
    ASCII part for Alt-M is 0. Also note how the Enter key has the same ASCII
    code as Ctrl-M but a different scan code. The scan code is different
    because a different physical key was pressed. Incidentally, if you need to
    write a program that discriminates between input of Ctrl-M and the Enter
    key, you can use Readkey() to check the scan code. (The getch() function
    cannot distinguish between the two keystrokes.)

    The following represents another sample run; this time the input is F1,
    Ctrl-F1, Shift-F1, Alt-F1, and Esc:

        : ascii =   0, scan =  59───────────────────────────────────────────F1
        : ascii =   0, scan =  94──────────────────────────────────────Ctrl-F1
        : ascii =   0, scan =  84─────────────────────────────────────Shift-F1
        : ascii =   0, scan = 104───────────────────────────────────────Alt-F1
    ^[: ascii =  27, scan =   1──────────────────────────────────────────Esc

    In this example, the scan code changes even though the same primary key
    was pressed each time. With ASCII characters, the ASCII code discriminates
    among diferent combinations, but with the control keys, the ASCII byte is
    always 0, so the scan code itself must change. Also, notice that these
    keystrokes are nonprinting; therefore, the program displays only the
    codes.

    Finally, this example uses the following input: 1, the End key, Shift-End,
    and Esc:

    1: ascii =  49, scan =   2─────────────────────────────────────────────|
        : ascii =   0, scan =  79───────────────────────────────────────────End
    1: ascii =  49, scan =  79─────────────────────────────────────Shift-End

    Note how Shift-End and 1 produce the same ASCII code (49) but different
    scan codes (2 and 79). We mention this because the QuickC editor uses the
    Shift-End combination to highlight a line. If the programming for QuickC
    relied on getch(), that would be impossible to do. Apparently QuickC, like
    our program, checks the scan code too. This lets it assign a different
    function to Shift-End.


Cursor and Screen Control with BIOS Calls

    Now that you know how to use interrupts, we can extend that technique to
    cursor and screen control. To illustrate these applications, we will
    construct a rudimentary first step toward a word processor. With this
    program, you can do the following:

    ■  Start with a clear screen.

    ■  Enter text from the keyboard and see it on the screen.

    ■  Use the arrow keys to move the cursor.

    ■  Use the function keys to turn highlighting on and off.

    ■  Highlight or unhighlight existing text by moving the cursor over it.

    To provide these features, we'll construct a library of approximately a
    dozen BIOS-based functions. Rather than jumping back and forth between
    program development and BIOS use, we'll develop the entire library first.

    Incidentally, the QuickC Graphics Library, which we discuss in Chapter
    15, provides an alternative means for implementing these features. Using
    the Graphics Library, however, produces executable programs substantially
    larger than those using the BIOS approach.

The Video I/O Interrupt

    The first step is to find the appropriate interrupt routine. Interrupt
    0x10, the video I/O interrupt, controls the display. Because maintaining a
    video display is more complex than monitoring a keyboard, this interrupt
    turns out to be much more involved than the keyboard I/O interrupt. It
    provides many subroutines, or functions, and many of them use several
    registers. Table 13-6 on the following pages lists and describes the
    functions we use in this book. The table mentions "attributes" and
    "pages." Attributes, as we saw in our discussion of ANSI.SYS, determine
    how a character is to be displayed. A page is a screenful of display. Some
    video modes can store more than one page at once, although only one can be
    displayed at any given time. We discuss these terms further as needed.

    When using int86() to invoke these functions, you set AH to the
    appropriate function code number and initialize any other registers given
    in the description. The first argument to int86() should be 0x10, the
    interrupt number.

    Table 13-6 Selected Video I/O Interrupt 0x10 Functions
    ──────────────────────────────────────────────────────────────────────────
    FUNCTION CODE 0: Set the Display Mode
    Action:            Switches to desired mode and clears display.
    Register setup:    Place 0 in AH
                        Place desired mode in AL

    Choose from the following modes:
    Mode               Meaning
    0                  40 x 25 B/W Text
    1                  40 x 25 Color Text
    2                  80 x 25 B/W Text
    3                  80 x 25 Color Text
    4                  320 x 200 Color Graphics
    5                  320 x 200 B/W Graphics
    6                  640 x 200 B/W Graphics
    7                  80 x 25 Monochrome
    13                 320 x 200 Color EGA
    14                 640 x 200 Color EGA
    15                 640 x 350 B/W EGA
    16                 640 x 350 Color EGA
    17                 640 x 480, 2-Color VGA
    18                 640 x 480, 16-Color VGA
    19                 320 x 200, 256-Color VGA

    FUNCTION CODE 2: Select Cursor Position
    Action:            Moves cursor to the specified row and number.
    Register setup:    Place 2 in AH
                        Place row number in DH
                        Place column number in DL
                        Place page number in BH

    Numbering of rows and columns starts with 0, not 1.

    FUNCTION CODE 3: Read Cursor Position
    Action:            Reports the row and column of cursor position.
    Register setup:    Place 3 in AH
                        Place page number in BH
    Returns:           Row number is in BH
                        Column number is in DL
                        Cursor type is in CH, CL

    FUNCTION CODE 5: Select Active Display Page
    Action:            Selects the page for modes supporting multiple pages.
    Register setup:    Place 5 in AH
                        Place page number in AL

    FUNCTION CODE 6: Scroll Up an Area of the Screen
    Action:            Scrolls up a section of the screen a specified amount.
    Register setup:    Place 6 in AH
                        Place number of lines to scroll in AL (0 in AL produces
                        a blank window)
                        Place blank-line attribute in BH
                        Place upper-left row number in CH
                        Place upper-left column number in CL
                        Place lower-right row number in DH
                        Place lower-right column number in DL

    FUNCTION CODE 8: Read Character and Attribute
    Action:            Reports the character and attribute code at the current
                        cursor position.
    Register setup:    Place 8 in AH
                        Place page number in BH (text modes)
    Returns:           Character at cursor is in AL
                        Attribute at cursor is in AH

    FUNCTION CODE 9: Write Character and Attribute
    Action:            Writes a specified character and attribute to the
                        current cursor position.
    Register setup:    Place 9 in AH
                        Place page number in BH (text modes)
                        Place character in AL
                        Place attribute (text modes) or color (graphics modes)
                        in BL
                        Place number of characters in CX

    Note: The character is written the indicated number of times starting at
    the current cursor position; the cursor position remains unchanged.

    FUNCTION CODE 15: Return Current Video State
    Action:            Reports the video mode, number of text columns, and the
                        current page value.
    Register setup:    Place 15 in AH
    Returns:           Current mode is in AL
                        Number of columns is in AH
                        Current active page number is in BH
    ──────────────────────────────────────────────────────────────────────────

Developing a Library of C Functions

    Our next step is to develop a set of C functions that use the video I/O
    interrupt. In this section, we will design several functions, each general
    enough to be useful for a variety of programs. We'll develop the functions
    individually but then collect them in one file so that they can share a
    common set of include files and definitions. You'll find the contents of
    this combined file, which is called SCRFUN.C, in Listing 13-23 beginning
    on p. 441. Finally, we'll use the LIB utility to make a library of the
    video functions.

    Setting the Cursor

    First, we need two C functions: one to set the cursor and another to
    report the current cursor position. We use functions 2 and 3 of the video
    interrupt to develop our own Setcurs() and Getcurs() functions (Listing
    13-13).

    Pass the desired row, column, and page to Setcurs(), and it positions the
    cursor. Use Getcurs() to place row and column information in variables
    whose addresses we pass. What about the page variable? For now, use the
    default value of 0. The following SETCURS.C program (Listing 13-14) is a
    short example that uses Setcurs() to see if our programming is on the
    right track.

    After you type in a row and column in the form 10 20, the program places
    the cursor there and then prints a message starting at that location. It's
    not a spectacular program, but it shows that our function is working
    correctly. As we build this library with other functions, you might want
    to write similar test programs. With QuickC it doesn't take long to do.

    ──────────────────────────────────────────────────────────────────────────
    #include <dos.h>
    #define VIDEO 0x10
    #define SETCURSOR 2
    #define GETCURSOR 3

    /* Setcurs() -- sets cursor to given row, column */
    void Setcurs(row, col, page)
    unsigned char row, col, page;
    {
        union REGS reg;

        reg.h.ah = SETCURSOR;
        reg.h.dh = row;
        reg.h.dl = col;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
    }

    /* Getcurs() -- reports current cursor position */
    void Getcurs(pr, pc, page)
    unsigned char *pr, *pc, page;
    {
        union REGS reg;

        reg.h.ah = GETCURSOR;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
        *pr = reg.h.dh;  /* row number */
        *pc = reg.h.dl;   /* column number */
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-13.  The setcurs() function.

    ──────────────────────────────────────────────────────────────────────────
    /* setcurs.c -- moves cursor, checks out Setcurs() */
    #include <dos.h>
    #include <stdio.h>
    #define VIDEO 0x10
    #define SETCURSOR 2
    void Setcurs(unsigned char, unsigned char,
                unsigned char);
    main()
    {
        int row, col;

        printf("Enter row and column: (q to quit)\n");
        while (scanf("%d %d", &row, &col) == 2)
        {
            Setcurs(row, col, 0);
            printf("Enter row and column: (q to quit)");
        }
    }

    /* Setcurs() -- sets cursor to row, column, and page */
    void Setcurs(row, col, page)
    unsigned char row, col, page;
    {
        union REGS reg;

        reg.h.ah = SETCURSOR;
        reg.h.dh = row;
        reg.h.dl = col;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-14.  The SETCURS.C program.

    Setting the page to 0 worked fine in our example; however, we may need to
    use pages later, so let's look at that topic.

    Getting and Setting the Page

    The information displayed on the screen is read from a dedicated section
    of memory called video memory. The amount of memory available depends upon
    the video adapter. In some modes, video memory can hold two or more
    screenfuls of data. In those cases, you can divide video memory into
    separate pages, one page per screenful. This lets a program alter one page
    in memory while displaying the other on the screen. To set a page, we will
    use the Setpage() function (Listing 13-15 on the following page).

    By default, screen modes start at page 0, and we'll also use that page for
    a while. But to keep our programming general, we need a function that can
    tell our code which is the current page. We use function 15 to develop the
    QuickC Getpage() function (Listing 13-16). The interrupt function places
    the page number in the BH register, and the function returns that value to
    the program.

    ──────────────────────────────────────────────────────────────────────────
    /* Setpage() -- sets page to given value */
    #include <dos.h>
    #define VIDEO 0x10
    #define SETPAGE 5
    void Setpage(page)
    unsigned char page;
    {
        union REGS reg;

        reg.h.ah = SETPAGE;
        reg.h.al = page;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-15.  The Setpage() function.

    ──────────────────────────────────────────────────────────────────────────
    /* Getpage() -- obtains the currently active page */
    #include <dos.h>
    #define VIDEO 0x10
    #define GETMODE 15
    unsigned char Getpage()
    {
        union REGS reg;

        reg.h.ah = GETMODE;
        int86(VIDEO, &reg, &reg);
        return reg.h.bh;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-16.  The Getpage() function.

    Clearing the Screen

    Another useful function is one that clears the screen. None of the
    interrupt functions specialize in that, but the Scroll Up function
    (function 6) can perform this task. Note in Table 13-6 that if register
    AL is set to zero, the entire designated area is cleared. However, several
    other registers also must be set. You define the area to be cleared by
    giving the coordinates of the upper-left and the lower-right corners.

    The BIOS routine starts numbering with 0, unlike ANSI.SYS, which starts
    with 1. This means the upper-left row and column are 0, the lower-right
    row is 24, and the lower-right column is 79. (We assume you're using an
    80-by-25 display.) The least straightforward register setting is the
    attribute setting for blank lines in register BH. An attribute is a value
    in the range 0 through 255 that modifies the display. The normal attribute
    is 7 for a "white-on-black" display. ("White" is white on a color display,
    but on a monochrome monitor "white" usually is green or amber.) Other
    values produce reverse video, blinking, underlining (on some monitors),
    and colors (on some monitors). We'll use the value 7.

    We use these register values to construct the following Clearscr()
    function (Listing 13-17).

    ──────────────────────────────────────────────────────────────────────────
    /*  Clearscr() -- clears the screen          */
    #include <dos.h>
    #define VIDEO 0x10
    #define SCROLLUP 6
    #define ROWS 25
    #define COLS 80
    void Clearscr()
    {
        union REGS reg;

        reg.h.ah = SCROLLUP;
        reg.h.al = 0;       /* clear the window */
        reg.h.ch = 0;
        reg.h.cl = 0;
        reg.h.dh = ROWS - 1;
        reg.h.dl = COLS - 1;
        reg.h.bh = NORMAL;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-17.  The Clearscr() function.

    Reading and Writing Characters and Attributes

    Before we use BIOS routines to read from and write to the screen, you need
    to know how the video system works. The video adapter has its own memory,
    which it uses to represent the screen. Let's concentrate for now on the
    80-by-25 text modes, the ones you probably use most often. All standard
    IBM video controllers (Monochrome, CGA, EGA, MCGA, and VGA) use the same
    scheme for their 80-by-25 text modes, so this discussion applies to all.

    ──────────────────────────────────────────────────────────────────────────
    Quick Tip
    If you are ambitious, you can generalize this function to work with
    40-by-25 displays by using function 15 of the 0x10 interrupt to find the
    number of columns actually being used. A call to function 15 places that
    number of columns into the AH register. Subtract 1 from this number (to
    account for the fact that column numbering begins with column 0), save the
    result, and assign it to the DL register before you call function 6.
    ──────────────────────────────────────────────────────────────────────────

    You can think of an 80-by-25 screen as holding 2000 cells, each capable of
    displaying a character. Each cell is represented by two bytes in the video
    memory. One byte holds the code for the character, and the second byte
    holds the attribute, which determines how the character is displayed. When
    a program sends output to the screen, the characters actually are first
    stored in video memory. A microprocessor called a video controller then
    scans the video memory, mapping the characters it finds there to the
    screen. Video interrupt 0x10 functions 8 and 9, which read and write
    characters and attributes to the screen, actually work with the video
    memory. (The monochrome display system uses a different memory address
    from the others, but the BIOS calls adjust for that.)

    The character code consists of the usual ASCII code plus extensions to the
    code that enable certain non-ASCII characters to be displayed on the
    screen. (IBM provides 128 such additional characters in its extended
    character set.) The attribute code also is simple, especially for
    black-and-white displays. Think of the attribute byte as a series of eight
    bits, numbers 7 to 0, left to right. To generate the normal
    black-and-white display, set the bits to 00000111. To produce reverse
    video, set the bits to 01110000. Note that these binary values translate
    to 0x7 and 0x70, respectively.

    In addition, you can intensify the foreground display by setting bit 3 to
    1 or put the display in "blink" mode by setting bit 7 to 1. The attributes
    we've discussed here produce white-on-black (or black-on-white) characters
    for the monochrome display and for color-text displays. We discuss
    color-related attributes in Chapter 14.

    To produce both normal and reverse video, our program must write the
    attribute as well as the character. We use video I/O function 9 instead of
    putch(), because the latter writes only characters, not attributes. Our
    Write_ch_atr() C function (Listing 13-18) uses that interrupt routine.
    This function writes the character-attribute pair num times to display a
    single pair several times in a row. We will use a num value of 1, but to
    preserve generality, we did not build that value into the function.

    One of our program goals was converting normal text to reverse video by
    passing the cursor over it. You can do that simply by changing the
    attribute at the cursor location. Because no BIOS function merely changes
    an attribute, we need to write a character-attribute pair. One way to do
    this is to read the current character from the screen and to then rewrite
    it using a different attribute. So let's start by devising a Read_ch_atr()
    (Listing 13-19) function to read the character and attribute at the
    current cursor location.

    Because the function must return two values, we pass it the addresses of
    the two variables to which the values will be assigned. To read the
    character and attribute at the current cursor position on page 0 into the
    variables ch and attr, make this call:

    Read_ch_atr(&ch, &attr, 0);

    We also could have the function return a two-member structure.

    ──────────────────────────────────────────────────────────────────────────
    /* Write_ch_atr() -- writes characters and attributes */
    #include <dos.h>
    #define VIDEO 0x10
    #define WRITECHATR 9
    void Write_ch_atr(ch, atr, page, num)
    unsigned char ch, atr, page;
    unsigned int num;
    {
        union REGS reg;

        reg.h.ah = WRITECHATR;
        reg.h.al = ch;
        reg.h.bl = atr;
        reg.h.bh = page;
        reg.x.cx = num;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-18.  The Write_ch_atr() function.

    Now we use the last two functions to produce the function our program
    requires. The Rewrite() function (Listing 13-20 on the following page)
    reads the current character and rewrites it with a potentially changed
    attribute.

    If speed is an issue, which it usually isn't for keyboard input, you can
    speed up Rewrite() by having it use int86() to call the read and write
    BIOS functions directly instead of going through Read_ch_atr() and
    Write_ch_atr().

    ──────────────────────────────────────────────────────────────────────────
    /* Read_ch_atr() -- reads character and attribute at     */
    /*                  cursor location                      */
    #include <dos.h>
    #define VIDEO 0x10
    #define  READCHATR 8
    void Read_ch_atr(pc, pa, page)
    unsigned char *pc, *pa;
    unsigned char page;
    {
        union REGS reg;

        reg.h.ah = READCHATR;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
        *pc = reg.h.al;  /* character at cursor */
        *pa = reg.h.ah;  /* attribute at cursor */
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-19.  The Read_ch_atr() function.

    ──────────────────────────────────────────────────────────────────────────
    /* Rewrite() -- changes attribute of on-screen  */
                    character                       */
    void Read_ch_atr(), Write_ch_atr(); /* used by  */
                                        /* Rewrite()*/
    void Rewrite(at, page)
    unsigned char at, page;
    {
        unsigned char ch, atr;

        Read_ch_atr(&ch, &atr, page);
        Write_ch_atr(ch, at, page, 1);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-20.  The Rewrite() function.

    More Cursor Movement

    We already have a function to set the cursor at a given row or column. But
    our primitive text editor really needs functions to move the cursor one
    column to the right when the Right Arrow key is pushed, and so on. We can
    use Setcurs() to create such functions. The Cursrt_lim() function (Listing
    13-21) demonstrates how to construct a right-movement function.

    Getcurs() and Setcurs() require the current page number; the Cursrt_lim()
    function uses Getpage() to obtain that information. Also, the function
    prevents the cursor from going past the column defined by limit. Our
    program will use a limit of 79, corresponding to the right side of the
    screen, but the numeric value is not built into the function. This
    variable limit lets you use the function with a program that confines the
    cursor to a section of the screen or with one that uses a 40-column
    screen.

    ──────────────────────────────────────────────────────────────────────────
    /* Cursrt_lim() -- moves cursor one space to the       */
                        right, but not past a set limit     */
    void Getcurs(), Setcurs();  /* functions used   */
    unsigned char Getpage();    /* by this function */

    unsigned char Cursrt_lim(limit)
    unsigned char limit;
    {
        unsigned char row, col, page;
        unsigned char status = 1;

        Getcurs(&row, &col, page = Getpage());
        if (col < limit)
            Setcurs(row, col + 1, page);
        else
            status = 0;
        return status;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-21.  The Cursrt_lim() function.

    Also, the program uses a return value to inform the calling program
    whether the cursor reached its limit. This gives the calling program the
    option of responding in some way, such as beeping or moving the cursor to
    the beginning of the next line, whenever the limit is reached.

    We can modify Cursrt_lim() to create functions corresponding to the other
    arrow keys. We'll show you these when we gather all the functions together
    into one file.

    Putting the Library Together

    By now we've created a small library of short, BIOS-based C functions.
    Before we use them in our intended sample program, let's reflect on how to
    organize this block of functions. One method is to give each its own file.
    Then, when we want to use a particular function in a program, we can add
    its filename to the QuickC programming list. Or we can simply append the
    function file to the program file. Another approach is to consolidate all
    the functions into one file and to add that file to the program list. This
    is more convenient, but it might result in adding code to your program for
    functions it doesn't use. If you use the functions frequently, the most
    satisfactory approach is to make a library file for them. (This procedure
    was described in Chapter 12.)

    Here's one way to make the library. Open the SCRFUN.C file from QuickC.
    Choose Compile from the Run menu and specify the Obj option. Then choose
    Compile File to produce a file called SCRFUN.OBJ. Now go to MS-DOS and
    enter the LIB command. Answer the prompts as shown:

    Library name: scrfun
    Library does not exist: create? y
    Operations: +scrfun
    List file: scrfun

    The LIB command creates a library file called SCRFUN.LIB in the current
    directory. You can then copy it to your library directory. The LIB command
    also creates a text file called SCRFUN that lists the names of the
    functions in the library.

    To help organize these functions, gather all the defined constants
    together into an include file. To this file, add function prototypes for
    all the functions. Then you can use this include file with your program.
    You still must incorporate the actual code by appending the source files
    or adding files to the program list or by using a library, but using the
    include file saves you the trouble of having to declare the functions. It
    also includes definitions useful to a program. We'll use the scrn.h
    include file (Listing 13-22 on the following page) for our programs.

    ──────────────────────────────────────────────────────────────────────────
    /* scrn.h -- header file for BIOS video I/O functions */
    /*           contained in scrfun.c and scrfun.lib     */
    #define VIDEO 0x10
    #define SETMODE 0
    #define SETCURSOR 2
    #define GETCURSOR 3
    #define SETPAGE 5
    #define SCROLL 6
    #define READCHATR 8
    #define WRITECHATR 9
    #define GETMODE 15
    #define NORMAL 0x7
    #define VIDREV 0x70
    #define INTENSE 0x8
    #define BLINK 0x80
    #define COLS 80
    #define ROWS 25
    #define TEXTBW80 2
    #define TEXTC80 3
    #define TEXTMONO 7

    void Clearscr(void),
        Setvmode(unsigned char),
        Setpage(unsigned char),
        Setcurs(unsigned char, unsigned char,
                unsigned char),
        Read_ch_atr(unsigned char *, unsigned char *,
                    unsigned char),
        Write_ch_atr(unsigned char, unsigned char,
                    unsigned char, unsigned int),
        Rewrite(unsigned char, unsigned char),
        Getcurs(unsigned char *, unsigned char *,
                unsigned char);

    unsigned char Getvmode(void),
                Getpage(void),
                Curslt_lim(unsigned char),
                Cursrt_lim(unsigned char),
                Cursup_lim(unsigned char),
                Cursdn_lim(unsigned char);

    /* macro definitions */

    #define Home()        Setcurs(0, 0, Getpage())
    /* the next four macros set cursor limits to the      */
    /* full screen                                        */
    #define Curslt()      Curslt_lim(0)
    #define Cursrt()      Cursrt_lim(COLS - 1)
    #define Cursdn()      Cursdn_lim(ROWS - 1)
    #define Cursup()      Cursup_lim(0)
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-22.  The scrn.h include file.

    The scrn.h file includes some function numbers that we won't use until
    later chapters. It also has some constants that we'll use in our program.
    Finally, note the macros at the end of the file. The Home() macro homes
    the cursor, and the cursor-movement macros select a range corresponding to
    the entire screen.

    For convenience, we've collected all the new functions together as shown
    in Figure 13-6 in a file called SCRFUN.C (Listing 13-23). We discuss the
    Getvmode() and Setvmode() functions in Chapter 15.

                                    ┌───────────────────┐
                        ┌─────┐      │                   │
            Setcurs() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
        Getcurs() │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
            Setpage()  │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
        Setvmode() │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
            Clearscr() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
    Read_ch_atr() │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
        Write_ch_atr() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
        Rewrite() │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
            Getvmode() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
        Getpage()  │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
            Curslt_lim() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
    Curst_lim() │     ├────────────┼─────              │
                └─────┘            │                   │
                        ┌─────┐      │                   │
            Cursup_lim() │     ├──────┼───────────        │
                        └─────┘      │                   │
                ┌─────┐            │                   │
    Cursdn_lim() │     ├────────────┼─────              │
                └─────┘            │                   │
                                    └───────────────────┘
                                            SCRFUN.C

    Figure 13-6. The SCRFUN.C program combines the functions we created
    previously.

    ──────────────────────────────────────────────────────────────────────────
    /*  scrfun.c -- contains several video BIOS calls     */
    /*  Setcurs() sets the cursor position                */
    /*  Getcurs() gets the cursor position                */
    /*  Setpage() sets the current video page             */
    /*  Setvmode() sets the video mode                    */
    /*  Clearscr() clears the screen                      */
    /*  Read_ch_atr() reads the character and             */
    /*                attribute at the cursor             */
    /*  Write_ch_atr() writes a character and             */
    /*                 attribute at the cursor            */
    /*  Rewrite() rewrites a screen character             */
    /*            with a new attribute                    */
    /*  Getvmode() gets the current video mode            */
    /*  Getpage() gets the current video page             */
    /*                                                    */
    /*  The following functions use Setcurs() to move the */
    /*  cursor one position at a time up to a limit.      */
    /*  Curslt_lim() moves cursor one column left         */
    /*  Cursrt_lim() moves cursor one column right        */
    /*  Cursup_lim() moves cursor one line up             */
    /*  Cursdn_lim() moves cursor one line down           */
    /*                                                    */
    /*  Programs using these functions should include the */
    /*  scrn.h file                                       */

    #include <dos.h>
    #include "scrn.h"

    /* sets cursor to row, column, and page */
    void Setcurs(row, col, page)
    unsigned char row, col, page;
    {
        union REGS reg;

        reg.h.ah = SETCURSOR;
        reg.h.dh = row;
        reg.h.dl = col;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
    }

    /* gets current cursor row, column for given page */
    void Getcurs(pr, pc, page)
    unsigned char *pr, *pc, page;
    {
        union REGS reg;

        reg.h.ah = GETCURSOR;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
        *pr = reg.h.dh;  /* row number */
        *pc = reg.h.dl;   /* column number */
    }

    /* sets page to given value */
    void Setpage(page)
    unsigned char page;
    {
        union REGS reg;

        reg.h.ah = SETPAGE;
        reg.h.al = page;
        int86(VIDEO, &reg, &reg);
    }

    /* sets video mode to given mode */
    void Setvmode(mode)
    unsigned char mode;
    {
        union REGS reg;

        reg.h.ah = SETMODE;
        reg.h.al = mode;
        int86(VIDEO, &reg, &reg);
    }

    /* clear the screen */
    void Clearscr()
    {
        union REGS reg;

        reg.h.ah = SCROLL;
        reg.h.al = 0;
        reg.h.ch = 0;
        reg.h.cl = 0;
        reg.h.dh = ROWS - 1;
        reg.h.dl = COLS - 1;
        reg.h.bh = NORMAL;
        int86(VIDEO, &reg, &reg);
    }

    /* reads the character and attribute at the cursor */
    /* position on a given page                        */
    void Read_ch_atr(pc, pa, page)
    unsigned char *pc, *pa;
    unsigned char page;
    {
        union REGS reg;

        reg.h.ah = READCHATR;
        reg.h.bh = page;
        int86(VIDEO, &reg, &reg);
        *pc = reg.h.al;  /* character at cursor */
        *pa = reg.h.ah;  /* attribute at cursor */
    }

    /* writes a given character and attribute at the */
    /* cursor on a given page for num times          */
    void Write_ch_atr(ch, atr, page, num)
    unsigned char ch, atr, page;
    unsigned int num;
    {
        union REGS reg;

        reg.h.ah = WRITECHATR;
        reg.h.al = ch;
        reg.h.bl = atr;
        reg.h.bh = page;
        reg.x.cx = num;
        int86(VIDEO, &reg, &reg);
    }

    /* rewrites the character at the cursor using    */
    /* attribute at                                  */
    void Rewrite(at, page)
    unsigned char at, page;
    {
        unsigned char ch, atr;

        Read_ch_atr(&ch, &atr, page);
        Write_ch_atr(ch, at, page, 1);
    }


    /* obtains the current video mode */
    unsigned char Getvmode()
    {
        union REGS reg;

        reg.h.ah = GETMODE;
        int86(VIDEO, &reg, &reg);
        return reg.h.al;
    }

    /* obtains the current video page */
    unsigned char Getpage()
    {
        union REGS reg;

        reg.h.ah = GETMODE;
        int86(VIDEO, &reg, &reg);
        return reg.h.bh;
    }

    /* moves cursor one column left, but not past */
    /* the given limit                            */
    unsigned char Curslt_lim(limit)
    unsigned char limit;
    {
        unsigned char row, col, page;
        unsigned char status = 1;
        Getcurs(&row, &col, page = Getpage());
        if (col > limit)
            Setcurs(row, col - 1, page);
        else
            status = 0;
        return status;
    }

    /* moves cursor one column right, but not past */
    /* the given limit                             */
    unsigned char Cursrt_lim(limit)
    unsigned char limit;
    {
        unsigned char row, col, page;
        unsigned char status = 1;

        Getcurs(&row, &col, page = Getpage());
        if (col < limit)
            Setcurs(row, col + 1, page);
        else
            status = 0;
        return status;
    }

    /* moves cursor one row down, but not past */
    /* the given limit                         */
    unsigned char Cursup_lim(limit)
    unsigned char limit;
    {
        unsigned char row, col, page;
        unsigned char status = 1;

        Getcurs(&row, &col, page = Getpage());
        if (row > limit)
            Setcurs(row - 1, col, page);
        else
            status = 0;
        return status;
    }

    /* moves cursor one row down, but not past */
    /* the given limit                         */
    unsigned char Cursdn_lim(limit)
    unsigned char limit;
    {
        unsigned char row, col, page;
        unsigned char status = 1;

        Getcurs(&row, &col, page = Getpage());
        if (row < limit)
            Setcurs(row + 1, col, page);
        else
            status = 0;
        return status;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-23.  The SCRFUN.C program.

    Our small routines certainly create a big file! However, you need only
    compile it once. After that, you can use the .OBJ or .LIB versions. We
    assume that you create a library file called SCRFUN.LIB.

A Text Program

    Finally, after much development, we have at hand all the tools we need for
    our program. The ROAMSCRN.C program (Listing 13-24) shows the results of
    our efforts. To run the program within the QuickC environment, be sure
    that Screen Swapping On is active.

    ──────────────────────────────────────────────────────────────────────────
    /*   roamscrn.c  -- puts text on screen, positions    */
    /*                  cursor with arrow keys, uses F1   */
    /*                  and F2 to control video inverse   */
    /*   program list -- roamscrn.c, scrfun.lib           */
    /*   user include files -- keys.h, scrn.h             */
    /*  Note: Activate Screen Swapping On in Debug menu   */
    #include <conio.h>
    #include "keys.h"
    #include "scrn.h"
    #define BELL '\a'
    #define ESC '\033'
    #define PAGE 0

    char *Heading =
    "Use standard keys to enter text. Use arrow keys to "
    "reposition cursor.\nUse F2 to turn on video inverse "
    "and F1 to turn it off.\nHit the ESC key to quit.\n";

    main()
    {
        int ch;
        unsigned char atr = NORMAL;

        Clearscr();
        Home();
        printf("%s", Heading);
        while ((ch = getch()) != ESC)
            {
            if (ch == '\r')
                {
                putch('\n');
                putch('\r');
                }
            else if (ch != 0)
                {
                Write_ch_atr(ch, atr, PAGE, 1);
                if (!Cursrt())
                    putch(BELL);
                }
            else
                {
                ch = getch();
                switch (ch)
                    {
                    case F1 : atr = NORMAL; break;
                    case F2 : atr = VIDREV; break;
                    case UP : Rewrite(atr, PAGE);
                        if (!Cursup())
                            putch(BELL);
                        break;
                    case DN : Rewrite(atr, PAGE);
                        if (!Cursdn())
                            putch(BELL);
                        break;
                    case LT : Rewrite(atr, PAGE);
                        if (!Curslt())
                            putch(BELL);
                        break;
                    case RT : Rewrite(atr, PAGE);
                        if (!Cursrt())
                            putch(BELL);
                        break;
                    default : break;
                    }
                }
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 13-24.  The ROAMSCRN.C program.

    Let's see how it works. The keys.h include file is the one we used earlier
    in this chapter; it defines the mnemonics for the function keys and the
    cursor control keys. The scrn.h include file is the one we just presented.
    We assume that you bring in the BIOS code by including the SCRFUN.LIB file
    in the program list, but you can also use one of the other methods we
    mentioned if you prefer.

    The program begins with the attribute variable atr set to NORMAL. This is
    defined in scrn.h as 7, which is the normal attribute for white-on-black
    text. Next, the program clears the screen, homes the cursor, and prints an
    instructive heading. Finally, in the main part of the program, a large
    while loop uses getch() to read keyboard input until Esc is pressed to
    terminate input.

    Next, the program inspects ch, the input character typed by the user. If
    it is \r, the carriage return character generated by the Enter key, the
    program translates that into a newline, that is, into \n\r. If the
    character is some other ASCII or extended ASCII value, the program uses
    Write_ch_atr() to display that character. Why not use putch() here?
    Because putch() has no provision for specifying the attribute. Note, too,
    the following code fragment:

    if (!Cursrt())
        putch(BELL);

    Write_ch_atr(), like the BIOS call it uses, does not advance the cursor
    after writing the character. Therefore, we use Cursrt() to move the
    cursor. Recall that we created Cursrt_lim() to stop when it reaches the
    right side of the screen and that the macro Cursrt() uses the rightmost
    column as the limit. If the limit is reached, Cursrt() returns a value of
    0, or false, causing the if statement to execute the putch(BELL) call. The
    action, then, is as follows: First the character is printed, then the
    program attempts to advance the cursor one column to the right. If it can,
    fine; otherwise, the system beeps. If you like, you can replace the
    beeping instructions with a Setcurs() command to relocate the cursor at
    the beginning of the next line.

    Finally, this sequence of if-else lines processes the case of ch being 0.
    This means the user entered a non-ASCII character. Another getch() call
    fetches the scan code for the key, and a switch checks for two of the
    function keys and for the arrow keys. Let's see what these keys do.

    If the user presses F1, the attribute variable atr is set to NORMAL; if
    the user presses F2, atr is set to VIDREV. This constant, defined in
    scrn.h as 0x70, is the reverse video attribute. The selected value for the
    variable atr is used in subsequent calls to Write_ch_atr() and Rewrite().
    The attribute setting holds until another is selected.

    Next, look at what happens when the Up Arrow key is pressed:

    case UP : Rewrite(atr, PAGE);
        if (!Cursup())
            putch(BELL);
        break;

    The Rewrite() function reads the character, if any, at the current cursor
    position and rewrites it using the current attribute. Then the cursor is
    moved up a line unless it already is at the top line. In that case, the
    system beeps. The purpose of the Rewrite() statement is to cause existing
    text to be replaced by text using the current attribute. For example, if
    you have selected the inverse attribute, then text passed over by the
    cursor is rewritten with that attribute. The coding for the other arrow
    keys is similar.

    All in all, the main program is fairly simple. Most of the work involved
    creating C functions to implement the various BIOS calls we needed to
    make.



────────────────────────────────────────────────────────────────────────────
Chapter 14  Monitors and Text Modes

    Professional application programs, including QuickC itself, use a much
    fancier screen interface than we have used in our programs. In this
    chapter we produce some of those screen features in QuickC. First we must
    overcome the problem posed by the variety of different display systems.
    IBM supports several monitor-video controller systems: monochrome, CGA,
    EGA, MCGA, and VGA. In general, these systems use different hardware, and
    different memory and port addresses. They also provide different colors,
    resolutions, and graphics capabilities.

    Writing programs that run on a range of video controller systems can be
    troublesome, especially if you want something fancier or faster than the
    teletype-like output produced by standard C Library functions. This
    chapter concentrates on solving these problems for text-mode programs. We
    continue using BIOS calls, and we introduce direct memory access and
    ports. We also look at the IBM "graphics character" set, which lets you
    create screen graphics without leaving text mode.


Monitors and Controllers

    IBM has developed several different video standards, each involving its
    own hardware video controller and corresponding monitors. In the PC
    series, the hardware controllers are on add-on cards called "adapters." In
    the new PS/2 series, however, the circuitry for controlling the monitor is
    built into the motherboard. We use the term "video controller" in this
    book to encompass both the adapter cards and the built-in control
    circuitry.

    The most widely used video controller is the Monochrome Display Adapter,
    or MDA. When coupled with a monitor called the Monochrome Display, it
    produces a high-resolution, text-only display consisting of 25 rows of 80
    characters each.

    The next most commonly used controller is the Color Graphics Adapter, or
    CGA. It can be used with color or B/W monitors capable of either 40-by-25
    or 80-by-25 text displays (but not the Monochrome Display). It has seven
    separate modes of operation. Although the 80-by-25 display shows as many
    characters as the Monochrome Display, its lower resolution creates coarser
    text characters.

    Recently, the Enhanced Graphics Adapter, or EGA, has become popular. It is
    compatible with the Monochrome Display, with normal CGA displays, and with
    a high-resolution monitor called the Enhanced Display (ED). Used with a
    Monochrome Display, it provides a graphics mode in addition to the text
    mode. Used with CGA-style monitors, it provides more colors than the CGA
    board does. Used with the Enhanced Display (or equivalent), it emulates
    the CGA modes with increased text resolution, and it provides three
    additional graphics modes.

    The newest controllers are the Multi-Color Graphics Array (MCGA), found on
    the PS/2 Model 30, and the Video Graphics Array (VGA), found on the PS/2
    Models 50, 60, 70, and 80. The MCGA matches CGA resolution but offers an
    enormously greater range of colors. The VGA emulates the EGA modes, adds
    three new graphics modes, offers higher resolution for all text modes, and
    provides more colors.

    Table 14-1 summarizes some of the differences in features offered by the
    various video controllers we have introduced. Resolution is given in
    pixels, or picture elements, the elementary display elements from which
    characters and images are built. The size of a pixel depends on the
    controller and the mode. A pixel in mode 0, for example, is twice as wide
    as a pixel in mode 2. However, the VGA controller produces smaller pixels
    than the CGA controller, even when both are in mode 0. Also, not all
    monitors have sufficient resolution to support a controller's use of
    pixels. A CGA monitor, for example, is physically incapable of generating
    the higher resolution (smaller pixel) modes of the EGA and VGA. In
    general, all modes cannot produce the maximum number of colors, and only a
    subset of available colors can be shown at any one time.

    Table 14-1 Video Controllers
    Name    Horizontal       Vertical        Colors  Modes    Monitors
            Resolution       Resolution
    ──────────────────────────────────────────────────────────────────────────
    MDA     720              350             2       1        MD☼
    CGA     640/320          200             16      7        Color☼, B/W☼
    EGA     640/320          350/200         64      12       ED☼, MD☼,
                                                            Color☼, B/W☼
    MCGA    720/360/640/320  400/480/200     262,    11       PSM☼, ED☼, MD☼,
                                            144              Color☼, B/W☼
    VGA     720/360/640/320  400/480/350/200 262,    15       PSM☼, ED☼, MD☼,
                                            144              Color☼, B/W☼
    ──────────────────────────────────────────────────────────────────────────


Text Modes and Portability

    Fortunately, all these video controllers support an 80-by-25 text mode,
    and that simplifies the task of writing programs to run with all
    combinations.

Controller Similarities

    A comparison of 80-by-25 text modes for different hardware combinations
    shows both similarities and differences. In all cases, the screen is
    treated as an array of characters rather than as an array of pixels. That
    is, you can only display or alter entire characters, not the individual
    pixels that comprise the characters.

    All controllers use two bytes of memory to represent each text-mode
    character. One byte holds the character's ASCII code, and the other byte
    holds the character's display attribute. All video controllers also
    contain random access memory (video RAM) in which character data is mapped
    to the display. That is, the controller periodically scans the video RAM
    to determine which characters it should display. Therefore, to change the
    screen display, you must change the appropriate bytes in the video RAM.
    Note that text-mode video RAM always consists of 4000 bytes: One screen
    holds 80 x 25, or 2000, characters, each represented by two bytes.

    All controllers also maintain a table of character fonts called a
    "character generator." The controller uses these pixel patterns to
    physically represent characters on the screen. For example, ASCII code 72
    in the video RAM tells the controller to put an H at a screen location,
    and the character font table specifies the particular "H" pixel pattern to
    use. (See Figure 14-1 on the following page.)

    These similarities ease the task of writing text programs that are
    compatible with the various displays.

        Video RAM                       Font ROM
    ┌─────────────────┐           ┌─────────────────┐
    │ 072 (ASCII code)│           │ H (Font info)   │
    │  ▒              │           │ ▒               │
    └──▒──────────────┘           └─▒───────────────┘
        ▒                            ▒
        ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
                ▒
            ╔═════▒═════════════════════╗
            ║  ┌──▼──────────────────┐  ║
            ║  │  H                  │  ║
            ║  │                     │  ║
            ║  │                     │  ║
            ║  │                     │  ║
            ║  │                     │  ║
            ║  └─────────────────────┘  ║
            ╚═══════════════════════════╝
                    Display

    Figure 14-1. Producing characters with the MDA.

    Differences between displays complicate the programming process. For
    example, one powerful video technique called "direct memory access" uses
    pointers to video RAM to directly alter RAM contents. However, the MDA
    uses a different video RAM address than other controllers, so your program
    must always test for its use. Another programming technique uses "ports"
    to access registers on the controllers. However, the various controllers
    have different numbers of registers, and each register has a different
    port address and performs a different function. This makes for very
    involved hardware programming.

    Video controllers also contain differing amounts of video RAM. The MDA has
    only enough memory to hold one screenful, or page, of characters. The
    other controllers hold enough memory to hold four or more pages of text.

    Most controllers offer different screen resolutions. Although all the
    controllers display a maximum of 2000 characters on the screen, some can
    generate more pixels than others. For example, the CGA screen consists of
    a matrix of 640 horizontal pixels (for 80 characters) by 200 vertical
    pixels (for 25 display lines). The net result is that each character is
    represented by an 8-by-8-pixel grid, or "character box." The MDA, on the
    other hand, generates 720 horizontal pixels and 350 lines, providing a
    9-by-14-pixel character box. Thus, MDA characters look better than their
    CGA counterparts because each character is drawn with more detail, as
    shown in Figure 14-2.

    Finally, the color displays can use the attribute byte to specify
    foreground and background colors for each character. Table 14-2 provides
    a summary of the different characteristics of video controllers operating
    in text mode.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 14-2 can be found on p.453 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 14-2. Character boxes.

    Table 14-2 Summary of Text-Mode Differences
    Controller           Video RAM Starting   Pages      Character Color
                        Address                         Box Size
    ──────────────────────────────────────────────────────────────────────────
    MDA                  0xB8000              1          9 x 14    No
    CGA                  0xB0000              4          8 x 8     Yes
    EGA (mode 7)         0xB8000              4/8☼       9 x 14    No
    EGA (modes 2, 3)     0xB0000              4/8☼       8 x 14    Yes
    MCGA                 0xB0000              8          9 x 16    Yes
    VGA                  0xB0000              8          9 x 16    Yes
    ──────────────────────────────────────────────────────────────────────────


Device-independent Programming

    When programming for the PC, you have the choice of programming for
    specific hardware or ignoring the hardware altogether. The direct memory
    access method discussed later in this chapter uses hardware information
    explicitly. To write device-independent programs that don't require
    explicit hardware information, use one or more of the following methods:

    1.  Program with the standard C Library output functions such as printf()
        and putchar(). This results in portable code, but it limits the
        positioning of text and doesn't permit the use of color.

    2.  Use the ANSI.SYS escape sequences, as described in Chapter 13.
        However, if your program utilizes the cursor-control keys, for
        example, you must use console I/O functions, which restrict
        portability. Also, using ANSI.SYS inhibits some special features of
        the EGA, such as the 43-line display. The ANSI approach works on all
        systems that recognize the standard ANSI codes; IBM PCs and clones
        must have the ANSI.SYS driver installed.

    3.  Use IBM PC BIOS calls, as described in Chapter 13. The BIOS includes
        programs for all PC video controllers, and it selects the appropriate
        code for the display and controller you are using. That's why our
        examples in Chapter 13 didn't specify a monitor or controller. BIOS
        calls also support using more than one page of screen memory; but
        because the MDA has only one page, we suggest you restrict
        applications to page 0.

    We thoroughly covered the first two choices in the last chapter. Although
    we also discussed BIOS calls, we skipped some of the detail until you
    understood more about the hardware. In the next section we will discuss
    BIOS calls in greater detail.

Working with BIOS Again: Attributes

    In Chapter 13, we built a small library (SCRFUN.LIB) of BIOS-based C
    functions that are hardware-independent. In fact, insulating the user from
    the hardware is one of the primary reasons for having a BIOS. For
    instance, we can use the same BIOS calls to control the way in which
    characters are displayed on an MDA, CGA, EGA, or VGA monitor. The
    attribute of a character controls its appearance. Let's see how we can use
    the BIOS to investigate and control attributes.

    An attribute is a 1-byte value in which the individual bits have
    particular meanings that affect the appearance of the associated
    character. For example, with the Monochrome Display Adapter, bit 7 of the
    attribute controls the blink function, bits 6─4 control the background,
    bit 3 controls the intensify foreground function, and bits 2─0 control the
    foreground──that is, the pixels constituting the character. (See Figure
    14-3.) Table 14-3 lists the standard attribute values used by the MDA.

    Bit numbers ─────7     6     5     4     3     2     1    0
                ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
                │     │     │     │     │     │     │     │     │
                └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                    │  └────────┬────────┘  │  └────────┬────────┘
                    │       Background      │       Foreground
                Blink bit             Intensity bit

    Figure 14-3. Monochrome attribute bits.

    Table 14-3 Monochrome Attributes
    Bit Pattern        Hex Value         Meaning
    ──────────────────────────────────────────────────────────────────────────
    0000 0000          0x00              No display
    0000 0111          0x07              Normal display
    0111 0000          0x70              Reverse video
    0000 0001          0x01              Underline
    0111 0111          0x77              Whiteout
    1xxx xxxx          0x80              Blink mode
    xxxx 1xxx          0x08              Intensified foreground
    ──────────────────────────────────────────────────────────────────────────

    Note: The last two entries are used with different modes. For example,
    10000111 (0x87) is normal display with blinking, while 11110000 (0xF0) is
    reverse video with blinking. The x's indicate that those values don't
    affect blinking or intensity.

    The other video controllers use bits 6─4 to control the color of the
    background and bits 2─0 to control the color of the foreground. Bits 7 and
    3 serve the same function as they do for the MDA. Figure 14-4 shows the
    color that each bit controls.

    Bit numbers ─────7     6     5     4     3     2     1    0
                ┌─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┐
                │ BL  │  R  │  G  │  B  │  I  │  R  │  G  │  B  │
                └─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┘
                    │  │ Red  Green Blue │  │  │ Red  Green Blue │
                    │  └────────┬────────┘  │  └────────┬────────┘
                    │       Background      │       Foreground
                    Blink                 Intensify
                                        foreground

    Figure 14-4. Color attribute bits.

    ──────────────────────────────────────────────────────────────────────────
    The EGA BIOS
    The IBM BIOS was created long before the inception of the EGA. How, then,
    can you use BIOS routines to control the EGA? The EGA card comes with a
    set of BIOS interrupt 0x10 video I/O routines in its own ROM. Recall that
    the address of each interrupt routine is stored in the interrupt vector
    table. When you boot an EGA system, the entry for video interrupt 0x10 is
    loaded with the EGA BIOS address instead of the motherboard BIOS address.
    Thus, the old BIOS routines are bypassed and the new EGA-supplied ones are
    used.
    ──────────────────────────────────────────────────────────────────────────

    To produce a blue character on a red screen, use an attribute of 01000001.
    This turns on the red background bit and the blue foreground bit. To make
    the foreground a bright blue, turn on the intensity bit with 01001001.

    If you set both the blue and green foreground bits, the blue and the green
    phosphors on the display screen are simultaneously turned on, producing a
    color called cyan. Setting all three foreground bits turns on all three
    colors, which, by the laws of video color mixing, produces white.
    Similarly, clearing all three bits causes no pixels to be turned on,
    producing black. Therefore, the "normal" monochrome attribute of 00000111
    also produces white-on-black characters for the CGA, EGA, and VGA. Table
    14-4 shows the colors generated by the various 3-bit combinations.

    Table 14-4 Text Color Values
    Bit Pattern        Hex Foreground    Hex Background     Color
    ──────────────────────────────────────────────────────────────────────────
    000                0x0               0x00               Black
    001                0x1               0x10               Blue
    010                0x2               0x20               Green
    011                0x3               0x30               Cyan
    100                0x4               0x40               Red
    101                0x5               0x50               Magenta
    110                0x6               0x60               Dark yellow
                                                            (brown)
    111                0x7               0x70               White (light gray)
    ──────────────────────────────────────────────────────────────────────────

    Note: The hex value 0x8 intensifies the foreground color, and 0x80 makes
    the character blink. Also, all attributes can be combined using logical
    operators.

    ──────────────────────────────────────────────────────────────────────────
    Colors
    All colors can be produced by combining three primary colors together in
    varying proportions. The three "additive" primary colors are red, green,
    and blue. For example, when you direct a beam of green light and a beam of
    red light toward a piece of white paper, the area where the beams overlap
    (or are "added") appears yellow.

    A color video screen also creates colors by combining the additive
    primaries. Each pixel on a color screen contains individual red, green,
    and blue dots. Turning a pixel blue amounts to turning on the blue dots in
    the pixel. To produce yellow, you turn on the green and the red dots in a
    pixel──the eye perceives only the combined light, which is yellow.

    The PC's system of using numbers to represent color imitates the physical
    color-mixing process. For example, in binary notation, the color number
    for red is 100 and the color number for green is 010. Turning on both the
    red and the green bits corresponds to specifying the binary number 110,
    which is the code for yellow.
    ──────────────────────────────────────────────────────────────────────────

    Suppose you want a yellow character on a blue background. Because yellow
    is produced by combining red and green light (bits 2 and 1), and
    background blue is bit 4, the corresponding attribute is 00010110, or
    0x16. (The actual colors you see depend on your monitor and its
    adjustments.)

    It's more interesting to display the attributes on the screen than it is
    to read about them, so let's develop a program that changes the attribute
    bits to demonstrate how the colors change. First we must write a function
    that prints a string using a given attribute. Using the functions from our
    SCRFUN.LIB library, we produce the following Print_attr() function
    (Listing 14-1).

    Print_attr() writes a character-attribute pair and moves the cursor one
    position to the right. Print_attr() has its limitations. First, it doesn't
    recognize the end of a line. Second, it doesn't have all the fancy
    formatting that printf() has. (You can modify the function to handle the
    end-of-line problem and use the sprintf() function to do the formatting.)

    Now let's use the Print_attr() string-displaying function in a program to
    display the various attributes. To demonstrate the role of each attribute
    bit, the program has you type the attribute byte as a binary number. The
    comments in the ATTRIB.C program (Listing 14-2 on the following page)
    explain the workings of the various functions. Before you run the ATTRIB.C
    program, pull down the Debug menu: Screen Swapping On should be active
    (indicated by a check to the left of the option). If it is not active,
    choose it from the menu to activate it.

    This program works with any of the previously mentioned standard video
    controllers. If you have a monochrome monitor, check to see what
    nonstandard combinations such as 00000100 produce. If you have a color
    monitor, enjoy the many color combinations.

    ──────────────────────────────────────────────────────────────────────────
    /* Print_attr() -- prints the string str using */
    /* attribute attr on the indicated page        */
    /* It uses functions from the scrfun.c file.   */

    void Print_attr(str, attr, page)
    char *str;
    unsigned char attr, page;
    {
        while (*str != '\0')
            {
            Write_ch_atr(*str++, attr, page, 1);
            Cursrt();
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-1.  The Print_attr() function.

    ──────────────────────────────────────────────────────────────────────────
    /* attrib.c -- this program illustrates attributes */
    /* program list: attrib.c, scrfun.lib              */
    /* user include files: scrn.h                      */
    /* Note: activate Screen Swapping On in Debug menu */
    #include <stdio.h>
    #include <conio.h>
    #include "scrn.h"
    #define PAGE 0
    #define ESC '\033'
    char *Format = "This message is displayed using an "
                    "attribute value of %2X hex (%s).";
    int Get_attrib(char *);
    void Print_attr(char *, unsigned char, unsigned char);

    main()
    {

        int attribute;       /* value of attribute   */
        char attr_str[9];    /* attr. in string form */
        char mesg[80];

        Clearscr();
        Home();
        printf("Enter an attribute as an 8-digit binary "
                "number, such as 00000111, and see a\n"
                "message displayed using that attribute."
                "Hit <Esc> to quit.\n"
                "Attribute = ");
        while ((attribute = Get_attrib(attr_str)) != -1)
            {
            Setcurs(10,0,PAGE);
            sprintf(mesg, Format, attribute, attr_str);
            Print_attr(mesg, attribute, PAGE);
            Setcurs(2, 12, PAGE);
            printf("         ");  /* clear old display */
            Setcurs(2, 12, PAGE);
            }
        Clearscr();
    }

    /* The following function reads in a binary number    */
    /* as a sequence of 1s and 0s. It places the 1 and 0  */
    /* characters in a string whose address is passed as  */
    /* an argument. It returns the numeric value of the   */
    /* binary number. Bad input is summarily rejected.    */
    /* The function returns -1 when you press Esc.        */
    int Get_attrib(a_str)
    char a_str[];     /* attribute as binary string */
    {
        int attrib[8];
        int index = 7;
        int ch;
        int attribute = 0; /* attrib. as numeric value */
        int pow;

        a_str[8] = '\0';  /* terminate string */
        while ((index >= 0) && (ch = getch()) != ESC)
            {
            if (ch != '0' && ch != '1')  /* bad input */
                putch('\a');
            else
                {
                putch(ch);
                a_str[index] = ch;      /* string form */
                attrib[index--] = ch - '0'; /* numeric */
                }
            }
        if (ch == ESC)
            return (-1);
        else            /* convert numeric array to a number */
            {
            for(index = 0, pow = 1; index < 8;
                                    index++, pow *= 2)
                attribute += attrib[index] * pow;
            return attribute;
            }
    }

    /* The following function prints the string str using */
    /* attribute attr on the indicated page.              */
    /* It uses functions from the scrfun.c file.          */

    void Print_attr(str, attr, page)
    char *str;
    unsigned char attr, page;
    {
        while (*str != '\0')
            {
            Write_ch_atr(*str++, attr, page, 1);
            Cursrt();
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-2.  The ATTRIB.C program.

Attributes and Bitwise Operators

    You can also manipulate attributes with the C bitwise operators that we
    discussed in Chapter 7. Suppose, for example, a program uses the
    following definitions:

    #define NORMAL 0x07
    #define VIDREV 0x70
    #define BLINK  0x80
    #define INTENSE 0x08

    To set mode to an intense, normal attribute, we can use the bitwise
    logical OR operator, as follows:

    mode = NORMAL | INTENSE;

    Because 1 OR anything is 1, all bits set to 1 are left on. Now, suppose
    mode has already gone through several changes. At this point it might be
    normal, reverse video, or have blinking on or off, etc. To turn on the
    intensify mode regardless of the current state, use an instruction like
    the following:

    mode = mode | INTENSE;

    The only bit this instruction can change is bit 3, the intensity bit,
    because all the other bits of INTENSE are 0, and 0 OR any bit is merely
    that bit. (That is, 0 | 0 is 0, and 1 | 0 is 1.) Furthermore, this
    instruction always sets bit 3 to 1, regardless of its previous value. (0 |
    1 is 1, and 1 | 1 is 1.) Incidentally, you can also use a combination
    assignment operator to rewrite the last C statement as follows:

    mode |= INTENSE;  /*unconditionally turns INTENSE on */

    Sometimes an instruction must "toggle" a bit. That is, the instruction
    turns on an off bit or turns off an on bit. For this, we use the EXCLUSIVE
    OR operator (^). Recall that this operator produces a "true" value (1) if
    one operand or the other is "true" but not if both are "true." The
    following expression toggles the intensity bit:

    mode = mode ^ INTENSE;

    If the intensity bit in mode is initially off, the expression becomes 0 ^
    1, which is 1, or on. If the intensity bit in mode is initially on, the
    expression is 1 ^ 1, which is 0, or off. Again, we can simplify the
    statement with the following combination assignment operator:

    mode ^= INTENSE;  /* toggles the intensity bit */

Compatible "Graphics"

    A system with true graphics capability lets you individually control each
    pixel on the screen. By its very makeup, the MDA lacks that ability. The
    CGA provides graphics modes, and the EGA and VGA have additional graphics
    abilities. Therefore, to produce true graphics, a program must address
    specific hardware.

    However, IBM has given the PC a limited but more universal graphics
    capability by extending the character set. The ASCII character set uses
    the values 0 through 127. But because a byte can store any value through
    255, IBM added 128 additional characters to the set and assigned them code
    values 128 through 255. These constitute the IBM Extended Character Set.
    Many of these characters are mathematical symbols, foreign-language
    characters, and so on. However, 48 of the characters (codes 176 through
    223) constitute the "graphics characters," which are useful for drawing
    and filling rectangular forms.

    With these characters, you can do a limited amount of hardware-independent
    graphics. The QuickC screen, for example, uses these characters to draw
    its boxes and borders. In fact, you can use QuickC to examine the extended
    ASCII set. Pull down the general Help menu and browse through it until you
    reach the screen that displays the extended set.

    You can also display the extended set of characters from the keyboard. To
    see what character 206 looks like, first press Num Lock; then, at your
    system prompt, hold down the Alt key and type 206 using the keys in the
    numeric keypad. When you release Alt, the character appears on the
    display.

Programming with the Graphics Character Set

    Let's develop a QuickC program to help us investigate the extended
    character set. Below are some of the features we need to develop in a
    program that draws with the graphics characters.

    ■  Key-mapping so that a single keystroke generates a graphics character

    ■  Cursor control for drawing at different screen locations

    ■  An erasing feature

    ■  An auto-drawing feature that generates strings of characters across the
        screen

    ■  An attribute manipulator for highlighting text or turning on blinking

    Many of these goals resemble problems we solved in Chapter 13; therefore,
    we can put our previously developed tools to good use now. For example, we
    can use the getch() function and scan codes to use the function keys and
    the cursor-control keys. We can call the BIOS to clear the screen and to
    provide cursor-movement functions. And by using the method developed for
    REKEY.C (Listing 13-7 on p. 411), we can map the keys. In short, we have
    the tools; now we have to organize them into a workable QuickC program.

    The User Interface

    Today it is not enough to design a program that works. Interactive
    programs require that the programmer think about the user's point of view.
    In our case, for example, we need to plan how to best use the keyboard to
    control the graphics characters.

    For example, which key represents which character? With 48 graphics
    characters, there is no obvious mnemonic method for assigning keys. And it
    is unreasonable to expect a user to remember 48 random assignments. To
    help the user, we display the graphics characters and key assignments at
    the bottom of the screen. We also list other important keys.

    Next, we must plan how to manage the drawing process. Drawing with
    graphics characters often involves repeatedly using the same character.
    Merely mapping a graphics character to a key is acceptable only for
    repeating the character left to right because that's the way keyboard
    input normally works. But drawing characters vertically or from right to
    left is more difficult. Cursor control helps, but drawing a vertical line
    would entail pressing the character key, using the Down Arrow key to move
    down a line, using the Left Arrow key to move under the first character,
    and then pressing the character key again. Therefore, we must use a
    different technique.

    In our solution to the problem, the character keys select, but do not
    display, a graphics character. To actually display the character, the user
    must press one of the arrow keys. This places the character at the current
    position of the cursor, then shifts the cursor in the direction of the
    arrow key. Until the user selects another character key, the current
    graphics character remains active. Therefore, repeatedly pressing an arrow
    key moves the cursor and leaves a display trail of the current graphics
    character. This simplifies drawing horizontal and vertical lines.

    Now let's add some refinements. The PgUp key disenables drawing so that
    the user can move the cursor without displaying characters. The PgDn key
    restores the drawing mode. The Spacebar represents the program's "eraser."
    The user can select the Spacebar as the current graphics character and use
    the cursor keys to delete unwanted characters. Our last refinement lets
    the user select character attributes with the function keys.

    We take advantage of QuickC's program list feature to split the program
    into three file modules. Note that two of the modules use SCRFUN.LIB and
    scrn.h and one uses keys.h, all developed in Chapter 13. We also collect
    the define statements for the three modules in an include file called
    grafchar.h.

    First, look at the main program, GRAFCHAR.C (Listing 14-3). To run this
    program within the QuickC environment, be sure that Screen Swapping On is
    active (on the Debug menu).

    GRAFCHAR.C is a simple program──it merely calls the other two files in the
    program list: initstuf.c and drawchar.c. We will discuss these modules in
    the next two sections. Before you proceed to those sections, however,
    examine the grafchar.h header file (Listing 14-4).

    ──────────────────────────────────────────────────────────────────────────
    /*   grafchar.c -- draws graphics characters with      */
    /*                 attributes on the screen            */
    /*  Program list : grafchar.c, initstuf.c, drawchar.c, */
    /*                  scrfun.lib                         */
    /*  User include files: keys.h, scrn.h, grafchar.h     */
    /*  Note: activate Screen Swapping On in Debug menu    */

    #include "grafchar.h"
    unsigned char Grchr[NUMCHARS];  /* to store graphics set */
    void Init_stuff(void);      /* in initstuf.c */
    void Draw_chars(void);      /* in drawchar.c */

    main()
    {
        Init_stuff();  /* initialize vital elements */
        Draw_chars();  /* map keys to graphics characters */
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-3.  The GRAFCHAR.C program.

    ──────────────────────────────────────────────────────────────────────────
    /* grafchar.h -- header file for grafchar.c program */
    /*               Version 1                          */
    #define NUMCHARS 48  /* number of graphics chars  */
    #define SPACE '\040'
    #define BOTLINE 19   /* line # for end of drawing space */
    #define PAGE 0
    #define GCSTART 0xB0 /* ascii for first graphics char */
    #define BEEP '\a'
    #define ESC '\033'
    #define TRUE 1
    #define FALSE 0
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-4.  The grafchar.h header file.

    Setting Up the Program: initstuf.c

    The initstuf.c module (Listing 14-5 on the following page) sets up the
    GRAFCHAR.C program. It initializes the external array grafchar.c to the 48
    graphics characters and clears the screen. At the bottom of the screen, it
    prints the graphics characters and their corresponding keystrokes. Also
    listed are the following non-ASCII keys and their functions: The F1
    function key sets the normal white-on-black text attribute; F2 selects
    reverse video; F3 toggles blinking; and F4 toggles foreground intensity.
    The program uses Print_attr() to show how the last three attributes appear
    on screen. For example, the phrase F3 : Blinking is displayed on screen in
    the blinking mode.

    ──────────────────────────────────────────────────────────────────────────
    /*  initstuf.c -- initializing module for grafchar.c */
    /*  assigns graphics character codes to an array     */
    /*  and initializes screen                           */

    #include "scrn.h"   /* Clearscr(), Home(), Setcurs() */
    #include "grafchar.h"
    extern unsigned char Grchr[];  /* defined in grafchar.c */
    void Print_attr(char *, unsigned char, unsigned char);
    [bn]
    void Init_stuff()
    {
        int i;

        /* initialize array with graphics characters */
        for (i = 0; i < NUMCHARS; i++)
            Grchr[i] = GCSTART + i;
        Clearscr();
        Home();

        /* show key meanings at bottom of screen */
        Setcurs(BOTLINE + 1, 0, PAGE);
        for (i = 0; i < 40; i++) /* graphics chars */
            {
            putch(Grchr[i]);
            putch(SPACE);
            }
        Setcurs(BOTLINE + 2, 0, PAGE);
        for (i = 0; i < 40; i++)  /* key assignments */
            {
            putch('0' + i);
            putch(SPACE);
            }
        Setcurs(BOTLINE + 3, 0, PAGE);
        for (i = 40; i < NUMCHARS; i++) /* second row */
            {
            putch(Grchr[i]);
            putch(SPACE);
            }
        /* show function key assignments */
        printf(" SPACE : ERASE  PgUp : No Draw ");
        printf(" PgDn : Draw  ESC : Quit");
        Setcurs(BOTLINE + 4, 0, PAGE);
        for (i = 40; i < NUMCHARS; i++) /* second row */
            {
            putch('0' + i);
            putch(SPACE);
            }
        /* more function key assignments */
        Print_attr("F1 : Normal ", NORMAL, PAGE);
        Print_attr("F2 : Reverse Video ", VIDREV, PAGE);
        Setcurs(BOTLINE + 5, 16, PAGE);
        Print_attr("F3 : Blinking ", NORMAL | BLINK, PAGE);
        Print_attr("F4 : Intense ", NORMAL | INTENSE, PAGE);
        Home();
    }

    void Print_attr(str, attr, page)
    char *str;
    unsigned char attr, page;
    {
        while (*str != '\0')
            {
            Write_ch_atr(*str++, attr, page, 1);
            Cursrt();
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-5.  The initstuf.c module.

    Drawing the Characters: drawchar.c

    The final module, drawchar.c (Listing 14-6), contains the code that
    translates keystrokes into action. First, it maps the 48 keystrokes to the
    graphics characters. After the program initializes the Spacebar character,
    it uses a switch statement to process PgUp, PgDn, the cursor control keys,
    and the four function keys. Figure 14-5 on p. 467 shows some sample
    output from this program.

    ──────────────────────────────────────────────────────────────────────────
    /* drawchar.c -- drawing module for grafdraw.c         */
    /* translates keystrokes to graphic characters,        */
    /* manages cursor control and function keys            */

    #include <conio.h>
    #include "keys.h"
    #include "scrn.h"
    #include "grafchar.h"
    extern unsigned char Grchr[];  /* defined in grafchar.c */

    void Draw_chars()
    {
        int ch, chout;
        unsigned char attrib = NORMAL;
        unsigned char draw = TRUE;

        chout = Grchr[0];  /* default  graphics character */
        while ((ch = getch()) != ESC)
            {
            if (ch >= '0' && ch <= '_')
                chout = Grchr[ch - '0'];
                /* this maps the 0 key to the first */
                /* graphics character, etc.         */
            else if (ch == SPACE)
                chout = SPACE;
            else if (ch == 0) /* process cursor keys */
                {             /* and function keys   */
                ch = getch();
                switch (ch)
                    {
                    case PU : draw = FALSE;
                            break;
                    case PD : draw = TRUE;
                            break;
                    case UP : if (draw)
                                Write_ch_atr(chout, attrib,
                                                PAGE, 1);
                            if (!Cursup())
                                putch(BEEP);
                            break;
                    case DN : if (draw)
                                Write_ch_atr(chout, attrib,
                                                PAGE, 1);
                            if (!Cursdn_lim(BOTLINE))
                                putch(BEEP);
                            break;
                    case LT : if (draw)
                                Write_ch_atr(chout, attrib,
                                                PAGE, 1);
                            if (!Curslt())
                                putch(BEEP);
                            break;
                    case RT : if (draw)
                                Write_ch_atr(chout, attrib,
                                                PAGE, 1);
                            if (!Cursrt())
                                putch(BEEP);
                            break;
                    case F1 : attrib = NORMAL; break;
                    case F2 : attrib = VIDREV; break;
                    case F3 : attrib ^= BLINK; break;
                    case F4 : attrib ^= INTENSE; break;
                    default : putch(BEEP);
                    }
                }
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-6.  The drawchar.c module.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 14-5 can be found on p.467 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 14-5. Drawing with GRAFCHAR.C.

Details of the Program

    The easiest way to see how the program works is to try it out. Move the
    cursor around with the arrow keys and use the keyboard to select different
    graphics characters. (Remember to have Screen Swapping On active if you're
    working in the QuickC environment.) To help you see why it works the way
    it does, we'll look at some of the programming details next.

    In the Draw_chars() function, the following statement maps the keystrokes
    to the graphics characters:

    if (ch >= '0' && ch <= '_')
        chout = Grchr[ch - '0'];

    In this function, ch is the input character; chout is the output character
    used when the cursor keys are pressed. When the user presses the 0 key,
    the program uses the array index of '0' - '0', or 0, which selects the
    first graphics character in the array. Similarly, the 1 key selects the
    second graphics character, and so on.

    Next, look at how the program handles the cursor key:

    case UP : if (draw)
                    Write_ch_atr(chout, attrib, PAGE, 1);
                if (!Cursup())
                    putch(BEEP);
                break;

    If draw is set to TRUE, the program displays the current output character
    (chout) using the current attribute (attrib). In this example, the cursor
    then moves up one line unless it already is at the top line, in which case
    the program issues a beep.

    The other arrow keys are processed similarly. Note that the Down Arrow key
    uses Cursdn_lim(BOTLINE) instead of Cursdn(). Recall from Chapter 13 that
    Cursdn() is a macro that moves the cursor down as far as line 25;
    Cursdn_lim(), however, is limited by a passed argument. Because we reserve
    the bottom of the screen for the key table, the BOTLINE limit keeps the
    cursor from intruding.

    The program uses the following code for selecting an attribute:

    case F1 : attrib = NORMAL; break;
    case F2 : attrib = VIDREV; break;
    case F3 : attrib ^= BLINK; break;
    case F4 : attrib ^= INTENSE; break;

    F1 and F2 set the attribute to the normal and reverse video modes,
    respectively. F3 and F4 use the EXCLUSIVE OR operator to toggle the
    intensify and the blink modes. Why directly set two modes and toggle the
    other two? The toggled intensify and blink modes can be on simultaneously
    and compounded with the other modes. However, NORMAL must be off when
    VIDREV is on, and vice versa. If both were on at the same time, the screen
    would display a white foreground on a white background──that is, a
    featureless white square. If both were off, the display would be black on
    black, or no display.

    Limitations of the Program

    To make the GRAFCHAR.C program compatible with all monitors (except
    40-by-25 displays), we must impose some limitations. Most importantly, the
    program limits attributes to monochrome values; less importantly, it uses
    only page 0 of memory.

    If you have a color monitor, you can add color to the program by setting
    the function keys as follows:

    case F1 : attrib ^= BLUE; break;
    case F2 : attrib ^= GREEN; break;
    case F3 : attrib ^= RED; break;
    case F4 : attrib ^= BG_BLUE; break;
    case F5 : attrib ^= BG_GREEN; break;
    case F6 : attrib ^= BG_RED; break;
    case F7 : attrib ^= BLINK; break;
    case F8 : attrib ^= INTENSE; break;

    Also, make the following definitions:

    #define BLUE   0x1
    #define GREEN  0x2
    #define RED    0x4
    #define BG_BLUE   0x10
    #define BG_GREEN  0x20
    #define BG_RED    0x40

    This lets you independently toggle each bit of the attribute. When the
    attribute is initially set to NORMAL, blue, green, and red are all toggled
    on. Pressing F2, for example, turns green off and leaves the red-blue
    (magenta) combination.

    You should also change the key table at the bottom of the screen to
    reflect the new uses of the function keys.


Direct Memory Access

    Thus far, we've used BIOS routines to place the proper character and
    attribute bytes into video memory. This method offers two advantages──it
    saves us work and lets us write hardware-independent programs that run on
    the MDA, CGA, EGA, VGA, and MCGA. However, we can create faster programs
    by bypassing the BIOS and placing data directly into video memory. This
    programming technique is called "Direct Video Memory Access," which we
    will refer to as DMA for the remainder of this book. (Don't confuse this
    use of DMA with the DMA chip built into the IBM PC and compatibles, which
    performs a different function.)

DMA Basics

    To copy information to or from a memory location, you need to use the
    address of that location. Often, you do so symbolically and indirectly by
    using variables and array names. Pointers provide a more obvious way to
    use addresses. So what do you use to access video memory?

    Because video memory is a large block of bytes, it's natural to think of
    it as a large array. As you've learned, arrays can often be described by
    either array notation or pointer notation. But with video memory, you must
    use pointers. The reason is that the compiler chooses the physical
    addresses to which an array corresponds, but you can choose the physical
    address to which a pointer points. In particular, you can choose to have a
    pointer point to the beginning of video memory.

    The specific address you use depends on the hardware. The MDA uses 0xB0000
    (720,896 in decimal), and the CGA uses 0xB8000 (753,664 in decimal). The
    EGA and VGA use 0xB0000 for the monochrome mode and 0xB8000 for the color
    text modes.

    To use the address you must typecast the numeric value to the proper
    pointer type. Also, in the small and medium memory models, a data pointer
    is a 16-bit quantity. Neither of the addresses we need fits into 16 bits.
    The large memory model uses a 32-bit pointer, but not in a way that lets
    us make our simple assignment. So before we can assign the video RAM
    address to a pointer, we must first examine how the PC and QuickC handle
    memory addresses.

Segmented Memory

    The PC has the same problem with large addresses that the small and medium
    models do. The 8086 chip normally uses a 16-bit register for addresses.
    However, this permits the register to address a maximum of 64 KB of
    memory, which falls far short of the address needed to access the video
    RAM.

    The 8086 family of microprocessors uses segmented memory to overcome this
    problem. The maximum size of each segment is 64 KB, the size addressable
    by an address register. Typically, a program uses one segment for program
    code and a second segment for data.

    Addresses for the program code are 16-bit addresses relative to the
    beginning of the program segment, and data addresses are relative to the
    beginning of the data segment. These relative addresses are called
    "offsets." In C, this offset is what is stored in a 16-bit pointer. The
    following statement:

    printf("Address of x is %u\n", &x);

    prints out the offset, in bytes, of x from the beginning of a data
    segment. To keep track of where the code and data segments are, the PC
    uses special registers: the CS, or Code Segment register, and the DS, or
    Data Segment register.

    To solve the problem of identifying the location of a segment using only
    16 bits, the PC divides the actual address of the segment by 16 (0x10
    hex). For example, 0xB0000 divided by 0x10 is 0xB000. This divided
    quantity is called the "segment value." (See Figure 14-6.) Thus, a
    segment value of 0xA000 corresponds to a segment address of 0xA0000. As a
    result of this system, segments must start at addresses that are multiples
    of 16.

    Suppose you want to specify the 0x20th byte of video memory. The absolute
    address of this byte is 0xB0020. The PC represents this by setting the
    data segment register DS to the segment value of 0xB000 and setting the
    data offset register to 0x20. The following equation expresses the
    relationship more generally:

    absolute address = 0x10 x segment value + offset

                                        ┌─┌──────────────────────┐
                                        │ │                      │
                                        │ │                      │
                                        │ │                      │
                                        │ ├──────────────────────┤
                            Data segment───┤ │  01001101 01001111   │───Data at
                                (64 KB)   │ ├──────────────────────┤─┐ specifie
                                        │ │                      │ │ location
        DS register                        │ │                      │ │
    ┌───────────────┐                     │ │                      │ │
    │ Segment value │                     │ │                      │ ├─Offset
    └──────┬────────┘                     │ │                      │ │
            │                              │ │                      │ │
            ▼                              └─└─────────────────────┘─┘
    Segment value * 16 = Segment address────┘

    Figure 14-6. Data addresses are represented by a segment value and an
    offset.

    Note that you can represent the same physical address in many ways. For
    example, the absolute address 0xB0020 also can be represented with a
    segment value of 0xB001 and an offset of 0x10.

    C, Segments, and Offsets

    As you already know, C has two classes of pointers──near and far. Near
    pointers, which are 16 bits, hold only the offset. Far pointers, which are
    32 bits, use the high 16 bits to hold the segment value and the low 16
    bits to hold the offset, as shown in Figure 14-7. Compact and large
    models use far data pointers by default. Small and medium models use near
    data pointers by default. However, you can use the nonstandard C keyword
    far to create far pointers in the small and medium models. To use the far
    keyword in QuickC, choose Language Extensions in the Compile Options
    dialog box.

    Using a Far Pointer

    To access the video memory, we must declare a far pointer, initialize its
    high bytes to the segment value for the video RAM, and use its low bytes
    for the offset. Declare a far pointer by using the keyword far, as
    follows:

    unsigned short far *far_pnt; /* far pointer */

    This creates a 32-bit pointer that points to a 2-byte unit. Each 2-byte
    unit holds the ASCII code and the attribute of a single displayed
    character.

    Next, let's set the pointer to an absolute screen address of 0xB0020. This
    corresponds to a segment value of 0xB000 (0xB0000 / 0x10) and an offset of
    0x20. To place the segment value into the high bytes, left-shift it 16
    places; because the offset goes into the lower bytes, you needn't
    manipulate it at all.

    far_pnt = (unsigned short far *) (0xB000L << 16) | 0x20;

    Note that we used a typecast to convert the right side (type long) to the
    correct type. Next, we used the L suffix to make the segment value type
    long. Otherwise, 0xB000 would be treated as type int (a 16-bit type on a
    PC), and the 16-bit left-shift would shift all the bits out, leaving only
    zeros. (See Figure 14-8 on the following page.)

    Note also that the bitwise OR (|) operator combines the segment value and
    the offset. Because one resides entirely in the upper bytes and the other
    is confined to the lower bytes, this has the same effect as addition, but
    is faster.

        Segment value        Offset
    ┌────────┴────────┬────────┴────────┐
    ┌────────┬────────┬────────┬────────┐
    │                 │                 │
    └────────┴────────┴────────┴────────┘
        High bytes         Low bytes

    Figure 14-7. Filling a far pointer.

                        │ 1 byte │
                        ┌────────┬────────┐
                        │  B 8   │  0 0   │
                        └────────┴────────┘
                                ▒
                            B800 << 16
                                ▒
            ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
            ▒                    ▒
            ▼           ┌────────▼────────┐
        B 8     0 0      │  0 0   │  0 0   │
    └───────────┘     └────────┴────────┘
    Discarded Values

    ┌────────┬────────┬────────┬────────┐
    │  0 0   │  0 0   │  B 8   │  0 0   │
    └────────┴────────┴────────┴────────┘
                        ▒
                    B800L  << 16
                        ▒
    ┌────────┬────────▼────────┬────────┐
    │  B 8   │  0 0   │  0 0   │  0 0   │
    └────────┴────────┴────────┴────────┘

    Figure 14-8. The left-shift operator.

    Finally, note that the far pointer does not hold the absolute address. It
    holds two quantities: segment value and offset. If, for some reason, you
    want the absolute address, you can obtain it by using the following
    expression:

    abs_addr = 0x10 * (far_ptr >> 16) + far_ptr & 0xFFFF;

    The right-shift produces the segment value; multiplying by 0x10 gives the
    segment address; and the 0xFFFF mask screens the segment value of the
    pointer, leaving just the offset. The variable abs_addr should be type
    long or unsigned long so that it can hold the entire address.

Using Direct Memory Access──An Example

    To use DMA to access video RAM, we must declare a far pointer and
    initialize it to point to the beginning of video memory. To do so, we must
    first decide which data type to point to. Think of one page of video
    memory as 2000 character-attribute units, with each unit describing a
    particular screen location. In QuickC, two bytes constitute a short value,
    so our pointer must be a far pointer to unsigned short. Use typedef to
    make VIDMEM a synonym for that type:

    typedef unsigned short (far * VIDMEM);

    Then declare screen as a pointer of that type, as follows:

    VIDMEM screen;

    Next, you must decide which segment value to use. For the monochrome mode
    (mode 7), use 0xB000. For the CGA and CGA-compatible modes (0 through 6),
    use 0xB800. Because we must left-shift the segment value 16 bits to use it
    with a C far pointer, we represent our values as follows:

    #define MONMEM ((VIDMEM) (0xB000L << 16))
    #define CGAMEM ((VIDMEM) (0xB800L << 16))

    The L suffix makes the addresses 32-bit quantities, and the typecasting to
    type VIDMEM gives the numeric values the same type as the screen pointer.

    Recall that in the character-attribute pair, the low byte holds the
    character and the high byte holds the attribute. Therefore, if screen is
    the pointer to the beginning of video memory, if offset is the character
    position we wish to set, and if ch and attrib are character and attribute
    values, we use the following statement:

    *(screen + offset) = (attrib << 8) | ch;

    The left-shift puts attrib into the high byte, and the bitwise OR operator
    combines the resulting values, as shown in Table 14-5. Note that in
    QuickC, attrib must be at least a 16-bit type. If it is an 8-bit type, the
    significant bits are lost. (With older versions of Microsoft C, type char
    was converted to int for calculation, and no bits were discarded.)

    Let's use this information in a simple program. The CH2000.C program
    (Listing 14-7 on the following page) echoes any pressed ASCII key.
    However, instead of echoing it once, the program uses DMA to echo it 2000
    times. Also, the program cycles through all possible attribute values,
    making the color version more spectacular than the monochrome version. (Be
    sure Screen Swapping On is active. Also, use CGAMEM instead of MONMEM if
    you are using a color display.)

    The program first reads a character. The for loop displays the character
    at all 2000 positions. By using the increment operator on attrib, we
    change the attribute at each position and cycle through all 256
    possibilities.

    Table 14-5 Manipulating Character and Attribute in RAM
    Byte(s)            Bit Values                           Hex Equivalents
    ──────────────────────────────────────────────────────────────────────────
    attrib             00000000 00000111                    0x0007
    attrib << 8        00000111 00000000                    0x0700
    ch                 01000001                             0x41
    (attrib << 8) | ch 00000111 01000001                    0x0741
    ──────────────────────────────────────────────────────────────────────────

    ──────────────────────────────────────────────────────────────────────────
    /* ch2000.c -- fills screen with 2000 characters       */
    /*    This program demonstrates direct memory access   */
    /*    of video memory.  It is set up for the MDA.      */
    /*    Assign CGAMEM instead of MONMEM to screen for    */
    /*    CGA and CGA-compatible modes.                    */
    /*    Press a key to fill; press Esc to quit.          */
    /* Note: activate Screen Swapping On in Debug menu     */

    #include <conio.h>
    #include "scrn.h"
    typedef unsigned short (far * VIDMEM);
    #define MONMEM ((VIDMEM) (0xB000L << 16)) /* monochrome */
    #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
    #define ESC '\033'
    #define CHARS 2000
    #define AMASK 0xFF   /* keep attribute in range */
    main()
    {
        unsigned ch;          /* character to be displayed */
        unsigned attrib = 7;  /* initial attribute         */
        VIDMEM screen;        /* pointer to video RAM      */
        int offset;           /* location on screen        */

        screen = MONMEM;      /* monochrome initialization */
        while ((ch = getch()) != ESC)
            {
            for (offset = 0; offset < CHARS; offset++)
                *(screen + offset) = ((attrib++ & AMASK) << 8) | ch;
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-7.  The CH2000.C program.

    Notice how quickly the program fills the screen. To appreciate the speed
    of this program, rewrite it using the BIOS-based Write_ch_atr() function
    from SCRFUN.LIB and note the difference!

Making DMA More Compatible

    CH2000.C is fast and simple, but it doesn't work with all controllers. The
    program needs to be able to choose the correct memory value itself.
    Function 15 of the BIOS 0x10 video I/O interrupt enables it to do so.
    Because this routine returns the current video mode, the program can use
    that value to select the right video RAM address. Our SCRFUN.C file
    includes the Getvmode() function (Listing 14-8) based on that BIOS call.
    Note that the constant GETMODE is defined in the scrn.h file.

    Now rewrite CH2000.C as shown in the CH2001.C program (Listing 14-9). The
    constants TEXTMONO, TEXTBW80, and TEXTC80 are defined in scrn.h; they
    represent mode 7 (monochrome), mode 2 (CGA 80-by-25 B/W), and mode 3 (CGA
    80-by-25 Color), respectively.

    ──────────────────────────────────────────────────────────────────────────
    #include <dos.h>
    #include "scrn.h"
    /* Getvmode() -- obtains the current video mode */
    unsigned char Getvmode()
    {
        union REGS reg;

        reg.h.ah = GETMODE;
        int86(VIDEO, &reg, &reg);
        return reg.h.al;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-8.  The Getvmode() function.

    ──────────────────────────────────────────────────────────────────────────
    /* ch2001.c -- fills screen with 2000 characters       */
    /*    This program demonstrates direct memory access   */
    /*    of video memory.  It uses the current video mode */
    /*    value to select the proper video RAM address.    */
    /*    Press a key to fill; press Esc to quit.          */
    /* Program list: ch2001.c, scrfun.lib                  */
    /* Note: activate Screen Swapping On in Debug menu     */

    #include <conio.h>
    #include "scrn.h"
    typedef unsigned short (far * VIDMEM);
    #define MONMEM ((VIDMEM) (0xB000L << 16)) /* monochrome */
    #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
    #define ESC '\033'
    #define CHARS 2000
    #define AMASK 0xFF
    main()
    {
        unsigned ch, mode;
        unsigned attrib = 7;
        VIDMEM screen;         /* pointer to video RAM */
        int offset;

        if ((mode = Getvmode()) == TEXTMONO)
            screen = MONMEM;
        else if (mode == TEXTC80 || mode == TEXTBW80)
            screen = CGAMEM;
        else
            exit(1);
        while ((ch = getch()) != ESC)
            {
            for (offset = 0; offset < CHARS; offset++)
                *(screen + offset) = ((attrib++ & AMASK) << 8) | ch;
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-9.  The CH2001.C program.

Storing and Displaying a Screen

    Let's use DMA to add some useful capabilities to the GRAFCHAR.C program.
    Although this program lets you draw on the screen using the graphics
    character set, when you quit the program, the drawing is lost. The program
    would be more useful if it let you store the created image in a file so
    you could use it later.

    DMA is ideally suited to copying information from the screen to a file and
    back again. We can incorporate the saving code into the program and make
    the recall a separate program.

    Saving the Screen

    To save the screen, rewrite the main program as SAVEGRAF.C (Listing
    14-10). In addition, we need to add some new definitions to the
    grafchar.h file. Listing 14-11 shows the new version. Be sure Screen
    Swapping On is active (on the Debug menu) before you run the program.

    ──────────────────────────────────────────────────────────────────────────
    /* savegraf.c -- uses DMA to save screen of graphics  */
    /*               characters and attributes            */
    /* Program list - savegraf.c, initstuf.c, drawchar.c, */
    /*                savescrn.c, scrfun.lib              */
    /* User include files - scrn.h, keys.h, grafchar.h    */
    /* Note: activate Screen Swapping On in Debug menu    */

    #include "grafchar.h"
    unsigned char Grchr[NUMCHARS];  /* to store graphics set */
    void Init_stuff(void);
    void Draw_chars(void);
    void Save_screen(void);  /* in savescrn.c */

    main()
    {
        int ch;

        Init_stuff();  /* initialize vital elements */
        Draw_chars();  /* map keys to graphics characters */
        Setcurs(BOTLINE + 1, 0, PAGE);
        printf("%-80s", "Save screen? <y/n> ");
        Setcurs(BOTLINE + 1, 20, PAGE);
        ch = getche();
        if (ch == 'y' || ch == 'Y')
            Save_screen();
        Setcurs(BOTLINE + 2, 0, PAGE);
        printf("%-80s\n", "BYE!");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-10.  The SAVEGRAF.C program.

    ──────────────────────────────────────────────────────────────────────────
    /* grafchar.h -- definitions for savescrn.c and       */
    /*               recall.c   Version 2                 */
    #define NUMCHARS 48
    #define SPACE '\040'
    #define BOTLINE 19  /* line # for end of drawing space */
    #define PAGE 0
    #define GCSTART 0xB0 /* ascii for first graphics char */
    #define BEEP '\a'
    #define ESC '\033'
    #define TRUE 1
    #define FALSE 0
    #define CHARS (BOTLINE + 1) * 80  /*    number of     */
                                    /* character positions */
    typedef unsigned short (far * VIDMEM);
    #define MONMEM ((VIDMEM) (0xB000L << 16)) /* mono */
    #define CGAMEM ((VIDMEM) (0xB800L << 16)) /* CGA, EGA */
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-11.   The revised grafchar.h header file.

    The SAVEGRAF.C program uses Setcurs() to position the text outside the
    drawing area. If the user chooses to save the screen, the Save_screen()
    function (Listing 14-12) does the work.

    Program Notes

    Because line numbering starts with 0, there are BOTLINE + 1 total lines.
    Therefore, the total number of character-attribute pairs that must be
    saved is that figure times 80; CHARS is defined as that value.

    ──────────────────────────────────────────────────────────────────────────
    /* savescrn.c -- saves screen, including attribute    */
    /*               values, in a file                    */
    /*               Uses direct memory access.           */

    #include <stdio.h>  /* for file handling */
    #include "scrn.h"
    #include "grafchar.h"
    void Save_screen()
    {
        FILE *save;
        char filename[80];
        unsigned char mode;
        unsigned short char_attr;  /* character, attribute */
        int offset;
        VIDMEM screen;

        if ((mode = Getvmode()) == TEXTMONO)
            screen = MONMEM;
        else if (mode == TEXTC80 || mode == TEXTBW80)
            screen = CGAMEM;
        else
            exit(1);
        Setcurs(BOTLINE + 1, 0, PAGE);
        printf("Please enter name for save file: ");
        scanf("%s", filename);
        if ((save = fopen(filename, "wb")) == NULL)
            {
            fprintf(stderr, "Can't open %s\n", filename);
            exit(1);
            }
        for (offset = 0; offset < CHARS; offset++)
            {
            char_attr = screen[offset];
            fwrite(&char_attr, 2, 1, save);
            }
        fclose(save);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-12.  The Save_screen()< function.

    In general, screen + offset points to the character-attribute offset
    positions from the beginning of video RAM. The value of that pair is
    *(screen + offset), which can also be written as screen[offset].

    The standard I/O function fwrite() copies the contents of screen memory
    one character-attribute pair at a time. This function takes four
    arguments: a pointer to a memory location, the number of bytes per display
    unit (here 2), the number of units to be copied, and a file stream
    pointer. The program first copies the video pair to char_attr because, in
    the default medium memory model used by QuickC, fwrite() expects a near
    pointer. In the large memory model, you could replace the for loop with
    the following code:

    fwrite(screen, 2, CHARS, save);  /* large model */

    Thus, in the large memory model, fwrite() uses a far pointer and can
    access video RAM directly. By specifying CHARS units to be copied by the
    function, you can dispense with the loop.

    Recovering the Screen

    To recover the stored screen, we need to reverse the storage process. That
    is, the program should open the file in the read mode. The
    attribute-character pairs found there should then be copied into the video
    memory, a natural task for DMA. From these basic techniques we develop the
    RECALL.C program (Listing 14-13). (To run the program, be sure that
    Screen Swapping On is active.)

    ──────────────────────────────────────────────────────────────────────────
    /* recall.c -- displays previously stored screen,     */
    /*             including attributes.  Uses DMA.       */
    /* Program list: recall.c, scrfun.lib                 */
    /* User include files: scrn.h, grafchar.h             */
    /* Note: activate Screen Swapping On in Debug menu      */

    #include <stdio.h>
    #include <conio.h>
    #include "scrn.h"
    #include "grafchar.h"

    main(ac, ar)
    int ac;
    char *ar[];
    {
        unsigned char mode;
        unsigned short char_attr;
        FILE *save;
        unsigned int offset;
        char filename[81];
        VIDMEM screen;

        if (ac < 2)
            {
            fprintf(stderr, "Usage: %s filename\n", ar[0]);
            exit(1);
            }

        if ((save = fopen(ar[1], "rb")) == NULL)
            {
            fprintf(stderr, "Can't open %s\n", ar[1]);
            exit(1);
            }

        if ((mode = Getvmode()) == TEXTMONO)
            screen = MONMEM;
        else if (mode == TEXTC80 || mode == TEXTBW80)
            screen = CGAMEM;
        else
            exit(1);

        Clearscr();
        for (offset = 0; offset < CHARS; offset++)
            {
            fread(&char_attr, 2, 1, save);
            screen[offset] = char_attr;
            }
        fclose(save);
        Setcurs(23, 0, PAGE);
        getch();   /* anti-scrolling for QC environment */
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-13.  The RECALL.C program.

    In the program, fread() recovers from the files what fwrite() placed into
    them. The Setcurs() call positions the MS-DOS prompt outside the drawing
    area when the program ends. The getch() call simply requires the user to
    press a key to terminate the program. Without this code, when you run the
    program in the QuickC environment, the QuickC prompt causes the screen to
    scroll up when the program ends. It still causes scrolling with this
    program, but not until you press a key.


Paging

    Now let's turn to a text topic that lies beyond the scope of the
    Monochrome Display Adapter── paging. The CGA, EGA, and VGA have enough
    memory to store more than one screenful, or page, of text. The 16 KB video
    memory of the CGA, for example, can hold four text pages. The BIOS
    supports the use of pages by providing routines for setting the page and
    for determining the current page number. Many other BIOS routines require
    page information. The SCRFUN.C file we developed in Chapter 13 contains
    two page-related functions──Getpage() and Setpage(), which are combined in
    Listing 14-14. As usual, the manifest constants are defined in scrn.h.

    Paging is very fast, even compared to DMA, because the video RAM doesn't
    need to be rewritten. The video controller simply changes the section of
    video memory that it reads. A typical application for paging stores a help
    screen on one page, while an application uses another page. This permits a
    rapid transition between the two screens without calling data from program
    memory or a disk file.

    ──────────────────────────────────────────────────────────────────────────
    /* Getpage() -- obtains the current video page */
    unsigned char Getpage()
    {
        union REGS reg;

        reg.h.ah = GETMODE;
        int86(VIDEO, &reg, &reg);
        return reg.h.bh;
    }
    /* Setpage() -- sets page to given value */
    void Setpage(page)
    unsigned char page;
    {
        union REGS reg;

        reg.h.ah = SETPAGE;
        reg.h.al = page;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-14.  The Getpage() and Setpage() functions.

    Let's develop a basic program that can switch back and forth between page
    0 and a help screen on page 1. We could use BIOS calls to write the
    contents of the two screens, but direct memory access is faster. However,
    to use direct memory access, we need to supply the address of page 1.
    Because each page holds 2000 character-attribute pairs, or 4000 bytes, you
    might expect that page 1 is offset 4000 bytes from the beginning of video
    memory. But computers relate more to powers of 2 than to powers of 10, so
    the actual offset is 4096 bytes, 0x1000 in hex. (You can use the extra
    bytes between pages to color in page borders.)

    We use the VIDMEM type pointer again to point to video memory. Because we
    define it to point to a 2-byte unit (the character-attribute pair), the
    offset in VIDMEM units is 2048 pairs, which is 0x800 in hex.

    The HELP.C program (Listing 14-15) is fairly simple. (Be sure Screen
    Swapping On is active before you run it.) The key points to note are its
    use of Setpage() to change pages and its use of direct memory access to
    write to the screen. The program uses two direct memory access modules.
    The writechr.c module (Listing 14-16 on the following page) writes a
    character-attribute pair a specified number of times beginning at a
    specified memory location. The writestr.c module (Listing 14-17 on p.
    483) is similar, but it writes a string once instead of a single character
    repeatedly. By choosing the appropriate memory location, you can use these
    functions to write to either page, no matter which one is currently
    displayed. For convenience, we've collected definitions of the colors in a
    file called color.h (Listing 14-18 on p. 483).

    ──────────────────────────────────────────────────────────────────────────
    /*  help.c -- uses paging and direct memory access    */
    /*            to display a help screen                */
    /*  Program list: help.c, writestr.c, writechr.c,     */
    /*                scrfun.lib                          */
    /*  User include files: scrn.h, color.h               */
    /*  Note: activate Screen Swapping On in Debug menu   */

    #include <stdio.h>
    #include <conio.h>
    #include "color.h"
    #include "scrn.h"
    typedef unsigned int (far * VIDMEM);
    #define CGAMEM ((VIDMEM) (0xB800L << 16))
    #define PAGESIZE 2000
    #define PAGEOFFSET 0x800L
    #define ESC '\033'
    #define ATTR1 (BG_BLUE | YELLOW)
    #define ATTR2 (BG_YELLOW | BLUE)
    #define ATTR3 (BG_RED | YELLOW | BLINK | INTENSE)
    #define CH1 (unsigned short) '\xB1'
    char *str1 = "Press ? key for help.";
    char *str2 = "Press Enter key to return.";
    char *str3 = "Press ESC key to quit.";
    char *str4 = "\xB1HELP!\xB1";
    void Write_chars(VIDMEM, unsigned short, unsigned
                        short, unsigned short);
    void Write_str(VIDMEM, unsigned short, char *);
    main()
    {
        int ch;
        unsigned char page = 0;
        unsigned char mode;

        mode = Getvmode();
        if (mode != TEXTC80 && mode != TEXTBW80)
            {
            printf("Only modes 2 and 3 supported. Bye.\n");
            exit(1);
            }
        Setpage(page);
        Write_chars(CGAMEM, '\0', ATTR2, PAGESIZE);
        Write_str(CGAMEM + 2 * COLS, ATTR1, str1);
        Write_str(CGAMEM + 2 * COLS, ATTR1, str1);
        Write_str(CGAMEM + 22 * COLS, ATTR1, str3);
        Write_chars(CGAMEM + PAGEOFFSET, '\0', ATTR1, PAGESIZE);
        Write_str(CGAMEM + PAGEOFFSET + 20 * COLS, ATTR2, str2);
        Write_str(CGAMEM + PAGEOFFSET + 22 * COLS, ATTR1, str3);
        Write_chars(CGAMEM + PAGEOFFSET + 10 * COLS + 36,
                    CH1, ATTR3, 7);
        Write_str(CGAMEM + PAGEOFFSET + 11 * COLS + 36,
                ATTR3, str4);
        Write_chars(CGAMEM + PAGEOFFSET + 12 * COLS + 36,
                    CH1, ATTR3, 7);

        while ((ch = getch()) != ESC)
            {
            if (ch == '?' && page == 0)
                Setpage(page = 1);
            else if (ch == '\r' && page == 1)
                Setpage(page = 0);
            }
        Write_chars(CGAMEM, '\0', NORMAL, PAGESIZE);
        Write_chars(CGAMEM + PAGEOFFSET, '\0', NORMAL, PAGESIZE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-15.  The HELP.C program.

    ──────────────────────────────────────────────────────────────────────────
    /* writechr.c -- writes char and attribute repeatedly */
    /*               using DMA                            */
    /* write character ch with attribute attr num times   */
    /* starting at location pstart -- uses array notation */

    typedef unsigned int (far * VIDMEM);

    void Write_chars(pstart, ch, attr, num)
    VIDMEM pstart;
    unsigned short ch, attr, num;
    {
        register count;
        unsigned short pair;
        pair = (attr << 8) | (ch & 0x00FF) ;
        for (count = 0; count < num; count++)
            pstart[count] = pair;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-16.  The writechr.c module.

    ──────────────────────────────────────────────────────────────────────────
    /*  writestr.c -- writes string and attribute using DMA */
    /*  Write the string str with attribute attr at         */
    /*  location pstart -- uses pointer notation.           */

    typedef unsigned int (far * VIDMEM);

    void Write_str(pstart, attr, str)
    VIDMEM pstart;
    unsigned short attr;
    char *str;
    {
        while (*str != '\0')
            *pstart++ = (attr << 8) | (*str++ & 0x00FF);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-17.  The writestr.c module.

    ──────────────────────────────────────────────────────────────────────────
    /* color.h -- defines the color attributes  */
        /* foreground colors */
    #define  BLACK   0x0
    #define  BLUE    0x1
    #define  GREEN   0x2
    #define  RED     0x4
    #define  CYAN    0x3
    #define  MAGENTA 0x5
    #define  YELLOW  0x6
    #define  WHITE   0x7
        /* background colors */
    #define  BG_BLACK   0x00
    #define  BG_BLUE    0x10
    #define  BG_GREEN   0x20
    #define  BG_RED     0x40
    #define  BG_CYAN    0x30
    #define  BG_MAGENTA 0x50
    #define  BG_YELLOW  0x60
    #define  BG_WHITE   0x70
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-18.  The color.h include file.

    Most of the HELP.C program involves using the new Write_chars() and
    Write_str() functions, so let's examine them. The two functions are quite
    similar in behavior, but to illustrate different programming techniques,
    one uses array notation and the other uses pointer notation.

    Write_chars() starts by combining the attribute and character into a
    2-byte unit. It left-shifts the attribute into the high byte and places
    the character in the low byte. Next, the function performs a logical AND
    operation with the character and 0x00FF to limit the character to the
    range 0 through 0xFF, or 0 through 255. Next, a loop assigns the 2-byte
    pair to num consecutive locations in memory, beginning with the location
    pointed to by pstart. Recall that the notation pstart[count] is equivalent
    to *(pstart + count).

    In the program, Write_chars() clears the two pages, setting them to yellow
    and blue, respectively. To clear the screen, the program sets the
    character part of the byte to a null character; the attribute sets the
    color.

    The Write_str() function uses pointer notation to display a string. Like
    the preceding function, it combines the left-shifted attribute with a
    masked character value. In this case, str initially points to the first
    character in the string, so *str represents the value of that character.
    The while loop continues until it reaches the terminating null character
    of the string. During each cycle, the increment operator advances the
    video memory pointer and the string pointer after they are used.

    In the main program, note how we use addresses to specify locations on the
    screen. Consider, for example, the following statement:

    Write_str(CGAMEM + PAGEOFFSET + 11 * COLS + 36,
                ATTR3, str4);

    The address CGAMEM locates the beginning of the CGA (and EGA and VGA)
    memory. The PAGEOFFSET value is the offset to the beginning of the next
    page. Each line contains COLS characters, so the expression 11 * COLS is
    the offset to the beginning of line 11 (the twelfth line, because
    numbering starts with zero). Finally, the 36 gives the offset, or the
    indention measured in character widths, from the left side of the display.

    Note that the QuickC Graphics Library provides alternatives to many of our
    BIOS-based functions, including functions that clear the screen and set
    the page. However, using the Graphics Library produces final code
    noticeably larger than that of our examples. We use the Graphics Library
    only in graphics programs, in which its power and generality become
    evident.


Ports

    Any discussion of hardware-dependent programming methods must mention
    "ports," which are information conduits between the CPU and the other
    devices and processors in a PC. In general, each processor or device has
    one or more registers of its own. Values placed in these registers can
    control the operation of the processor or, perhaps, test its state of
    readiness. In the PC, various registers are assigned "port addresses" that
    are completely separate from the memory address system and are handled
    differently. The CPU accesses registers through ports by using special
    port instructions. (See Figure 14-9.)

    An 8086 CPU can address as many as 64,000 8-bit ports, but only a small
    fraction of that number (fewer than 200) are actually used. In assembly
    language, you access the ports with the instructions IN and OUT: IN reads
    a register; OUT writes to it. C does not contain these instructions, so
    QuickC supplies the non-ANSI inp() and outp() functions to serve the same
    purpose.

Reading Ports with inp()

    As mentioned in Chapter 13, the inp() and outp() functions are defined in
    conio.h. The following is the syntax for inp():

    #include <conio.h>

    int inp(port)
    unsigned port;    /* port number */

    This function reads the register at port number port, which can be a value
    in the range 0 through 65,535. It then returns the byte it reads. With
    write-only ports, inp() returns the value 255, or all bits set to 1.
    However, a return value of 255 does not always signal a write-only
    register because 255 is also a valid register setting.

                        ┌───────────────────┐
                        │ Video controller  │
                        │    ┌─────────┐    │
                        │    │register │    │
                        └────┴────╥────┴────┘
    ┌───────────┐                 ║
    │         ┌─┤                 ║
    │         │ │◄──Port 0x3B8    ║
    │         │ ╞═════════════════╝
    │   CPU   └─┤
    │         ┌─┤
    │         │ ╞═════════════════╗
    │         │ │◄──Port 0x67     ║
    │         └─┤                 ║
    └───────────┘                 ║
                        ┌────┬────╨────┬────┐
                        │    │register │    │
                        │    └─────────┘    │
                        │ Speaker controller│
                        └───────────────────┘

    Figure 14-9. Ports and registers.

    The short PORTINFO.C program (Listing 14-19) lets you access and read
    various ports. Note that it uses the return value of scanf() to terminate
    the input loop. We prompt for hexadecimal port numbers because technical
    manuals usually list them in that form. Note that scanf() returns a value
    equal to the number of successful reads. Therefore, if it reads a hex
    value, it returns 1. If it finds input that is not hex, such as the letter
    q, scanf() returns 0, and the loop terminates.

    The following is a sample run:

    Enter number (in hex) of the port you wish to read: 3da
    Value returned for port 3da is 199 (decimal)  c6 (hex)
    Next port? (q to quit): 61
    Value returned for port 61 is 32 (decimal)  31 (hex)
    Next port? (q to quit): 42
    Value returned for port 42 is 174 (decimal)  60 (hex)
    Next port? (q to quit): 3b8
    Value returned for port 3b8 is 255 (decimal)  ff (hex)
    Next port? (q to quit): q

    You may get different values from those in this sample run──some of the
    registers change values as you use the computer.

    In the IBM PC and compatibles, the 0x3DA port reports status information
    about the MDA. Port 61 controls the speaker, and port 42 regulates the
    frequency of the 8253 timer chip. Finally, port 3B8 is the control port
    for the 6845 video controller on the MDA. (Because the last port is a
    write-only port, the reported value is not necessarily the true one.)

    ──────────────────────────────────────────────────────────────────────────
    /* portinfo.c -- reads port values                    */
    /* program list -- portinfo.c (inp() not in core lib) */

    #include <conio.h>
    #include <stdio.h>
    main()
    {
        unsigned int portnum;
        int regvalue;

        printf("Enter number (in hex) of the port ");
        printf("you wish to read: ");
        while (scanf("%x", &portnum) == 1)
            {
        regvalue = inp(portnum);
        printf("\nValue returned for port %x is %d (decimal)"
                "  %x (hex)\n", portnum, regvalue, regvalue);
        printf("Next port? (q to quit): ");
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-19.  The PORTINFO.C program.

    As you can see, reading a register is a simple procedure. The difficult
    part is wading through the technical literature to see which port
    addresses correspond to which devices and to find out the meaning of the
    register settings.

Writing to a Port with outp()

    You write to a port with the outp() function by using the following
    syntax:

    #include <conio.h>

    int outp(port, value)
    unsigned port;             /* port number  */
    int value;                 /* output value */

    The function sends value to port number port. Although value is declared
    type int, you should use only numbers in the range 0 through 255. The
    function returns the same value it sends.

    Although it is easy to write to a port, you must do so with caution.
    Sending wrong values to some video controller registers, for example, can
    damage your monitor. Other ports can disable your keyboard, the system
    memory, the monitor, and so on. Do not use the experimental method when
    you write to ports! Before we write a sample program that uses port number
    0x3B8, the MDA control register, study the function of each bit in the
    register as described in Table 14-6.

    Let's write a short program that blanks the screen and then restores it.
    We can turn off the display by setting bit 3 to 0. Because this does not
    affect the video RAM, resetting bit 3 to 1 restores the display.

    Ideally, we would use inp() to read and save the current register setting.
    Then we could use that value to restore the original setting when we are
    done. However, 3B8 is a write-only register, so we must use Table 14-6 to
    select the proper setting.

    Table 14-6 Video Control Register Functions
    Bit         Function
    ──────────────────────────────────────────────────────────────────────────
    0           If this bit is 0, no communication is permitted between the
                CPU and the video display memory. (This prevents data from
                being changed.) If this bit is 1 (the default value),
                communication between the CPU and the video display memory is
                enabled. (This lets the CPU read from and write to memory.)
                1, 2
                Not used.
    3           If this bit is 0, the display is disabled, which blanks the
                screen. The contents of video RAM, however, are unaffected. If
                this bit is 1 (the default value), the display is enabled, and
                data stored in video RAM is displayed.
    4           Not used.
    5           If this bit is 0, the blink attribute bit in video RAM
                controls the background intensity. If this bit is 1 (the
                default value), it controls blinking.
    6, 7        Not used.
    ──────────────────────────────────────────────────────────────────────────

    Clearly, we originally want bit 0 to be 1. Normally, bits 3 and 5 should
    be 1, too. Because the other bits don't affect the port, we can set them
    to 0. This makes our default setting 00101001 in binary, or 0x29 in hex.
    To turn the display off, set bit 3 to 0, which changes the setting to 0010
    0001 in binary, or 0x21 in hex.

    The short BLANK.C program (Listing 14-20) demonstrates the results of our
    efforts.

    As we mentioned, the port approach is hardware-dependent. For example,
    changing the register number from 3B8 to 3D8 makes this program work with
    the CGA, but not with the EGA. By accessing the ports directly, you can
    make the video controllers do things that are impossible with BIOS calls
    alone. However, a new display adapter (and different port assignments) can
    render your program nonfunctional. Our BLANK.C program illustrates both
    these points.

    But sometimes you must use ports. For example, there are no BIOS calls
    that control the speaker; therefore, if you want to play a little tune,
    programming the port is the only method available.

Eliminating CGA Snow

    Let's examine another port example. The CGA can display "snow" when used
    with direct memory access. The problem arises from the interference caused
    when the CPU sends data to the video RAM at the same time that the CGA
    controller reads the RAM. (The other controllers don't have this problem.)
    The problem can be solved by not writing to the video RAM when the video
    controller is in the horizontal retrace mode. (The video display works by
    scanning an electron beam in horizontal lines across the screen. When the
    beam reaches the edge of the screen, it must be reset to the beginning of
    the next line. That resetting of the beam is called the horizontal
    retrace.)

    ──────────────────────────────────────────────────────────────────────────
    /* blank.c -- blanks MDA screen                        */
    /* program list -- blank.c (outp() not in core lib)    */

    #include <conio.h>
    #define CONTROLREG 0x3B8 /* control register MDA */
    #define DEFAULTSET 0x29
    #define VIDEOOFF 0x21
    main()
    {
        outp(CONTROLREG, VIDEOOFF);
        getch();
        outp(CONTROLREG, DEFAULTSET);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-20.  The BLANK.C program.

    The CGA read-only 0x3DA port contains the controller status and reports
    when the video RAM can be written to without interfering with the display.
    When the video RAM can be accessed without interference, bit 0 is set to
    1. The code uses that information to limit access to the proper times:

    while ((inp (0x3DA) & 01) != 0)
        {;}  /* wait to end of current retrace */
    while ((inp (0x3DA) & 01) == 0)
        {;} /* wait for next retrace */
    /* put memory access code here */

    The second loop is obvious──it waits until bit 0 is 1 before accessing the
    video memory. The first loop prevents memory access from starting part way
    through a horizontal retrace. Suppose, for example, a retrace is 90%
    complete. Without the first loop, the program skips the second loop
    because bit 0 currently would be 1. However, that leaves only 10% of the
    retrace time to perform all the memory access, and that might not be
    enough time.


The EGA and VGA

    The normal text modes for the EGA and VGA systems are modes 3 and 4, which
    emulate the CGA 80-by-25 B/W and Color text modes. (Both systems also
    support mode 7 so that they can be used with a monochrome monitor.) All
    the applications we've discussed so far, aside from the port example, work
    with the EGA and VGA. These video controllers, however, have additional
    text capabilities that you might want to exploit.

    Normally, when used with a high-resolution monitor, the EGA and VGA use
    more pixels per character than the CGA to achieve better-looking text.
    However, these controllers also can produce smaller characters by using
    the CGA 8-by-8 character grid instead of the normal 8-by-14 (EGA) or
    9-by-16 (VGA) grid. This lets us generate a 43-line screen with the EGA
    and a 50-line screen with the VGA. For simplicity, we'll use the term
    "extended-line" to describe either.

    The EGA and VGA handle fonts differently than the MDA and CGA. The EGA and
    VGA store some standard fonts in ROM, much like the CGA and MDA. However,
    rather than scanning the ROM directly to get font information, the EGA and
    VGA first copy the fonts to a video RAM area beginning at memory location
    0xA0000. Then they scan the RAM for font information. Thus, you can use
    BIOS calls to select a font, or you can even load a font of your own
    design. To access the extended-line mode, you must load the 8-by-8 font
    instead of the default (8-by-14 or 9-by-16) font.

    To produce the extended-line display, you must reset several video
    controller registers. (New BIOS routines that come with these controllers
    simplify the process.) The LINES43.C program (Listing 14-21 on the
    following page) sets up the extended-line mode. If ANSI.SYS is running,
    this program will not work properly──it displays the small characters, but
    it limits the display to 25 lines.

    The program first sets the usual text mode. Next, it calls a new routine
    added to the EGA and VGA versions of interrupt 0x10. Routine 0x11, labeled
    CHAR_GEN in our program, specifies the character font to be used. Setting
    register AL to 0x12 selects the 8-by-8-pixel character set stored in the
    video ROM and resets the register settings to display 43 (or 50) lines.

    Why did the program set a block to 0? The EGA and VGA can simultaneously
    store as many as four fonts. Block 0 refers to the first font, block 1 to
    the second, and so on. Because block 0 is used unless you explicitly
    switch to another, we copied the font to that block.

    For this program, use the Run menu to select the .exe compilation. You
    must leave QuickC to run the program because it has no effect in the
    QuickC environment. Use the MS-DOS MODE CO80 command to restore the usual
    mode.

    What happens if you set the extended-line mode and then run another
    program? Some programs reset the mode and undo the change. Some display
    the small characters but assume that only 25 lines can be displayed. Some,
    like QuickC, check to see the number of lines in use and display the full
    43 (or 50) lines.

    ──────────────────────────────────────────────────────────────────────────
    /* lines43.c -- leaves EGA in 43-line mode */

    #include <dos.h>
    #include <conio.h>
    #define VIDEO 0x10
    #define SETVMODE 0
    #define CHAR_GEN 0x11   /* an EGA BIOS function number */
    #define ROM8X8   0x12
    #define BLOCK 0
    #define TEXTC80 3

    main()
    {
        union REGS reg;

        reg.h.ah = SETVMODE;    /* set text mode */
        reg.h.al = TEXTC80;
        int86(VIDEO, &reg, &reg);

        reg.h.ah = CHAR_GEN;   /* char generator routine */
        reg.h.al = ROM8X8;  /* use 8x8 ROM character box */
        reg.h.bl = BLOCK;   /* copy to block 0 */
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 14-21.  The LINES43.C program.



────────────────────────────────────────────────────────────────────────────
Chapter 15  Graphics and QuickC

    Generating computer graphics is one of the PC's most spectacular uses. All
    the video controllers listed in the preceding chapters──except the MDA──
    offer graphics modes that permit pixel-by-pixel control of the entire
    screen, enabling you to create figures and patterns and to set colors for
    individual pixels. The quality of graphics (and graphics programming in
    general) is hardware-dependent. The CGA, EGA, and VGA offer various
    graphics modes that are not compatible with one another. Fortunately,
    QuickC's extensive Graphics Library substantially simplifies graphics
    programming. We devote most of this chapter to exploring this library.


The Graphics Modes

    First, let's review the available graphics modes. All of these modes are
    dependent on specific video controllers and displays. Table 15-1 on the
    following page shows which modes are available to various hardware
    systems.

    Table 15-1 Graphics Modes
    Mode      Adapters     Displays           Resolution   Colors per   Palette
                                                            Palette
    ───────────────────────────────────────────────────────────────────────────
    4         CGA, EGA,    B/W☼ , CD☼ , ED☼ , 320 x 200    4            2
            VGA          VD☼
    5       CGA, EGA, CD☼ , ED☼ ,    320 x 200 4          1           4 gray
            VGA       VD☼
    6       CGA, EGA, B/W☼ , CD☼ ,   640 x 200 2          1           2 B/W☼
            VGA       ED☼ , VD☼
    13      EGA, VGA  CD☼ , ED☼ ,    320 x 200 16         User-       16
                    VD☼                                 definable
    14      EGA, VGA  ED☼ , VD☼      640 x 200 16         User-       16
                                                        definable
    15      EGA, VGA  MD☼            650 x 350 2          1           2 B/W☼
    16      EGA, VGA  ED☼ , VD☼      640 x 350 4/16       User-       16/64
                                                        definable
    17      VGA       VD☼            640 x 480 2          User-       262,144
                                                        definable
    18      VGA       VD☼            640 x 480 16         User-       262,144
                                                        definable
    19      VGA       VD☼            320 x 200 256        User-       262,144
                                                        definable
    ──────────────────────────────────────────────────────────────────────────

    Notes: For the EGA and VGA, mode 5 is the same as mode 4.
            For mode 16, the number of colors available to the EGA depends on
            the size of EGA memory. The lower figure is for 64 KB of memory;
            the higher figure is for 128 KB or more of EGA memory.
            To use any of these graphics modes from within a program, specify
            the mode with a BIOS call or with one of the Graphics Library
            functions.

Modes and BIOS

    In Chapter 14 we used the BIOS-based Getvmode() function from our
    SCRFUN.LIB library to obtain the current video mode. That library also
    provides the Setvmode() function shown in Listing 15-1.

    ──────────────────────────────────────────────────────────────────────────
    /* Setvmode() -- sets video mode to given mode */
    #include <dos.h>
    #include "scrn.h"
    void Setvmode(mode)
    unsigned char mode;
    {
        union REGS reg;

        reg.h.ah = SETMODE;
        reg.h.al = mode;
        int86(VIDEO, &reg, &reg);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-1.  The Setvmode() function.

    You use this function to set mode 4, for example, with the following
    program lines:

    if (Getvmode() != 7)  /* not the monochrome */
        Setvmode(4);
    else
        {
        printf("Monochrome monitor does not use mode 4.\n");
        exit(1);
        }

    The Graphics Library routines, however, simplify and enhance this
    procedure.

Modes and the Graphics Library

    The QuickC Graphics Library contains functions specifically designed to
    handle the display interface and resides in the library file GRAPHICS.LIB
    and in the QuickC library file GRAPHICS.QLB. The graphics routines are not
    part of the standard QuickC core library. However, they are easy to
    access. First, you can use a program list containing the name of your
    program's file. This causes QuickC to use its linker, which accesses the
    whole library, including GRAPHICS.LIB. Or you can place the graphics
    routines in QuickC's in-memory library by using the /l option:

    qc /l graphics.qlb

    The second method produces faster compilation, and we assume that you use
    it. However, if your computer doesn't have enough memory to follow that
    method, you can use a program list instead.

    To use the graphics functions, you must include the header file graph.h.
    The following sections describe some of its mode-related functions.

    The _setvideomode() Function

    This more sophisticated version of our Setvmode() function takes as an
    argument the number of the desired mode. The graph.h file (Listing 15-2
    on the following page) contains the list of manifest constants that you
    can use.

    The _setvideomode() function has a few important features that Setvmode()
    lacks. For one thing, it keeps track of the original video mode, which
    means that you can use the _DEFAULTMODE argument to restore that mode.
    Another feature of _setvideomode() is that it has a return value. If the
    function succeeds in setting the requested mode, it returns a nonzero
    value. If the function fails, it returns a zero. Using the return value,
    we can rewrite our DMA (Direct Video Memory Access) examples from Chapter
    14 so that they don't need to first obtain the current mode. The relevant
    code in those examples follows:

    if ((mode = Getvmode()) == TEXTMONO)
        screen = MONMEM;
    else if (mode == TEXTC80 || mode == TEXTBW80)
        screen = CGAMEM;
    else
        exit(1);

    ──────────────────────────────────────────────────────────────────────────
    /*   Mode constants from graph.h  */
    /*   arguments to _setvideomode() */
    #define _DEFAULTMODE  -1
                        /* restore screen to original mode */
    #define _TEXTBW40      0  /* 40 x 25 text, 16-gray */
    #define _TEXTC40       1  /* 40 x 25 text, 16/8-color */
    #define _TEXTBW80      2  /* 80 x 25 text, 16-gray */
    #define _TEXTC80       3  /* 80 x 25 text, 16/8-color */
    #define _MRES4COLOR    4  /* 320 x 200, 4-color */
    #define _MRESNOCOLOR   5  /* 320 x 200, 4-gray */
    #define _HRESBW        6  /* 640 x 200, B/W */
    #define _TEXTMONO      7  /* 80 x 25 text, B/W */
    #define _MRES16COLOR  13  /* 320 x 200, 16-color */
    #define _HRES16COLOR  14  /* 640 x 200, 16-color */
    #define _ERESNOCOLOR  15  /* 640 x 350, B/W */
    #define _ERESCOLOR    16  /* 640 x 350, 4- or 16-color */
    #define _VRES2COLOR   17  /* 640 x 480, 2-color */
    #define _VRES16COLOR  18  /* 640 x 480, 16-color */
    #define _MRES256COLOR 19  /* 320 x 200, 256-color */
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-2.  Mode constants from graph.h.

    After we add the #include <graph.h> line to the program, we can replace
    the preceding code with the following:

    if (_setvideomode(_TEXTMONO))
        screen = MONMEM;
    else if (_setvideomode(_TEXTC80) || _setvideomode(_TEXTBW80))
        screen = CGAMEM;
    else
        exit(1);

    This program attempts to set the MDA mode. If it succeeds, it sets the
    video display pointer to the MDA value. If it fails to set the MDA mode,
    it attempts to set either the CGA color 80-by-25 mode or the B/W
    equivalent. If either of those attempts succeeds, it sets the video
    display pointer to the CGA value. If none of these attempts succeed, the
    program exits. Note the way the second if works. If _setvideomode()
    succeeds in setting the _TEXTC80 mode, the function returns a true value.
    Because the first part of the logical OR expression is true, the whole
    expression is true, and thus the second half of the expression need not be
    evaluated.

    The _getvideoconfig() Function

    The Graphics Library also lets us retrieve a variety of information about
    the current mode. The _getvideoconfig() function fills a structure called
    videoconfig with mode-related information. The function is defined in the
    graph.h file, as shown in Listing 15-3. The listing also shows the
    defined constants that you can use with the videoconfig structure.

    ──────────────────────────────────────────────────────────────────────────
    /* video configuration information from graph.h  */
    struct videoconfig {
        short numxpixels;    /* number of pixels on X axis */
        short numypixels;    /* number of pixels on Y axis */
        short numtextcols;   /* number of text columns available */
        short numtextrows;   /* number of text rows available */
        short numcolors;     /* number of actual colors */
        short bitsperpixel;  /* number of bits per pixel */
        short numvideopages; /* number of available video pages */
        short mode;          /* current video mode */
        short adapter;       /* active display adapter */
        short monitor;       /* active display monitor */
        short memory;        /* adapter video memory in K bytes */
    };

    /* videoconfig adapter values */
    /* These manifest constants can be used to test adapter     */
    /* values for a particular adapter using the bitwise AND    */
    /* operator (&). */
    #define _MDPA   0x0001 /* Monochrome Display Adapter (MDPA) */
    #define _CGA    0x0002 /* Color Graphics Adapter     (CGA)  */
    #define _EGA    0x0004 /* Enhanced Graphics Adapter  (EGA)  */
    #define _MCGA   0x0008 /* Multi-Color Graphics Array (MCGA) */
    #define _VGA     0x0010 /* Video Graphics Array      (VGA)  */

    /* videoconfig monitor values */
    /* These manifest constants can be used to test monitor     */
    /* values for a particular monitor using the bitwise AND    */
    /* operator (&). */
    #define _MONO     0x0001  /* Monochrome                     */
    #define _COLOR    0x0002  /* Color (or Enhanced emulating   */
                            /* color)                         */
    #define _ENHCOLOR 0x0004  /* Enhanced Color                 */
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-3.  Video configuration information from graph.h.

    When you pass the _setvideomode() function the address of a struct
    videoconfig structure, the function fills the structure with the indicated
    data. The MODEINFO.C program (Listing 15-4 on the following pages) cycles
    through the modes supported by QuickC and displays the mode-related
    information. When the program ends, the _setvideomode(_DEFAULTMODE)
    function call restores the original mode setting.

    Notice in the output of MODEINFO.C that the _getvideoconfig() function
    returns 32 for the number of colors available in all text modes, including
    monochrome. This value indicates the range of values accepted by the
    _settextcolor() function, not necessarily the number of unique color
    options.

    Because the actual mode values do not form a set of consecutive integers,
    the program holds the values in an array. However, the array indexes are
    consecutive, so they can be used in a loop.

    ──────────────────────────────────────────────────────────────────────────
    /* modeinfo.c -- sets modes and obtains information    */
    /* Demonstrates _setvideomode() and _getvideoconfig()  */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <conio.h>
    #include <graph.h>
    struct videoconfig vc;
    int modes[15] ={_TEXTBW40, _TEXTC40, _TEXTBW80, _TEXTC80,
            _MRES4COLOR, _MRESNOCOLOR, _HRESBW, _TEXTMONO,
            _MRES16COLOR, _HRES16COLOR, _ERESNOCOLOR, _ERESCOLOR,
            _VRES2COLOR, _VRES16COLOR, _MRES256COLOR};
    char *Adapt(short), *Display(short);
    main()
    {
        int i;

        for (i = 0; i < 15; i++)
            {
            if (_setvideomode(modes[i]))
                {
                _getvideoconfig(&vc);
                printf("video mode is %d\n", vc.mode);
                printf("number of columns is %d\n", vc.numtextcols);
                printf("number of colors is %d\n", vc.numcolors);
                printf("number of pages is %d\n", vc.numvideopages);
                printf("adapter is %s\n", Adapt(vc.adapter));
                printf("display is %s\n", Display(vc.monitor));
                printf("the adapter has %dK of memory\n",
                        vc.memory);
                }
            else
                printf("mode %d not supported\n", modes[i]);
            printf("press a key for next mode\n");
            getch();
            }
        _setvideomode(_DEFAULTMODE);
    }

    /* Adapt() returns a pointer to a string describing   */
    /* the adapter characterized by adapt_num.            */
    char *Adapt(adapt_num)
    short adapt_num; /* videoconfig.adapter value         */
    {
        static char *anames[6] = {"Monochrome", "CGA", "EGA",
                                "MCGA", "VGA", "Not known"};
        char *point;

        switch (adapt_num)
            {
            case _MDPA : point = anames[0];
                        break;
            case _CGA  : point = anames[1];
                        break;
            case _EGA  : point = anames[2];
                        break;
            case _MCGA : point = anames[3];
                        break;
            case _VGA  : point = anames[4];
                        break;
            default    : point = anames[5];
            }
        return point;
    }

    /* Display() returns a pointer to a string describing  */
    /* the monitor characterized by disp.                  */
    char *Display(disp)
    short disp;  /* videoconfig.monitor value              */
    {
        static char *types[5] = {"monochrome", "color",
                                "enhanced color", "analog",
                                "unknown"};
        char *point;

        if (disp & _MONO)
            point = types[0];
        else if (disp & _COLOR)
            point = types[1];
        else if (disp & _ENHCOLOR)
            point = types[2];
        else if (disp & _ANALOG)
            point = types[3];
        else
            point = types[4];
        return point;
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-4.  The MODEINFO.C program.

    The Adapt() function uses a switch statement to select the string that
    corresponds to the adapter value returned by _getvideoconfig(). The
    Display() function uses the manifest constants defined in graph.h and
    logical AND testing to identify the monitor. The returned values, being
    powers of 2, are such that mode & _MONO is zero (false) unless mode is
    _MONO and so on.


CGA Graphics

    We now have the tools to select a mode. The first mode we will explore is
    mode 4 (_MRES4COLOR) because all the graphics video controllers support
    it. This is the medium-resolution CGA mode.

    Because the graphics modes allow each screen pixel to be set individually,
    they require more video memory than do the text modes. The exact amount of
    memory, however, depends on how much information is needed to describe
    each pixel. For example, a black-and-white mode requires only one bit per
    pixel because the two possible values of the bit (0 and 1) are enough to
    describe the two possible values for the pixel (off and on). With color
    modes, the amount of memory needed depends on the number of colors
    available to each pixel. With a fixed amount of memory, using more bits
    per pixel increases the color options but decreases the total number of
    pixels that can be mapped. The CGA 320-by-200 four-color mode
    (_MRES4COLOR) offers a compromise between resolution and color variety by
    restricting the display to four colors at a time.

The Graphics Palette and Background

    The CGA four-color mode represents each pixel with two bits of memory.
    This unit of memory can be set to a number in the range 0 through 3, thus
    providing a choice of four colors. Color 0 represents the background
    color, and the other three colors constitute the "palette." The background
    color and the palette are set in separate operations.

    You can set the background color to any one of 16 values, numbers 0
    through 15. The values 0 through 7 are the text foreground choices we
    previously listed in the color.h file (Listing 14-18 on p. 483). The
    values 8 through 15 are the intensified versions of these same colors.
    Note that the graphics background choice applies to the entire screen; the
    text background applies only to a particular character box. Thus, in the
    graphics mode, only one background choice can be in effect at a time.

    In CGA four-color mode, you can choose one of only two palettes, which are
    described in Table 15-2. Suppose you set the palette to 0 and the
    background to blue. When you set a bit pair in the video memory to 0, the
    corresponding pixel is set to blue. Setting the bit pair to 1, 2, or 3
    produces the colors green, red, and dark yellow, respectively. Setting the
    video mode clears the display because it sets all the video display bits
    to 0. Thus, initially, the entire screen is the background color.

    Now suppose you create a pattern on the screen. If you select a different
    background color, the background for the entire screen changes, but the
    video display memory remains unchanged. Changing the background color
    essentially tells the controller how to interpret a zero value in the
    video memory. Similarly, changing the palette actually tells the
    controller how to interpret values of 1, 2, or 3 in the video memory.

    Table 15-2 Mode 4 Palette Choices
    Palette            Color 1           Color 2            Color 3
    ──────────────────────────────────────────────────────────────────────────
    0                  Green             Red                Dark yellow
    1                  Cyan              Magenta            Light gray
    ──────────────────────────────────────────────────────────────────────────

The QuickC Graphics Library

    Creating a graphics image requires several steps. First, set a graphics
    mode such as _MRES4COLOR. Then select a background color and a palette.
    Finally, set the appropriate bits in the video memory to the required
    values. To perform these tasks, you can use the BIOS video I/O routines or
    the QuickC Graphics Library functions, or you can directly access the
    video display memory and the controller registers. We will use the
    Graphics Library routines, but first let's briefly outline the other
    approaches.

    The BIOS provides modest support for the graphics mode. It includes
    interrupt routines for selecting a background color and a palette. Other
    routines read a pixel from the screen, write a pixel to the screen, and
    generate text. After that, you are on your own. To draw a line, you first
    must figure out which pixels to turn on; then you must turn them on
    individually. The BIOS routines are also quite slow. The write-pixel
    routine, for example, takes a long time to fill a square.

    On the other hand, the direct memory access approach is extremely fast.
    But the programming is difficult. First, because each byte of memory
    represents four separate pixels, you must use bitwise manipulations to
    alter only one of those pixels. Second, the CGA stores the bit pairs for
    the odd-numbered rows in a different section of memory than the
    even-numbered rows. To fill a solid figure on the screen, you must jump
    back and forth in the video display memory.

    The Graphics Library overcomes the difficulties of the other two
    programming methods. Its drawing routines are much faster than the BIOS
    routines because they use direct memory access. The library functions are
    conveniently oriented toward end results, not internal representations.
    For example, the library provides functions to draw boxes and circles, and
    the functions describe the screen in terms of screen position, not in
    terms of memory location. Also, the library simplifies creating programs
    that work in more than one graphics mode. The EGA and VGA graphics modes,
    for example, use a different memory location (0xA0000) and different
    schemes for representing pixels and colors; the library makes those
    differences invisible to the user. The main drawback in using the Graphics
    Library is the size it adds to a program. However, in our opinion, the
    speed, convenience, power, and monitor-compatibility of the library
    approach easily outweigh that factor for writing graphics programs.

Choosing in Modes

    Let's explore the rudiments of graphics programming by creating a program
    that turns on a few pixels. Although we use the CGA medium-resolution mode
    4, you might want to try the program in another mode. To make that easy to
    do, we use the following code (on the next page) with most of our
    examples.

    #include <stdlib.h>
    main (argc, argv)
    int argc;
    char *argv[];
    {
        int mode = _MRES4COLOR;
        int ch;

        if (argc > 1)
            mode = atoi(argv[1]);

        if (_setvideomode(mode) == 0)
            {
            printf("Can't do mode %d.\n", mode);
            exit(1);
            }

    This code fragment sets the mode to _MRES4COLOR by default. However, if
    you use a command-line argument (argc > 1), mode is set to that argument.
    The function atoi(), which is declared in STDLIB.C, converts the argument
    from a string to a numeric value. To select a mode by this method, display
    the Runtime Options dialog box before running the program and then enter
    the desired mode number in the command-line window. For example, entering
    16 causes the program to set mode 16 (_ERESCOLOR).

    One convenient feature of the Graphics Library is that the same function
    calls work for all modes. Although different modes might require different
    argument values because the screen has more or fewer pixels, you can use
    _getvideoconfig() information to scale argument values accordingly. That
    is the approach we use.

    Setting the mode also clears the screen, so you don't need to clear it
    explicitly. (When you do need to clear the screen, the Graphics Library
    provides a _clearscreen() function.)

Color Basics

    Use the _selectpalette() function to choose a palette. This function takes
    the palette number as its argument and returns the former palette value.
    The Graphics Library supplements the two palettes provided by the BIOS
    with intensified versions of each. (See Table 15-3.) In all cases, color
    0 is the current background color.

    Table 15-3 Palette Values for _selectpalette()
    Palette            Color 1           Color 2            Color 3
    ──────────────────────────────────────────────────────────────────────────
    0                  Green             Red                Dark yellow
    1                  Cyan              Magenta            Light gray
    2                  Light green       Light red          Yellow
    3                  Light cyan        Light magenta      White
    ──────────────────────────────────────────────────────────────────────────

    To select a particular color from a palette, use _setcolor(). For example,
    if palette 0 is in effect, _setcolor(2) sets the color to red. (This
    function interprets color values differently in the EGA and VGA modes, as
    we'll see later.) When you call a drawing function from the Graphics
    Library, it draws with the currently defined color.

    The Graphics Library function for setting the background color is
    _setbkcolor(). It takes a long color value as an argument and returns the
    color value of the background in effect when the function is called. The
    numeric value of the argument depends on whether you use _setbkcolor() in
    a text mode or in a graphics mode. This complication arises from the need
    to make the function compatible with the VGA graphics modes. Table 15-4
    lists the color values and the manifest constant names defined in graph.h.

    The graphics color values are not consecutive values. Therefore, it is
    often convenient to initialize an array to the values so they can be
    accessed consecutively with an array index. The DOTS.C program (on p.
    503) demonstrates this procedure.

    Table 15-4 Background Color Values
    Color               Text Color    Graphics Color   Manifest Constant
                        Value (dec)   Value (hex)
    ──────────────────────────────────────────────────────────────────────────
    Black                0L           0x000000L        _BLACK
    Blue                 1L           0x2A0000L        _BLUE
    Green                2L           0x002A00L        _GREEN
    Cyan                 3L           0x2A2A00L        _CYAN
    Red                  4L           0x00002AL        _RED
    Magenta              5L           0x2A002AL        _MAGENTA
    Dark yellow (brown)  6L           0x00152AL        _BROWN
    White (light gray)   7L           0x2A2A2AL        _WHITE
    Dark gray            8L           0x151515L        _GRAY
    Light blue           9L           0x3F1515L        _LIGHTBLUE
    Light green         10L           0x153F15L        _LIGHTGREEN
    Light cyan          11L           0x3F3F15L        _LIGHTCYAN
    Light red           12L           0x15153FL        _LIGHTRED
    Light magenta       13L           0x3F153FL        _LIGHTMAGENTA
    Light yellow        14L           0x153F3FL        _LIGHTYELLOW
    Bright white        15L           0x3F3F3FL        _LIGHTWHITE
    ──────────────────────────────────────────────────────────────────────────

Physical Coordinates

    The library drawing functions use coordinates to determine the location
    images on the screen. Functions in the library use two forms of
    coordinates: physical coordinates and logical coordinates. Physical
    coordinates use the upper-left corner of the screen as their origin;
    logical coordinates let you select the origin. Most of the drawing
    functions use the logical coordinates. However, by default, the logical
    coordinates are the same as the physical coordinates, so let's discuss
    physical coordinates first.

    Both systems measure distances in pixels. The physical coordinate system
    uses the upper-left corner as the origin, that is, as the point whose
    coordinates are (0, 0). The column number, or x value, is listed first,
    and the row number, or y value, is listed second. The column values
    increase to the right, and the row values increase downward. Thus, for a
    320-by-200 mode, the physical coordinates of the lower-right corner are
    (319, 199). Remember that numbering starts with 0, so column 319 is the
    320th column. (See Figure 15-1.)

    A Simple Example

    Let's write a small program that uses the _setpixel() function. This
    function takes two arguments──the horizontal and vertical location of a
    pixel──then sets that pixel to the color last set by _setcolor(). Our
    program uses the default logical coordinate system, whose origin is at the
    upper-left corner of the screen. If coordinates fall outside the drawing
    region, the function returns a value of -1.

    The DOTS.C program (Listing 15-5) uses nested loops to draw a rectangular
    pattern of pixels. Recall that you can override the default mode
    (_MRES4COLOR) with a command-line argument. After the program draws a
    pattern, you can press p to change the palette and press b to change the
    background. Each keystroke increments the palette or background number by
    one. (Palette changing works only in the CGA modes.) Remember that you
    must either use the \l option to load the GRAPHICS.QLB library into memory
    or else use a program list to access it. To exit the program, press the
    Esc key.

    ┌──Origin at upper-left corner (x = 0, y = 0)
    │
    │
    │      ┌────────────────────────────────────────────────┐
    └──────┼►▒─────────────►                                │
            │ │  x pixels   |                                │
            │ │             |                                │
            │ │y pixels     |                                │
            │ │             |                                │
            │ ▼-------------▓◄────pixel                      │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            │                                                │
            └────────────────────────────────────────────────┘

    Figure 15-1. Locating a pixel using physical coordinates.

    ──────────────────────────────────────────────────────────────────────────
    /* dots.c -- illustrates the _setcolor(), _setpixel(), */
    /*           and _selectpalette() functions from the   */
    /*           QuickC Graphics Library                   */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <conio.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <graph.h>
    #define ESC '\033'
    #define BKCOLS 8      /* number of background colors */
    #define PALNUM 4      /* number of palettes */
    long Bkcolors[BKCOLS] = {_BLACK, _BLUE, _GREEN, _CYAN, _RED,
                            _MAGENTA, _BROWN, _WHITE};
    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        unsigned int col, row;
        short color = 0;
        int bkc_index = 1;  /* blue background */
        short palette = 0;  /* red, green, brown */
        int firstcol, firstrow, lastrow, lastcol;
        int mode = _MRES4COLOR;
        int ch;

        if (argc > 1)
            mode = atoi(argv[1]);

        if (_setvideomode(mode) == 0)
            {
            printf("Can't do that mode.\n");
            exit(1);
            }
        _getvideoconfig(&vc);
        firstcol = vc.numxpixels / 5;
        firstrow = vc.numypixels / 5;
        lastcol = 4 * vc.numxpixels / 5;
        lastrow = 4 * vc.numypixels / 5;
        _selectpalette(palette);
        _setbkcolor(Bkcolors[bkc_index]);
        for (col = firstcol; col <= lastcol; ++col)
            {
            _setcolor((++color / 3) % vc.numcolors);
            for (row = firstrow; row <= lastrow; ++row)
                _setpixel(col, row);
            }
        while ((ch = getch()) != ESC)
            {
            if (ch == 'p')
                _selectpalette(++palette % PALNUM);
            else if (ch == 'b')
                _setbkcolor(Bkcolors[++bkc_index % BKCOLS]);
            }
        _setvideomode(_DEFAULTMODE);  /* reset orig. mode */
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-5.  The DOTS.C program.

    Program Notes

    Drawing the pattern dot by dot is a slow process. But palette and
    background changes are practically instantaneous because they do not alter
    the video memory; they merely alter the interpretation of the bits already
    present.

    The Bkcolors[] array is initialized to the first eight background colors.
    Later, the program steps through these nonsequential background color
    values by incrementing the array index.

    The program uses mode-dependent information to draw the figure to scale.
    The _getvideoconfig() function obtains the number of pixels per row and
    column, and the program sizes the figure accordingly. The following code
    defines an area covering 60 percent of the rows and of the columns:

    firstcol = vc.numxpixels / 5;
    firstrow = vc.numypixels / 5;
    lastcol = 4 * vc.numxpixels / 5;
    lastrow = 4 * vc.numypixels / 5;

    Thus, in the 320-by-200 mode, the first column is 64 and the last column
    is 256, while in the 640-by-350 EGA mode, the first column is 128 and the
    last is 512.

    The following statement changes the current color value:

    _setcolor((++color / 3) % vc.numcolors);

    This increments color each time the program writes a new column. However,
    because integer division is truncated, the expression color / 3 increases
    only when color increases by 3. Thus, the columns change color every third
    column instead of every column. Unbounded incrementing causes color to
    exceed the valid range. Therefore, the code uses the modulus operator to
    produce a value in the range 0 through vc.numcolors - 1. For the
    _MRES4COLOR mode, where vc.numcolors is equal to 4, this range is 0
    through 3. (Using vc.numcolors makes the program more portable among
    different video modes.)

EGA and VGA Considerations

    Recall that the Runtime Options feature lets the program run in EGA and
    VGA modes. How does changing the mode affect the program? First, the row
    and column limits are set to reflect the new height and width of the
    screen in pixels. Second, the vc.numcolors value is reset to the new mode.
    The _MRES4COLOR mode sets a value of 4 in vc.numcolors; the _ERESCOLOR
    reports a value of 16 if sufficient EGA memory is available, and it
    reports a value of 4 otherwise.

    Those are the explicit provisions we made for other modes. In addition,
    some of the functions work differently. The _selectpalette() function, for
    example, is recognized only by the 320-by-200 four-color mode and the
    320-by-200 B/W mode; other color graphics modes ignore it because they
    don't use the simple CGA palette system. Instead, the EGA and VGA
    graphics-mode palettes contain more than four colors and also let you
    select the palette colors individually. The default palette for the
    EGA/VGA modes is essentially the same as the background colors shown in
    Table 15-4 on p. 501. (The EGA/VGA brown, however, is a different tint
    than the CGA brown.) The _setcolor() function uses the same numeric values
    as shown in the text color column of Table 15-4, except that it uses type
    short instead of type long.

Drawing Lines

    Two minor modifications to the DOTS.C program produce major changes in its
    operation. First, we can speed up the program noticeably by using the
    _lineto() function from the Graphics Library. This function takes a column
    coordinate and a row coordinate as arguments and draws a line from the
    current screen position to the specified position. The _moveto() function
    changes the current screen position to the column and row specified by its
    two arguments. This function lets you relocate your figurative drawing pen
    without drawing. It's easy to modify DOTS.C to use these functions instead
    of _setpixel(). First, use the MS-DOS COPY command or, within QuickC,
    choose Merge or Save As from the File menu to create a copy of DOTS.C.
    Then modify the copy by replacing the following lines:

    for (col = firstcol; col <= lastcol; ++col)
        {
        _setcolor((++color / 3) % vc.numcolors);
        for (row = firstrow; row <= lastrow; ++row)
            _setpixel(col, row);
        }

    with these lines:

    for (col = firstcol; col <= lastcol; ++col)
        {
        _setcolor((++color / 3) % vc.numcolors);
        _moveto(col, firstrow);   /* new and improved */
        _lineto(col, lastrow);    /* version          */
        }

    This replaces the inner for loop of DOTS.C with two library functions that
    make this version of the program approximately 10 times faster than the
    original.

A Beautiful Example

    If _lineto() draws so much faster than _setpixel(), why bother to use the
    latter function at all? One reason is that _setpixel() offers more
    detailed control. It lets you create involved and intriguing displays, as
    the next example shows.

    One way to create interesting patterns is to key the color of a pixel to
    the value of its coordinates. Make another copy of DOTS.C and name it
    MOIRE.C. Alter the lines

    for (col = firstcol; col <= lastcol; ++col)
        {
        _setcolor((++color / 3) % vc.numcolors);
        for (row = firstrow; row <= lastrow; ++row)
            _setpixel(col, row);
        }

    so that they read as follows:

    for (col = firstcol; col <= lastcol; ++col)
        {
        for (row = firstrow; row <= lastrow; ++row)
            {
            _setcolor(((row * row + col * col) / 10)
                        % vc.numcolors);
            _setpixel(col, row);
            }
        }

    Note that the _setcolor() function has a new argument and that it has been
    moved to the inner loop.

    This alteration produces a dramatic change in the display──complex,
    interlocking patterns called "Moire patterns." The difference is even more
    impressive in the EGA and VGA modes because of their higher resolution and
    greater number of colors.

Logical Coordinates

    Many of the drawing functions, including the ones we've used, take logical
    coordinates rather than physical coordinates. By default, they are the
    same, so we haven't made a distinction between the two. However, the
    _setlogorg() function lets you select another point as the origin of the
    logical coordinate system. To use the function, pass it the physical
    coordinates of the new origin. For example, using the call _setlogorg(100,
    50) makes the point (100, 50) the new origin. Points to the left of the
    new origin now have negative row values, and points to the right are
    positive. Similarly, points above the new origin have negative row values,
    and points below have positive values. (See Figure 15-2.)

                ┌──Origin set by _setlogorg()
                │
                │
    ┌────────────┼───┬──────────────────────────────────┐
    │            │   │Negative y                        │
    │            │   │                                  │
    │Negative x  └──►│                   Positive x     │
    ├────────────────▒─────────────►────────────────────┤
    │                │  x pixels   |                    │
    │                │             |                    │
    │                │y pixels     |                    │
    │                │             |                    │
    │                ▼-------------▓                    │
    │                │                                  │
    │                │                                  │
    │                │Positive y                        │
    │                │                                  │
    └────────────────┴──────────────────────────────────┘

    Figure 15-2. Locating a pixel using logical coordinates.

    Using logical coordinates can simplify specifying locations. For example,
    the center of the screen is a common choice as the logical origin because
    the signs of the coordinates signal which quadrant of the screen a point
    is in.

Drawing Rectangles

    Now let's draw some rectangles. You could use _lineto(), but the Graphics
    Library contains a ready-made function for the task, _rectangle(). This
    function lets you do the following:

    ■  Specify whether the rectangle is drawn in outline or as a filled
        figure.

    ■  Specify the size and location of the rectangle.

    ■  Select a color.

    ■  Select a line-drawing style for outlined rectangles.

    ■  Select a fill pattern for filled rectangles.

    Function arguments determine the type, size, and location of the
    rectangle. The first argument specifies whether the rectangle is an
    outline or solid. The two values for this argument (from the graph.h file)
    are as follows:

    #define _GBORDER         2    /* draw outline only  */
    #define _GFILLINTERIOR   3    /* fill using current */
                                    /* fill mask          */

    Outlined figures are drawn using the current line style, which is, by
    default, a solid line. Likewise, filled figures use the current fill
    pattern ("fill mask"), which is, by default, a solid color.

    The remaining four arguments are the x and y logical coordinates of the
    upper-left corner of the rectangle and the x and y logical coordinates of
    the lower-right corner of the rectangle.

    The drawing color (outline or fill pattern) is the last value of
    _setcolor(). The _setlinestyle() function specifies the line style, and
    the _setfillmask() function specifies the fill pattern. The _rectangle()
    function uses the last value set by these two functions or, if they aren't
    used, it uses the default values.

    The RECT.C program (Listing 15-6) illustrates logical coordinates,
    _rectangle(), and _setlinestyle(). Figure 15-3 shows output from the
    program.

    Program Notes

    Using logical coordinates, you can easily center rectangles on the screen:
    Merely assign the upper-left corner the negative coordinates of the
    lower-right corner. This program generates a series of centered rectangles
    by repeatedly scaling down the dimensions. Specifying this sequence of
    rectangles in the physical coordinate system is a more tedious task
    because you have to calculate all the coordinates.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-3 can be found on p.508 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-3. Output of RECT.C.

    ──────────────────────────────────────────────────────────────────────────
    /*  rect.c -- illustrates logical coordinates,         */
    /*            the _rectangle() and _setlinestyle()     */
    /*            functions                                */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <graph.h>
    #include <conio.h>
    #define STYLES 5
    short Linestyles[STYLES] = {0xFFFF, 0x8888, 0x7777,
                                0x00FF, 0x8787};
    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _MRES4COLOR;
        int xcent, ycent;
        int xsize, ysize;
        int i;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            printf("Can't open that mode.\n");
            exit(1);
            }
        _getvideoconfig(&vc);
        xcent = vc.numxpixels / 2 - 1;
        ycent = vc.numypixels / 2 - 1;
        _setlogorg(xcent, ycent);
        xsize = 0.9 * xcent;
        ysize = 0.9 * ycent;
        _selectpalette(1);
        _setcolor(3);
        _rectangle(_GBORDER, -xsize, -ysize, xsize, ysize);
        xsize *= 0.9;
        ysize *= 0.9;
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, -xsize, -ysize, xsize, ysize);
        for (i = 0; i < 16; i++)
            {
            _setcolor(((i % 2) == 0) ? 2 : 3);
            _setlinestyle(Linestyles[i % 5]);
            xsize *= 0.9;
            ysize *= 0.9;
            _rectangle(_GBORDER, -xsize, -ysize, xsize, ysize);
            }
        getch();      /* Type a key to terminate. */
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-6.  The RECT.C program.

    The program draws the outer rectangle in outline and the next rectangle is
    solid. Then it draws a series of diminishing outline rectangles using a
    variety of line styles. The following statement:

    _setcolor(((i % 2) == 0) ? 2 : 3);

    chooses color 2 if the index i is even, and color 3 if the index is odd;
    this prevents the use of color 1, which would not appear against a
    background of the same color.

    Line Styles

    Now let's see how the rectangle-drawing loop cycles though a list of line
    styles.

    The _setlinestyle() function takes one argument, a 16-bit "mask." This
    mask is a template in which each bit represents a pixel in the line being
    drawn. If a bit is set to 1, the corresponding pixel is turned on when the
    line is drawn. The pixel is set to the background color if the bit is 0.
    This template covers only 16 pixels, but you can visualize it as being
    moved along the line 16 pixels at a time.

    The default line style is a solid line. That translates into a mask of
    sixteen 1s, or the hex value 0xFFFF. Now consider the following statement:

    _setlinestyle(0x0F0F);

    This function call creates a mask with the bit pattern 0000111100001111,
    which produces a pixel pattern of four pixels off, four pixels on, four
    off, four on──a dashed line. (See Figure 15-4.)

    In RECT.C, the linestyles array contains five line masks. The for loop
    near the end of the program cycles through these line styles as it draws a
    series of rectangles of decreasing size.

    Hex                Binary
            ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
    OxOFOF  │0│0│0│0│1│1│1│1│0│0│0│0│1│1│1│1│
            └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
                        │               │
                        ▼               ▼
                    ▒▒▒▒▒▒▒         ▒▒▒▒▒▒▒
            └───────────────┬───────────────┘
                        Line style

    Figure 15-4. A line style mask.

The _ellipse() Function

    The Graphics Library does not limit you to lines and rectangles. The
    _ellipse() function, for example, lets you draw ellipses. It takes the
    same argument list as _rectangle() and draws an ellipse that fits just
    inside the rectangular framework. The EGGS.C program (Listing 15-7)
    presents an example of this function and also illustrates what happens
    when solid figures overlap. (See Figure 15-5 on p. 512.)

    ──────────────────────────────────────────────────────────────────────────
    /*  eggs.c -- draws colorful eggs                                     */
    /*  This program illustrates use of the video configuration           */
    /*  structure, the _ellipse() function, the effect of over-           */
    /*  lapping solid figures, and the use of logical coordinates.        */
    /*  If you load graphics.qlb, no program list is needed.              */

    #include <stdio.h>
    #include <stdlib.h>
    #include <graph.h>
    #include <conio.h>
    #define ESC '\033'

    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _MRES4COLOR;
        short xcent[3], ycent[3]; /* egg centers */
        short xsize, ysize;       /* egg limits  */
        int egg;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            printf("Can't open mode %d\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        xsize = 0.3 * vc.numxpixels;
        ysize = 0.3 * vc.numypixels;
        xcent[0] = 0.3 * vc.numxpixels;
        xcent[1] = 0.5 * vc.numxpixels;
        xcent[2] = 0.7 * vc.numxpixels;
        ycent[0] = ycent[2] = 0.4 * vc.numypixels;
        ycent[1] = 0.6 * vc.numypixels;
        _selectpalette(0);
        _setbkcolor(_MAGENTA);
        for (egg = 0; egg < 3; egg++)
            {
            _setlogorg(xcent[egg], ycent[egg]);
            _setcolor(egg + 1);
            _ellipse(_GFILLINTERIOR, -xsize, -ysize, xsize, ysize);
            }
        _settextposition(24, 0);
        _settextcolor(1);
        _outtext("Strike any key to terminate.");
        getch();
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-7.  The EGGS.C program.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-5 can be found on p.512 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-5. Output of EGGS.C.

    This program produces three colored Easter eggs. When figures overlap, the
    figure drawn last prevails. Again, we use logical coordinates to simplify
    specifying the figures. The arrays xcent[] and ycent[] contain the
    coordinates for the centers of the three ellipses. The same command draws
    all three figures; however, the program changes the logical coordinates
    each time so that the ellipses are centered on different points.

    We also use Graphics Library text functions in this program. Unlike
    printf(), these functions let you specify the location and color of the
    text. The _settextposition() function positions the text on the screen.
    Unlike the graphics functions, it measures positions in character rows and
    columns rather than in pixel columns and rows. Thus, our program positions
    text at row 24, column 0. The _settextcolor() command sets the color of
    the text. In the CGA graphics modes, you select the colors from the
    graphics palette. The EGA and VGA modes use the EGA and VGA palettes. In
    the color text modes, the colors follow the usual scheme──1 is blue, 2 is
    green, and so on. The _outtext() function takes an argument of a pointer
    to a string and prints it using the location and color specified by the
    previous functions.

Filling Figures: _setfillmask() and _floodfill()

    Just as _rectangle() uses a line mask to determine the line style,
    _rectangle() and _ellipse() use a "fill mask" to determine the pattern
    that fills a figure. By default, this pattern is a solid color, but you
    can redefine the pattern with _setfillmask(). Recall how _setlinestyle()
    uses a 16-bit pattern to represent a 16-pixel section of line. The
    _setfillmask() function uses an 8-by-8-bit pattern to represent a pixel
    pattern. In particular, it uses an array of eight bytes, with each byte
    representing one row.

    To create a bit map, draw an 8-by-8 pattern of squares. Fill in the
    squares that represent turned-on pixels. Then visualize each row as a
    binary number, each dark square representing a 1 and each clear square
    representing a 0. Take these eight binary numbers and convert them into a
    form recognized by C. (Use hexadecimal notation because each set of four
    bits corresponds to one hex digit.) After you initialize an array with
    those eight values, you can use the array as a fill mask.

    Figure 15-6 shows a pattern that you can use repeatedly to create a
    brick-like pattern. The first row has a bit pattern of 1111 1111. The
    pattern 1111 corresponds to the hex value F; therefore, the entire byte is
    0xFF in hex. The remaining rows generate the values shown in the figure.
    Create a mask that uses these values with the following declaration:

    unsigned char Mask[]= {0xFF, 0x80, 0x80, 0x80,
                            0xFF, 0x08, 0x08, 0x08};

    Use this as the fill mask with the following statement:

    _setfillmask(Mask);

    directly before you call _rectangle() or _ellipse() in the fill mode. The
    color of this mask is the same as the color of the figure.

    To make the fill a different color than the outline, draw the figure in
    the outline mode, change the current color, and use the _floodfill()
    function to fill the figure. This function takes three arguments: x, or
    pixel column, position; y, or pixel row, position; and a color number. If
    the specified position falls within a closed boundary drawn in the
    indicated color, the interior of the figure is filled with the current
    fill pattern. If the point is outside the figure, the exterior is filled.
    Do not specify a point that lies on the line.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-6 can be found on p.513 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-6. A fill mask.

    The MASKS.C program (Listing 15-8) demonstrates how to use masks. Note
    that this program looks better in the CGA mode than it does in either the
    EGA or VGA modes. That's because the patterns are larger at lower
    resolution and because the colors 1, 2, and 3 in the other modes don't
    look as good together as do the colors of the CGA palette 0. The program
    first draws a large rectangle and divides it into three parts. Then a for
    loop fills the parts using three separate masks and three different
    colors. (See Figure 15-7.)

    ──────────────────────────────────────────────────────────────────────────
    /*  masks.c -- illustrates _setfillmask() and          */
    /*             _floodfill()                            */
    /* Program list: masks.c                               */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <graph.h>
    unsigned char Inversemask[8];
    unsigned char Masks[3][8] = {
                {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00},
                {0xFF, 0x80, 0x80, 0x80, 0xFF, 0x08, 0x08, 0x08},
                {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _MRES4COLOR;
        short xc, yc;
        short box, i;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "Can't set mode %d\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        xc = vc.numxpixels / 2;
        yc = vc.numypixels / 2;
        for (i = 0; i < 8; i++)
            Inversemask[i] = ~Masks[1][i];
        _setlogorg(xc, yc);
        _selectpalette(0);
        _setcolor(1);
        _rectangle(_GBORDER, -xc + 1, -yc + 1, xc - 1, yc - 1);
        _moveto(-xc + 1, -yc / 3);
        _lineto(xc - 1, -yc / 3);
        _moveto(-xc + 1, yc / 3);
        _lineto(xc - 1, yc / 3);
        for (box = 0; box < 3; box++)
            {
            _setcolor(box + 1);
            _setfillmask(Masks[box]);
            _floodfill(0, (box - 1) * yc / 2, 1);
            }
        _settextposition(5, 10);
        _outtext("Press a key to continue");
        getch();
        _setcolor(3);
        _setfillmask(Inversemask);
        _floodfill (0, 0, 1);
        _setcolor(2);
        _setfillmask(Masks[0]);
        _floodfill(0, yc / 2, 1);
        _settextposition(5, 10);
        _outtext("Press a key to terminate");
        getch();
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-8.  The MASKS.C program.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-7 can be found on p.515 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-7. Output of MASKS.C.

    Pressing a key initiates the second phase of the program, which
    illustrates what happens when you refill a figure. For example, the
    program overlays the bottom third of the rectangle with the mask used in
    the top third. Wherever the top mask has a bit set to 1, it covers the
    bottom pattern; wherever it has a bit set to 0, the bottom shows through.
    Thus, those portions of a mask set to 0 are "transparent."

    The middle third of the rectangle demonstrates another aspect of
    superimposed masks. Inversemask[] is the opposite of Masks[1][]:

    for (i = 0; i < 8; i++)
        Inversemask[i] = ~Masks[1][i];

    The loop uses the bitwise ~ operator to create a mask containing the
    reversed bits of Masks[1][] (1s become 0s, and 0s become 1s).
    Superimposing the inverse mask on the original mask but using a different
    color produces a two-color pattern. (See Figure 15-8.)

    When you use _floodfill(), use a solid border; the pattern "leaks" through
    a border with gaps. If you superimpose one pattern on top of another, the
    boundary color (the third argument to _floodfill()) must be a different
    color from the original pattern. The _floodfill() function turns on pixels
    until it reaches pixels set with the specified boundary color. If the
    pixels from the first mask have the same color as the boundary, the fill
    function stops when it encounters them, and thus it does not fill the
    entire figure.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-8 can be found on p.516 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-8. More output of MASKS.C.

Filling Other Shapes

    You can use the _floodfill() command with any area enclosed by a solid
    boundary. The ARROW.C program (Listing 15-9) provides an example using
    the _lineto() function. Figure 15-9 on the following page shows the
    output. Note that the Mask pattern fills the interior of the figure, and
    the Outmask pattern fills the exterior.

    ──────────────────────────────────────────────────────────────────────────
    /* arrow.c -- fills inside and outside of a line       */
    /*            drawing                                  */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <graph.h>
    #include <conio.h>
    #define ESC '\033'
    #define BKCOLS 16   /* use 16 background colors */
    long Bkcolors[BKCOLS] = {_BLACK, _BLUE, _GREEN, _CYAN,
                    _RED, _MAGENTA, _BROWN, _WHITE,
                    _GRAY, _LIGHTBLUE, _LIGHTGREEN,
                    _LIGHTCYAN, _LIGHTRED, _LIGHTMAGENTA,
                    _LIGHTYELLOW,_BRIGHTWHITE};
    char Mask[8] = {0x90, 0x68, 0x34, 0x19, 0x19, 0x34, 0x68,
                    0x90};
    char Outmask[8] = {0xFF, 0x80, 0x80, 0x80, 0xFF, 0x08, 0x08,
                        0x08};
    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _MRES4COLOR;
        float x1, y1, x2, y2, x3, y3, y4, x5, y5;
        long bk = _BLUE;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            printf("Can't set mode %d.\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);

        x1 = 0.1 * vc.numxpixels;
        x2 = 0.7 * vc.numxpixels;
        x3 = 0.6 * vc.numxpixels;
        x5 = 0.9 * vc.numxpixels;
        y1 = 0.45 * vc.numypixels;
        y2 = 0.55 * vc.numypixels;
        y3 = 0.3 * vc.numypixels;
        y4 = 0.7 * vc.numypixels;
        y5 = 0.5 * vc.numypixels;
        _selectpalette(0);
        _setcolor(1);
        _moveto(x1, y1);
        _lineto(x2, y1);
        _lineto(x3, y3);
        _lineto(x5, y5);
        _lineto(x3, y4);
        _lineto(x2, y2);
        _lineto(x1, y2);
        _lineto(x1, y1);
        _setcolor(2);
        _setfillmask(Mask);
        _floodfill(x2, y5, 1) ;
        _setcolor(3);
        _setfillmask(Outmask);     /* restores default mask */
        _floodfill(5, 5, 1) ;
        _settextcolor(1);
        _settextposition(23, 0);
        _outtext("Press <enter> to change background.");
        _settextposition(24, 0);
        _outtext("Press <esc> to end.");
        while (getch() != ESC)
            _setbkcolor(Bkcolors[++bk % BKCOLS]);
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-9.  The ARROW.C program.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-9 can be found on p.518 of the printed version of the book.  │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-9. Output of ARROW.C.

    This program uses a 16-element array of background color values to display
    all 16 background colors. In the CGA mode, the background extends beyond
    the area filled by the Outmask pattern. In the EGA and VGA modes, the
    background area is the same as the fill area.

Replicating Images

    Sometimes you might want to use an image on the screen more than once.
    Rather than redrawing it several times, you can use the Graphics Library
    _getimage() and _putimage() functions to transfer the image from the
    screen to memory and then back to a different screen location.

    The _getimage() Function

    The _getimage() function copies a rectangular region into memory using the
    following format:

    _getimage(xa, ya, xb, yb, storage);

    In this syntax, xa and ya are the coordinates of the upper-left corner of
    the region to be copied, and xb and yb are the coordinates of the
    lower-right corner. The storage parameter is a far pointer to a block of
    memory large enough to hold all the pixel information.

    Typically, you use the _imagesize() function to calculate the number of
    bytes required and then use malloc() to allocate the necessary memory, as
    follows:

    char far *storage;
        ...
    storage = (char far *) malloc((unsigned int) _imagesize(xa, ya, xb,
    yb));

    The _imagesize() function requires as arguments the same corner
    coordinates used by _getimage(). It returns type long far, so you must use
    a typecast to make it agree with malloc().

    The _putimage() Function

    The _putimage() function copies a previously stored image from memory to a
    specified screen location using the following format:

    _putimage(x, y, storage, action);

    The x and y arguments are the coordinates of the upper-left corner of the
    desired position for the image. The storage argument is a pointer to the
    memory location containing the image. The action parameter describes how
    the new image interacts with any existing image at that location. The
    graph.h file contains a list of defined constants, or "action verbs," that
    can be used as action arguments. Table 15-5 on the following page lists
    and explains these constants.

    Table 15-5 Action Verbs for _putimage()
    Action Verb  Meaning
    ──────────────────────────────────────────────────────────────────────────
    _GAND        Combine the new and old images using a logical AND. That is,
                the final color for a pixel is newcolor & oldcolor.
    _GOR         Combine the new and old images using a logical OR. That is,
                the final color for a pixel is newcolor | oldcolor.
    _GXOR        Combine the new and old images using a logical EXCLUSIVE OR.
                That is, the final color for a pixel is newcolor ^ oldcolor.
    _GPSET       Overwrite the old image and display the transferred image as
                it originally appeared. That is, the final color for a pixel
                is newcolor.
    _GPRESET     Overwrite the old image and display the transferred image in
                inverted color. That is, the final color for a pixel is
                ~newcolor.
    ──────────────────────────────────────────────────────────────────────────

    The GETPUT.C program (Listing 15-10) demonstrates all the action verbs.
    It draws an image in the upper-left quadrant of the screen. Next, it fills
    the other three quadrants with striped patterns. Then it copies the
    original image five times to each of the other quadrants, using all five
    action verbs. The original image consists of vertical stripes of the
    palette colors 1, 2, and 3, and the backgrounds consist of horizontal
    stripes of the same colors. Therefore, this example shows all possible
    interactions. (See Figure 15-10 on p. 523.)

    ──────────────────────────────────────────────────────────────────────────
    /*  getput.c -- illustrates _getimage(), _putimage(),  */
    /*              the image-background interaction, and  */
    /*              the aspect ratio                       */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <stdlib.h>  /* declares malloc()  */
    #include <graph.h>
    #include <conio.h>
    #define ESC '\033'

    /* The following variables describe various          */
    /* coordinates and sizes.                            */
    /* They are declared externally so that they can be  */
    /* shared easily by several functions.               */
    int X1, Yb1, X2, Y2, Xdelta, Xside, Yside; /* image  */
    int Xmid, Xmax, Ymid, Ymax;           /* background  */
    int Xps, Xpr, Xand, Xor, Xxor, Ytop, Ybot; /* copies */
    int X[3], Y[3];
    float Ar;    /* aspect ratio */

    struct videoconfig Vc;
    char Mask[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0, 0, 0, 0};
    void Initialize(void), Drawfig(void),
        Drawbackground(void), Drawcopies(void);
    main(argc, argv)
    int argc;
    char *argv[];
    {
        int mode = _MRES4COLOR;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "Can't handle mode %d\n", mode);
            exit(1);
            }
        Initialize();
        Drawfig();
        Drawbackground();
        Drawcopies();
        _settextposition(1, 1);
        _outtext("Press a key to end");
        _settextposition(3, 1);
        _outtext("_GPSET _GPRESET _GAND");
        _settextposition(11, 5);
        _outtext("_GOR _GXOR");
        getch();
        _setvideomode(_DEFAULTMODE);
    }

    void Initialize()
    {
        _getvideoconfig(&Vc);
        Ar = (float) (10 * Vc.numypixels) /
            (6.5 * Vc.numxpixels);
        _setlogorg(0, 0);
        Xmid = Vc.numxpixels / 2;
        Ymid = Vc.numypixels / 2;
        Xmax = Vc.numxpixels - 1;
        Ymax = Vc.numypixels - 1;
        /* locate three background rectangles */
        X[0] = Xmid;
        Y[0] = 0;
        X[1] = Xmid;
        Y[1] = Ymid;
        X[2] = 0;
        Y[2] = Ymid;
        X1 = 0.2 * Vc.numxpixels;
        Yb1 = 0.2 * Vc.numypixels;
        Xdelta = 0.033 * Vc.numxpixels;
        Xside = 3 * Xdelta;
        Yside = 3 * Ar * Xdelta;
        X2 = X1 + Xside;
        Y2 = Yb1 + Yside;
        /* offsets for _putimage() */
        Xps = .05 * Vc.numxpixels;
        Xpr = .20 * Vc.numxpixels;
        Xand = 0.35 * Vc.numxpixels;
        Xor = .10 * Vc.numxpixels;
        Xxor = .30 * Vc.numxpixels;
        Ytop = .05 * Vc.numypixels;
        Ybot = 2 * Ytop + Yside;
        _selectpalette(0);
    }

    void Drawfig()
    {
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, X1, Yb1,
                    X1 + Xdelta, Y2);
        _setcolor(2);
        _rectangle(_GFILLINTERIOR, X1 + Xdelta + 1, Yb1,
                    X1 + 2 * Xdelta, Y2);
        _setcolor(3);
        _rectangle(_GFILLINTERIOR, X1 +  2 * Xdelta + 1,
                    Yb1, X2, Y2);

    }

    void Drawbackground()
    {
        _setfillmask(Mask);
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, Xmid, 0, Xmax - 1, Ymid - 1);
        _setcolor(2);
        _rectangle(_GFILLINTERIOR, Xmid, Ymid, Xmax, Ymax);
        _setcolor(3);
        _rectangle(_GFILLINTERIOR, 0, Ymid, Xmid - 1, Ymax);

    }

    void Drawcopies()
    {
        int quad;   /* quadrant used */
        char far *storage;

        storage = (char far *) malloc((unsigned)_imagesize
                    (X1, Yb1, X2, Y2));
        _getimage(X1, Yb1, X2, Y2, storage);
        for (quad = 0; quad < 3; quad++)
            {
            _putimage(X[quad] + Xps, Y[quad] + Ytop,
                    storage, _GPSET);
            _putimage(X[quad] + Xpr, Y[quad] + Ytop,
                    storage, _GPRESET);
            _putimage(X[quad] + Xand, Y[quad] + Ytop,
                    storage, _GAND);
            _putimage(X[quad] + Xor, Y[quad] + Ybot,
                    storage, _GOR);
            _putimage(X[quad] + Xxor, Y[quad] + Ybot,
                    storage, _GXOR);
            }
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-10.  The GETPUT.C program.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-10 can be found on p.523 of the printed version of the book. │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-10. Output of GETPUT.C.

    To recognize the precise interactions, you must understand the action of
    C's bitwise operators, and you must remember that the verbs operate on the
    palette numbers, not the color numbers. For example, if CGA palette 0 is
    in effect, and you select the color green, the action verbs use the
    palette number (1), not the color number for green (2). Because of this,
    the action verbs affect CGA mode colors differently than EGA or VGA
    colors. For example, when CGA palette 0 is used, palette number 1 is
    green. In the two-digit binary that represents CGA palette choices, the
    number is actually 01. When you apply the bitwise negation operator
    _GPRESET to the number, you get 10. In decimal, this is palette choice 2,
    or brown. Therefore, in CGA modes, _GPRESET converts a green image to
    brown. However, the EGA mode uses a 16-color palette. The default palette
    represents green with palette number 2, which is 0010 in binary. _GPRESET
    converts this to 1101 in binary, which is intensified magenta. Therefore,
    in EGA modes, _GPRESET converts green not to brown but to intensified
    magenta──quite different from the CGA operation.

Aspect Ratios

    The GETPUT.C program also illustrates how to make squares. In most modes,
    the pixel width is different from the pixel height, so you cannot make a
    square figure by creating a rectangle with equal numbers of horizontal and
    vertical pixels. To create square rectangles (or circular ellipses), you
    need to calculate the proper "aspect ratio." This is the ratio of the
    number of pixels in a vertical line to the number of pixels in a
    horizontal line of the same physical length. The aspect ratio is the
    product of two ratios:

    ■  (screen height in pixels) / (screen width in pixels)

    ■  (screen height in inches) / (screen width in inches)

    The GETPUT.C program represents that ratio with the following code:

    Ar = (float) (10 * Vc.numypixels) / (6.5 * Vc.numxpixels);

    Then the program uses the aspect ratio to scale the number of y pixels:

    Xside = 3 * Xdelta;
    Yside = 3 * Ar * Xdelta;

    This results in Yside and Xside representing equal lengths on the screen.
    Of course, the vertical and horizontal size settings on your monitor will
    affect your results.

Simple Animation

    You can use the _getimage() and _putimage() functions to create animation.
    The method is to erase the current image and to put a copy of the image at
    a slightly displaced location. To erase, you can superimpose a copy on the
    original using the _GXOR action verb. Because this operation combines like
    bits to produce an off bit, all bits get turned off.

    The RACE.C program (Listing 15-11) uses this animation technique to stage
    a race across the screen. The contestants are three patterned circles. The
    race, in this case, goes to the luckiest, for the program uses the rand()
    random number function to control the motion. At each movement
    opportunity, the program calls rand(). If it returns an even value, the
    circle moves ahead a step; otherwise it stays put.

    ──────────────────────────────────────────────────────────────────────────
    /* race.c -- race of the patterned circles             */
    /*    Illustrates animation with _getimage() and       */
    /*    _putimage(), random number use with srand() and  */
    /*    rand(), and system clock use with time() and     */
    /*    ftime().                                         */
    /* Program list: race.c (for srand(), rand(), and      */
    /*                       ftime())                      */

    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <graph.h>
    #include <time.h>
    #include <sys\types.h>
    #include <sys\timeb.h>

    #define END 25
    #define FIGNUM 3
    typedef char far *PTFRCHAR;
    PTFRCHAR Bufs[FIGNUM];
    unsigned char Masks[FIGNUM][8] = {
                {0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00},
                {0xF0, 0xF0, 0xF0, 0xF0, 0x0F, 0x0F, 0x0F, 0x0F},
                {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA}};
    short Xul[FIGNUM], Yul[FIGNUM];  /* figure locations */
    short Xsize, Ysize;              /* figure size      */
    struct videoconfig Vc;
    void Initialize(void);
    void Draw_n_store(void);
    void Move_figs(void);
    void Wait(double);

    main(argc, argv)
    int argc;
    char *argv[];
    {
        int mode = _MRES4COLOR;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "mode %d not supported\n", mode);
            exit(1);
            }
        Initialize();
        Draw_n_store();
        _settextcolor(2);
        _settextposition(1, 1);
        _outtext("Place your bets and press a key");
        _settextposition(25, 1);
        _outtext("Press a key again when done");
        getch();
        Move_figs();
        getch();
        _setvideomode(_DEFAULTMODE);
    }

    void Initialize()
    {
        int i;
        float ar;  /* aspect ratio */

        _getvideoconfig(&Vc);
        ar = (float)(10 * Vc.numypixels) / (6.5 * Vc.numxpixels);
        /* set size, initial positions */
        Xsize = Vc.numxpixels / 30;
        Ysize = ar * Xsize;
        for(i = 0; i < FIGNUM; i++)
            {
            Xul[i] = 0;
            Yul[i] = (i + 1) * Vc.numypixels /
                        (FIGNUM + 1);
            }
        _selectpalette(0);
        _setcolor(1);
            /*  draw finish line */
        _moveto(END * Xsize, 0);
        _lineto(END * Xsize, Vc.numypixels - 1);
    }

    void Draw_n_store() /* draw images, save them */
    {
        int i;

        for (i = 0; i < FIGNUM; i++)
            {
            _setcolor(i + 1);
            _setfillmask(Masks[i]);
            _ellipse(_GFILLINTERIOR, Xul[i], Yul[i],
                    Xul[i] + Xsize, Yul[i] + Ysize);
            _ellipse(_GBORDER, Xul[i], Yul[i],
                    Xul[i] + Xsize, Yul[i] + Ysize);
            Bufs[i] = (PTFRCHAR) malloc((unsigned int)
                    _imagesize(0, Yul[i], Xul[i] +
                    Xsize, Yul[i] + Ysize));
            _getimage(Xul[i], Yul[i], Xul[i] + Xsize, Yul[i] +
                    Ysize, Bufs[i]);

            }
    }
    void Move_figs()
    {
        int i, j;
        static int dx[FIGNUM] = {0, 0, 0}; /* displacements */
        time_t tval;

        time(&tval);    /*   use the current time value   */
        srand(tval);    /*   to initialize rand()         */
        while (dx[0] < END && dx[1] < END && dx[2] < END)
            {
            for (i = 0; i < FIGNUM; i++)
                {
                /* Advance the figure one position if  */
                /* rand() returns an even number.      */
                if (rand() % 2 == 0)
                    {
                    /* erase old image */
                    _putimage(dx[i] * Xsize, Yul[i],
                            Bufs[i], _GXOR);
                    /* redraw in new position */
                    _putimage((1 + dx[i]) * Xsize, Yul[i],
                            Bufs[i], _GPSET);
                    dx[i]++;
                    }
                }
            Wait(0.15);
            }
        for (j = 0; j < 5; j++)
            {
            for(i = 0; i < FIGNUM; i++)
                {
                /* flash winning figure */
                if (dx[i] >= END)
                    {
                    Wait(0.2);
                    _putimage(dx[i] * Xsize, Yul[i],
                            Bufs[i], _GPRESET);
                    Wait(0.2);
                    _putimage(dx[i] * Xsize, Yul[i],
                            Bufs[i], _GPSET);
                    }
                }
            }
    }

    void Wait(pause) /* wait for pause seconds */
    double pause;
    {
        struct timeb start, end;
        long delay;
        delay = 1000 * pause;  /* convert to milliseconds */
        ftime(&start);
        ftime(&end);
        while ((1000 * (end.time - start.time) +
                + end.millitm - start.millitm) < delay)
            ftime(&end);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-11.  The RACE.C program.

    The RACE.C program uses three action verbs. The _GPSET verb generates new
    ("moving") images. The _GXOR verb erases the old images. Finally, the
    program alternates between _GPSET and _GPRESET, creating a flashing image
    for the winner. (Ties are possible, in which case the cowinners flash.)

    The program also features several standard C functions. The rand()
    function returns a random number in the range 0 through 32,767. Actually,
    it returns a "pseudorandom" number, meaning that eventually the function
    returns the same sequence of numbers. Also, rand() always starts with the
    same sequence of numbers unless you first use srand() to select a
    different starting point. The program uses the time() function to "seed"
    srand() with a different argument each time the program is called. As a
    result, the program always runs using a different sequence of random
    numbers. (The time() function places the number of seconds elapsed between
    00:00:00, January 1, 1970 [GMT] and the current clock time into the
    location whose address is passed to it as an argument.)

    Programs such as this can run into problems because different computers
    and different video modes run the animation at different speeds. What
    plods on one system might blur on another. To meet this problem, RACE.C
    uses the system clock to run the animation at a constant rate. The Wait()
    function causes the program to pause for the number of seconds indicated
    by its floating-point argument. Wait(), in turn, uses the standard C
    function ftime(). The argument for this function is a pointer to a type
    struct timeb structure, as defined in <SYS\timeb.h>. The time field of
    this structure is the time in seconds since 00:00:00 GMT, January 1, 1970
    (the value provided by time()). The millitm field is a fraction of a
    second in milliseconds. This added information allows Wait() to pause for
    fractions of a second. (Wait() converts all times to milliseconds to
    facilitate finding the difference between two times.)

    Although the timeb structure tells time to the nearest millisecond, the
    system clock might progress in larger jumps. The IBM PC/XT/AT clock, for
    example, has 18.2 ticks per second; so each tick advances the time
    approximately 50 milliseconds.


EGA Graphics

    As Table 15-1 on p. 492 suggests, the EGA offers more graphics
    capabilities than the CGA offers. The two adapters also map video memory
    to the display differently, but the QuickC Graphics Library functions hide
    those details from us. What QuickC can't hide are the different ways that
    the EGA graphics modes handle color.

The Palette

    For comparison, let's quickly review the CGA palette. It has four colors
    numbered 0 through 3, and you can use the _setcolor() function to choose a
    particular color from the palette. Color 0 is the background color, and
    you set its value with the _setbkcolor() function by using one of the
    manifest constants in Table 15-4 on p. 501. The other three colors are
    chosen by using _selectpalette() to select one of the four preset
    combinations in Table 15-3 on p. 500.

    The EGA has a 16-color palette, using values in the range 0 through 15.
    Palette value 0 represents the background color. By default, the EGA color
    palette is set to the same colors shown in Table 15-3. As with the CGA,
    you can use _setcolor() to select a particular color from this palette;
    merely use the palette number as the argument. However, the EGA modes
    ignore the _selectpalette() function. Instead, these modes use the
    _remappalette() and _remapallpalette() functions to reassign colors to the
    palette values. That is, palette value 1, by default, is blue. But the
    remapping functions let you assign red, for example, to palette value 1.

    The remapping functions provide you with a powerful tool. Suppose you've
    drawn and colored a figure, but you want to change the colors. Rather than
    redraw the figure and fill it again, you can remap the palette assignments
    and change the on-screen colors almost immediately.

    Specifying Palette Values and Color Values

    In the EGA graphics modes, each pixel is represented by four bits in the
    EGA video memory. These bits represent the palette value, which is a
    number in the range 0 through 15. If a particular pixel has a palette
    number of 3, for example, the EGA looks up the "color value" for that
    palette number and makes the pixel that color. The EGA color value, in
    turn, is a 6-bit number. Essentially, the palette number is an index to a
    table of color values, and the remapping functions alter that table.

    The 6-bit EGA color values can generate 64 colors, but only mode 16
    (_ERESCOLOR) makes all 64 available. The other modes use only 16 colors of
    the default palette; however, they let you select the palette number which
    will correspond to a given color. The total range of EGA colors uses the
    color values 0 through 63. However, the QuickC remapping functions use VGA
    color values for compatibility reasons. This complicates accessing all 64
    EGA colors because, in the VGA representation, the EGA colors are not
    consecutive values. However, you can access the 16 colors of the default
    palette by using the manifest constants in graph.h. (See Table 15-6 on
    the following page.) These are set to the VGA values shown in the second
    column. The EGA values are provided for your information. (Later we will
    show you how to access all 64 colors.)

    Setting the Palette

    The QuickC Graphics Library contains two functions for setting the EGA
    palette. One, called _remappalette(), lets you assign a color value to a
    particular palette value. You can use it, for example, to change palette
    value 3 from cyan to magenta.

    Table 15-6 VGA Color Values for the Default Palette
    #define Name       VGA Color Value   EGA Color Value
    ──────────────────────────────────────────────────────────────────────────
                        Hex               rgb   RGB   Octal
    _BLACK             0x000000L         000   000   000
    _BLUE              0x2A0000L         000   001   001
    _GREEN             0x002A00L         000   010   002
    _CYAN              0x2A2A00L         000   011   003
    _RED               0x00002AL         000   100   004
    _MAGENTA           0x2A002AL         000   101   005
    _BROWN             0x00152AL         010   100   024
    _WHITE             0x2A2A2AL         000   111   007
    _GRAY              0x151515L         111   000   070
    _LIGHTBLUE         0x3F1515L         111   001   071
    _LIGHTGREEN        0x153F15L         111   010   072
    _LIGHTCYAN         0x3F3F15L         111   011   073
    _LIGHTRED          0x15153FL         111   100   074
    _LIGHTMAGENTA      0x3F153FL         111   101   075
    _LIGHTYELLOW       0x153F3FL         111   110   076
    _BRIGHTWHITE       0x3F3F3FL         111   111   077
    ──────────────────────────────────────────────────────────────────────────

    The function uses VGA color values, for which it's convenient to use the
    graph.h constants. To make palette value 3 represent magenta, use the
    following call:

    _remappalette(3,_MAGENTA);

    The second function, called _remapallpalette(), lets you remap all the
    palette values simultaneously. Its argument is an array of the desired
    color values. Because the VGA color values are type long, use a type long
    array initialized to the desired VGA color values.

    The RINGS.C program (Listing 15-12 beginning on p. 532) presents an
    interesting example of palette remapping. It initializes the newpalette[]
    array to light blue for all but three palette values. Palette value 0
    (which represents the background) is assigned gray, and palette values 1
    and 8 are assigned red and light red. Then _remapallpalette() sets the
    palette to the values provided by newpalette[]. Next, a for loop draws a
    series of concentric circles:

    for (index = 2; index < 16; index++)
        {
        xmax /= 1.4;
        ymax /= 1.4;
        _setcolor(index);
        _ellipse(_GFILLINTERIOR, -xmax, -ymax, xmax, ymax);
        }

    If the palette weren't reset, this code would produce concentric rings of
    different colors. However, with the new palette, all but two rings are
    light blue, and all but those two rings blend into a featureless
    background.

    Next, the program enters a loop that shifts the color values in
    newpalette[] by one array element; then it remaps the palette. The first
    pass through this loop assigns red to palette value 2 and light red to
    palette value 9. On the screen, this produces the illusion that each ring
    has moved one position inward. (See Figure 15-12 on the following page.)
    One final program feature lets you use the keys 2 through 7 to change the
    ring colors──by changing the color assignments for newpalette[]. Press the
    Esc key to quit.

    ──────────────────────────────────────────────────────────────────────────
    EGA Color Values
    The QuickC Graphics Library doesn't use the EGA color values explicitly,
    but here's how they work. Each of the 64 EGA colors is represented by a
    6-bit color value. The left three bits represent low-intensity red, green,
    and blue, respectively. The right three bits represent normal-intensity
    red, green, and blue, respectively. The sequence rgbRGB represents this
    order symbolically. The binary value 000001, for example, has the B bit
    (normal-intensity blue) set.

    How then does the EGA form the intensified colors──light blue, light red,
    and so on? The CGA forms them by adding low-intensity white to the
    standard colors. Low-intensity white is 111000 in EGA notation, so light
    blue, for example, is 111001. Table 15-6 defines brown as 010100, or red
    plus low-intensity green. This produces a different tint than the
    corresponding CGA color, which is yellow, or 000110.

    The EGA has other combinations with no CGA equivalents. For example,
    001000 would be a low-intensity blue, and 010100 would be a mixture of
    low-intensity green and normal-intensity red. Altogether, the 6-bit
    representation permits 64 combinations, corresponding to the integers 0
    through 63. Another way of looking at the color value is that each color
    is described by two bits, permitting four intensity settings for that
    color: off, low, normal, and high-intensity, as shown in Figure 15-11.
    The rgb bits are sometimes referred to as the "1/3" intensity bits and the
    RGB bits as the "2/3" intensity bits. Setting both gives full intensity.
    Note, however, that the actual ratios of intensities depend on the
    display's brightness and contrast settings. (The rgbRGB system is also
    reflected in the EGA hardware, which uses six wires, one for each bit, to
    communicate color information to the monitor.)

                                ┌─┐      ┌─┐
    EGA color value bits  r  g │b│ R  G │B│      Blue intensity
                                └─┘      └─┘
                    ┌──        0        0        0   (off)
        Bit values ──┤          1        0       1/3  (low intensity)
                    │          0        1       2/3  (normal)
                    └──        1        1        1   (high intensity)

    Figure 15-11. The EGA supports four intensities for blue.

    ──────────────────────────────────────────────────────────────────────────

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-12 can be found on p.532 of the printed version of the book. │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-12. Output of RINGS.C.

    ──────────────────────────────────────────────────────────────────────────
    /*  rings.c -- shoots colored rings                     */
    /*  This program illustrates _remapallpalette() and     */
    /*  how it can be used to produce the appearance of     */
    /*  motion. The program is intended for EGA modes 13,   */
    /*  14, and 16.                                         */
    /*  Program list: rings.c                               */
    /*  If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <graph.h>
    #define ESC '\033'

    long Colors[16] = {_BLACK, _BLUE, _GREEN, _CYAN,
                    _RED, _MAGENTA, _BROWN, _WHITE,
                    _GRAY, _LIGHTBLUE, _LIGHTGREEN,
                    _LIGHTCYAN, _LIGHTRED, _LIGHTMAGENTA,
                    _LIGHTYELLOW, _BRIGHTWHITE};

    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        float aspect;
        short xmax, ymax;
        long int newpalette[16];
        long int temp;
        int index;
        int hot1 = 1;  /* first colored ring  */
        int hot2 = 8;  /* second colored ring */
        int mode = _ERESCOLOR;
        int ch;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (mode < 13)
            {
            fprintf(stderr, "Requires EGA or VGA mode\n");
            exit(1);
            }
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "% d mode unavailable\n", mode);
            exit(2);
            }
        _getvideoconfig(&vc);
        _setlogorg(vc.numxpixels / 2 - 1, vc.numypixels / 2 - 1);
        aspect = (10.0 * vc.numypixels) / (6.5 * vc.numxpixels);
        ymax = vc.numypixels / 2 - 2;
        xmax = ymax / aspect;
        for (index = 2; index < 16; index++)
            newpalette[index] = _LIGHTBLUE;
        newpalette[0] = _GRAY;
        newpalette[hot1] = _RED;
        newpalette[hot2] = _LIGHTRED;
        _remapallpalette(newpalette);  /* set initial palette */
        _setcolor(1);
        _ellipse(_GFILLINTERIOR, -xmax, -ymax, xmax, ymax);
        /* draw concentric circles */
        for (index = 2; index < 16; index++)
            {
            xmax /= 1.4;
            ymax /= 1.4;
            _setcolor(index);
            _ellipse(_GFILLINTERIOR, -xmax, -ymax, xmax, ymax);
            }
        do
            {
            while (!kbhit())
                {
                temp = newpalette[15];
                for(index = 15; index > 1; index--)
                    newpalette[index] = newpalette[index - 1];
                newpalette[1] = temp;
                _remapallpalette(newpalette);
                hot1 = hot1 % 15 + 1;  /* index of colored ring */
                hot2 = hot2 % 15 + 1;
                }
            ch = getch();
            if (ch > '1' && ch < '8')  /* reassign colors */
                {
                newpalette[hot1] = Colors[ch - '0'];
                newpalette[hot2] = Colors[ch - '0' + 8];
                }
            } while (ch != ESC);
        _clearscreen(_GCLEARSCREEN);
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-12.  The RINGS.C program.

    The program illustrates a useful graphics programming technique──using an
    array to hold the standard color values:

    long Colors[16] = {_BLACK, _BLUE, _GREEN, _CYAN,
                        _RED, _MAGENTA, _BROWN, _WHITE,
                        _GRAY, _LIGHTBLUE, _LIGHTGREEN,
                        _LIGHTCYAN, _LIGHTRED, _LIGHTMAGENTA,
                        _LIGHTYELLOW, _BRIGHTWHITE};

    This use of an array lets a program access the nonsequential VGA values
    with a sequential array index.

Accessing All 64 EGA Colors

    The _ERESCOLOR mode lets you use all 64 EGA colors. However, to do so with
    the QuickC remapping functions, you must use the VGA color code for these
    colors. Although the graph.h list of manifest constants provides 16 of
    these codes, 48 EGA colors are unrepresented. To access them, you need to
    generate their VGA codes.

    The VGA uses six bits to describe the intensity of each of the three
    primary colors. Six bits produce 64 intensity levels for each primary
    color, so the total number of combinations is 64 x 64 x 64, or 262,144
    colors. The VGA stores this information in a 4-byte unit. The leftmost
    byte is set to 0, the next byte contains the 6-bit code for blue
    intensity, the next byte contains the 6-bit code for green intensity, and
    the last byte contains the 6-bit code for red intensity. Each of these
    color bytes has its leftmost two bits set to 0. (See Figure 15-13.)

    How does this compare to the EGA system? Consider the color blue. The EGA
    rgbRGB system can generate four levels of blue: 000000, 001000, 000001,
    and 001001. They represent 0, 1/3, 2/3, and full intensity. The VGA has 64
    levels of intensity, represented by a blue byte in the range 0 through 63.
    Zero intensity is 0. The 1/3 level is 21 decimal, or 0x15 hex. The 2/3
    intensity level is 42 decimal, or 0x2A hex. The full level is 63 decimal,
    or 0x3F hex. Extending this analysis to red and green produces the list of
    correspondences shown in Table 15-7. Hex is the natural base to use for
    VGA values because two hex digits can represent one byte. Thus, the first
    two hex digits represent the blue intensity, the second two hex digits
    represent the green intensity, and the final two hex digits represent the
    red intensity.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-13 can be found on p.535 of the printed version of the book. │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-13. VGA color value storage.

    Table 15-7 EGA Bit to VGA Byte Conversion
    EGA Color Bit        VGA Equivalent       Color
    Setting
    ──────────────────────────────────────────────────────────────────────────
    000 001              0x2A0000             Blue (2/3 intensity)
    000 010              0x002A00             Green (2/3 intensity)
    000 100              0x00002A             Red (2/3 intensity)
    001 000              0x150000             Blue (1/3 intensity)
    010 000              0x001500             Green (1/3 intensity)
    100 000              0x000015             Red (1/3 intensity)
    ──────────────────────────────────────────────────────────────────────────

    Because the table lists all of the EGA color bits as VGA values, you can
    now combine them to represent any EGA color. For example, cyan is blue
    plus green, or 0x2A2A00. For another example, consider light blue, which
    is 111001 in EGA notation. We can sum the VGA equivalents as follows:

    EGA                  VGA                  Color
    ──────────────────────────────────────────────────────────────────────────
    100 000              0x000015             Blue (1/3 intensity)
    010 000              0x001500             Green (1/3 intensity)
    001 000              0x150000             Red (1/3 intensity)
    000 001              0x2A0000             Blue (2/3 intensity)
    -------              --------             ---------------------
    111 001              0x3F1515             Light blue
    ──────────────────────────────────────────────────────────────────────────

    The final value, 0x3F1515, matches VGA_LIGHTBLUE in Table 15-3 on p. 530.

    Defining Nonpalette Colors

    You can use this information to construct a header file (Listing 15-13 on
    the following page) to supplement the graph.h color value constants.

    ──────────────────────────────────────────────────────────────────────────
    /* egacolor.h -- vga equivalents for base ega colors */
    #define r 0x000015L      /* 1/3 intensity red   */
    #define g 0x001500L      /* 1/3 intensity green */
    #define b 0x150000L      /* 1/3 intensity blue  */
    #define R 0x00002AL      /* 2/3 intensity red   */
    #define G 0x002A00L      /* 2/3 intensity green */
    #define B 0x2A0000L      /* 2/3 intensity blue  */
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-13.  The egacolor.h program.

    The constants in egacolor.h provide the base color values. To generate any
    other EGA color, use the bitwise OR operator with these values. For
    example, a color whose rgbRGB representation is 101010 has the VGA value r
    | b | G. What color is this? Well, 111000 is dim white (dark gray), and
    101010 replaces the faint green of 111000 with a brighter green. The
    result is a greenish gray.

    The SCAPE.C program (Listing 15-14) demonstrates how to define other
    colors in terms of the base colors. It draws a simple scene, shown in
    Figure 15-14, in fresh, new colors, none of which are in the default
    palette.

    ──────────────────────────────────────────────────────────────────────────
    /* scape.c -- uses nondefault EGA colors               */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <graph.h>
    #include "egacolor.h"
    #include <stdlib.h>
    #include <conio.h>
    #define SKY (b | B | g)
    #define OCEAN b
    #define SAND (R | g | b)
    #define SUN (R | G | r | g)

    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _ERESCOLOR;
        short xmax, ymax, sunx, suny, sunsizex, sunsizey;
        float ar;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "mode %d not supported\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        xmax = vc.numxpixels - 1;
        ymax = vc.numypixels - 1;
        sunx = 0.7 * xmax;
        suny = 0.2 * ymax;
        ar = (float)(10 * vc.numypixels) / (6.5 * vc.numxpixels);
        sunsizex = xmax / 30;
        sunsizey = ar * sunsizex;
        _remappalette(1, SKY);
        _remappalette(2, OCEAN);
        _remappalette(3, SAND);
        _remappalette(4, SUN);
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, 0, 0, xmax, 2 * ymax / 5);
        _setcolor(4);
        _ellipse(_GFILLINTERIOR, sunx - sunsizex, suny -
                sunsizey, sunx + sunsizex, suny + sunsizey);
        _setcolor(2);
        _rectangle(_GFILLINTERIOR, 0, 2 * ymax / 5, xmax,
                    2 * ymax / 3);
        _setcolor(3);
        _rectangle(_GFILLINTERIOR, 0, 2 * ymax / 3, xmax, ymax);
        getch();
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-14.  The SCAPE.C program.

    ┌────────────────────────────────────────────────────────────────────────┐
    │ Figure 15-14 can be found on p.537 of the printed version of the book. │
    └────────────────────────────────────────────────────────────────────────┘

    Figure 15-14. Output of SCAPE.C.

    Automatic Color Value Conversion

    A second approach to using nonpalette colors is to write a function that
    converts an EGA value to the corresponding VGA value. This lets us map the
    simple EGA color values into the complicated VGA color values that the
    library functions use. Listing 15-15 presents a function that takes an
    EGA color value as an argument and returns the corresponding VGA color
    value.

    The program's egacolor >> bit operation shifts a specified bit to position
    0. The &1 operation then masks all the other bits. This gives the
    expression a value of 1 if the bit is 1; otherwise it is a 0. This is
    multiplied by the VGA equivalent for the bit, and the loop obtains a
    running total.

    A Palette-Mapping Example

    Now we can look at all of the hidden 48 EGA colors. The ALLCOLOR.C program
    (Listing 15-16) uses Ega_to_vga() and _remappalette() to display all the
    EGA colors. It divides the screen vertically into two rectangles. The left
    rectangle fills with blue, and the right with red. Windows at the bottom
    of each rectangle show the current color value. Pressing g advances the
    color value of the left rectangle by 1 (it remaps palette value 1 to the
    next color value). Similarly, h advances the color value of the right
    rectangle. Pressing Shift with these keys decrements the color values.
    Using this program, you can thus match any two of the 64 colors side by
    side and see the sometimes subtle distinctions.

    ──────────────────────────────────────────────────────────────────────────
    /* egatovga.c -- converts ega color values to vga      */
    /*               color values                          */

    long Ega_to_vga(egacolor)
    int egacolor;        /* ega color value */
    {
        static long vgavals[6] = {0x2A0000L, 0x002A00L,
                            0x00002AL, 0x150000L,
                            0x001500L, 0x000015L};
        /* array holds VGA equivalents to EGA bits */
        long vgacolor = 0L; /* vga color value */
        int bit;

        /* convert each bit to equivalent and sum */
        for (bit = 0; bit < 6; bit++)
            vgacolor += ((egacolor >> bit) &1) * vgavals[bit];
        return (vgacolor);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-15.  The Ega_to_vga() function.

    ──────────────────────────────────────────────────────────────────────────
    /* allcolor.c -- shows _ERESCOLOR 64-color palette     */
    /* If you load graphics.qlb, no program list is needed.*/

    /*  Press <g> to advance left palette, <G> to go back. */
    /*  Press <h> to advance right palette, <H> to go back.*/
    /*  Press <Esc> to quit.                               */
    #include <stdio.h>
    #include <graph.h>
    #include <conio.h>
    #define MAXCOLORS 64
    #define ESC '\033'
    long Ega_to_vga(int);   /* color value conversion */

    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _ERESCOLOR;
        int xmax, ymax;
        int c1 = 1;
        int c2 = 4;
        char left[11];
        char right[11];
        int lpos, rpos;
        char ch;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "%d mode not supported\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        _setlogorg(vc.numxpixels / 2, vc.numypixels / 2);

        xmax = vc.numxpixels / 2 - 1;
        ymax = vc.numypixels / 2 - 1;
        lpos = vc.numxpixels / 32 - 5;
        rpos = lpos + vc.numxpixels / 16;
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, -xmax, -ymax, 0, ymax);
        _setcolor(4);
        _rectangle(_GFILLINTERIOR, 1, -ymax, xmax, ymax);
        sprintf(left, "<-G %2d g->", c1);
        sprintf(right, "<-H %2d h->", c2);
        _settextcolor(6);
        _settextposition(0, 0);
        _outtext("Press Esc to quit");
        _settextposition(24, lpos);
        _outtext(left);
        _settextposition(24, rpos);
        _outtext(right);
        while ((ch = getch()) != ESC)
            {
            switch (ch)
                {
                case 'g': c1 = (c1 + 1) % MAXCOLORS;
                        _remappalette(1, Ega_to_vga(c1));
                        break;
                case 'G': c1 = (c1 - 1) % MAXCOLORS;
                        _remappalette(1, Ega_to_vga(c1));
                        break;
                case 'h': c2 = (c2 + 1) % MAXCOLORS;
                        _remappalette(4, Ega_to_vga(c2));
                        break;
                case 'H': c2 = (c2 - 1) % MAXCOLORS;
                        _remappalette(4, Ega_to_vga(c2));
                        break;
                }
            sprintf(left, "<-G %2d ->g", c1);
            sprintf(right, "<-H %2d ->h", c2);
            _settextposition(0, 0);
            _outtext("Press Esc to quit");
            _settextposition(24, lpos);
            _outtext(left);
            _settextposition(24, rpos);
            _outtext(right);
            }
        _setvideomode(_DEFAULTMODE);
    }
    long Ega_to_vga(egacolor)
    int egacolor;       /* ega color value */
    {
        static long vgavals[6] = {0x2A0000L, 0x002A00L, 0x00002AL,
                                0x150000L, 0x001500L, 0x000015L};
        long vgacolor = 0L; /* vga color value */
        int bit;
        for (bit = 0; bit < 6; bit++)
            vgacolor += ((egacolor >> bit) &1) * vgavals[bit];
        return (vgacolor);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-16.  The ALLCOLOR.C program.

    In ALLCOLOR.C, the following code does the remapping:

    case 'g': c1 = (c1 + 1) % MAXCOLORS;
                _remappalette(1, Ega_to_vga(c1));
                break;

    When g is pressed, the program increments by one the color value (c1) for
    the left rectangle. The modulus operator limits the final value to the
    range 0 through 63. The Ega_to_vga() function converts the EGA color value
    to a VGA color value, which is then used as an argument to
    _remappalette(). Notice how fast the colors change using the remapping
    approach.

Remapping the Entire Palette

    The RINGS.C program (Listing 15-12 beginning on p. 532) demonstrated how
    to use the _remapallpalette() function to reassign the 16 default colors
    to different palette values. Now let's use the function with all 64 EGA
    colors.

    First, we must initialize a 64-element array to the VGA color values for
    the EGA colors and then initialize a 16-element array to the color values
    for a particular palette. Thus, the 64-element array supplies color values
    for the palette array, and we can use the palette array with
    _remapallpalette() to remap the palette.

    To see how this works, let's apply the approach to our MOIRE.C program;
    the resulting program is REMOIRE.C (Listing 15-17). After it draws a
    pattern, the program begins remapping the colors. Press any key to stop
    the remapping; press any key (except Esc) again to restart the program. To
    terminate the program, press the Esc key while the program is paused. The
    screen shown on the back cover of this book is a sample of the moire
    pattern produced by this program.

    ──────────────────────────────────────────────────────────────────────────
    /* remoire.c -- adds palette remapping to moire.c      */
    #include <conio.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <graph.h>
    #define ESC '\033'
    #define MAXCOLORS 64
    #define PALCOLORS 16
    long Ega_to_vga(int);

    main (argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        unsigned int col, row;
        long colors[MAXCOLORS];
        long palette[PALCOLORS];
        int index;
        int shift = 1;
        int firstcol, firstrow, lastrow, lastcol;
        int mode = _ERESCOLOR;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            printf("Can't do that mode.\n");
            exit(1);
            }
        /* Create array of all 64 color values. */
        for (index = 0; index < MAXCOLORS; index++)
            colors[index] = Ega_to_vga(index);
        /* Create array of 16 palette choices. */
        for (index = 0; index < PALCOLORS; index++)
            palette[index] = colors[index];
        _remapallpalette(palette);
        _getvideoconfig(&vc);
        firstcol = vc.numxpixels / 5;
        firstrow = vc.numypixels / 5;
        lastcol = 4 * vc.numxpixels / 5;
        lastrow = 4 * vc.numypixels / 5;

        for (col = firstcol; col <= lastcol; ++col)
            {
            for (row = firstrow; row <= lastrow; ++row)
                {
                _setcolor(((row * row + col * col) / 10)
                            % vc.numcolors);
                _setpixel(col, row);
                }
            }
        _settextposition(1, 1);
        _outtext("Press a key to stop or start.");
        _settextposition(2, 1);
        _outtext("Press Esc while paused to quit.");
        do
            {
            while (!kbhit())
                {
                /*  Set palette array to new color values. */
                for (index = 1; index < PALCOLORS; index++)
                    palette[index] = (colors[(index + shift)
                                    % MAXCOLORS]);
                _remapallpalette(palette);
                shift++;
                }
            getch();  /* pause until key is pressed */
            } while (getch() != ESC);

        _setvideomode(_DEFAULTMODE);  /* reset orig. mode */
    }
    long Ega_to_vga(egacolor)
    int egacolor;       /* ega color value */
    {
        static long vgavals[6] = {0x2A0000L, 0x002A00L, 0x00002AL,
                                0x150000L, 0x001500L, 0x000015L};
        long vgacolor = 0L; /* vga color value */
        int bit;

        for (bit = 0; bit < 6; bit++)
            vgacolor += ((egacolor >> bit) &1) * vgavals[bit];
        return (vgacolor);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-17.  The REMOIRE.C program.

    The following code initializes the arrays:

    /* Create array of all 64 color values. */
    for (index = 0; index < MAXCOLORS; index++)
        colors[index] = Ega_to_vga(index);
    /* Create array of 16 palette choices. */
    for (index = 0; index < PALCOLORS; index++)
        palette[index] = colors[index];

    The first loop initializes colors[] to the VGA color values. The second
    loop sets palette[] to the first 16 EGA colors. Note that this is not the
    default EGA palette. (See Table 15-6 on p. 530.)

    The following code reassigns the colors to palette[]:

    do
        {
        while (!kbhit())
            {
            /*  Set palette array to new color values. */
            for (index = 1; index < PALCOLORS; index++)
                palette[index] = (colors[(index + shift) % MAXCOLORS]);
            _remapallpalette(palette);
            shift++;
            }
        getch();  /* pause until key is pressed */
        } while (getch() != ESC);

    Because shift is initialized to 1, the first pass through this loop sets
    the palette array to the second through seventeenth members of colors[].
    When shift is incremented, the next pass moves the palette one element
    further into colors[].

    This example showcases the speed of remapping compared to the time the
    program originally took to color the screen.


VGA Graphics

    Now let's look at some programs that exclusively use VGA features. The VGA
    supports all EGA modes while providing three additional modes. Modes 17
    and 18 offer even a higher resolution (640 by 480 pixels) than does EGA
    mode 16. Mode 17 uses a 2-color palette, and mode 18 uses a 16-color
    palette. Mode 19 uses only medium resolution (320 by 200 pixels) but
    offers a 256-color palette. Furthermore, you can select the colors used in
    these modes from 262,144 color values. By choosing a suitable palette, you
    can construct images with much more realistic shadings of color than you
    can get from the CGA or EGA palettes. (The MCGA supports only mode 19 of
    these three.)

    To use these modes, you need the proper video display controller (VGA,
    MCGA, or a clone) and the proper monitor. Unlike the CGA and EGA adapters,
    which control display colors with digital signals, the VGA and MCGA use
    analog signals. Thus, they cannot be used with CGA or EGA display
    monitors. (The popular multisync monitors, however, can handle both
    digital and analog signals and can be used with all these adapters. Some
    automatically switch between digital and analog modes; others require you
    to manually set a switch.)

The 256-Color Palette

    The 256-color palette makes mode 19 the most interesting of the new VGA
    modes, and because both the MCGA and the VGA support it, mode 19 is also
    the most general. The COL256.C program (Listing 15-18) displays the
    256-color default palette. It draws a rectangular border and then uses
    _moveto() and _lineto() to divide the screen into a 16-by-16 array of
    rectangles. Finally, the program uses _floodfill() to display all 256
    colors. The expression:

    _setcolor(row * ROWS + col);

    in the final nested for loop sets the palette value (the argument to
    _setcolor()) to 0 through 255 in turn──the full range of palette values in
    this mode.

    ──────────────────────────────────────────────────────────────────────────
    /* col256.c  -- shows 256 colors in mode 19             */
    /* If you load graphics.qlb, no program list is needed. */

    #include <stdio.h>
    #include <graph.h>
    #include <conio.h>
    #define ESC '\033'
    #define ROWS 16
    #define COLS 16
    main()
    {
        struct videoconfig vc;
        int mode = _MRES256COLOR;
        short xmax, ymax;           /* screen size */
        short xcs[ROWS][COLS];      /* coordinates of the */
        short ycs[ROWS][COLS];      /* 256 rectangles     */
        short row, col;

        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "%d mode not supported\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);

        xmax = vc.numxpixels - 1;
        ymax = vc.numypixels - 1;

        /* Compute an interior point for each rectangle. */
        for (col = 0; col < COLS; col++)
            for (row = 0; row < ROWS; row++)
                {
                xcs[row][col] =  col * xmax / COLS + 5;
                ycs[row][col] =  row * ymax / ROWS + 5;
                }

        /* draw outside boundary */
        _setcolor(1);
        _rectangle(_GBORDER, 0, 0, xmax, ymax);

        /* draw gridwork */
        for (col = 1; col < COLS ; col++)
            {
            _moveto(col * (xmax + 1) / COLS, 0);
            _lineto(col * (xmax + 1) / COLS, ymax);
            }
        for (row = 1; row < ROWS;  row++)
            {
            _moveto(0, row * (ymax + 1) / ROWS);
            _lineto(xmax, row * (ymax + 1) / ROWS);
            }

        /*  fill in rectangles with palette colors */
        for (col = 0; col < COLS; col++)
            for (row = 0; row < ROWS; row++)
                {
                _setcolor(row * ROWS + col);
                _floodfill(xcs[row][col], ycs[row][col], 1);
                }

        /* terminate program */
        getch();
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-18.  The COL256.C program.

Changing the Palette

    You can use the _remappalette() and _remapallpalette() functions to change
    the palette settings. Of course, you must use the VGA color value system
    to do so. Recall that the color value is represented by a 4-byte number.
    The low byte represents the red intensity level and can have any value
    from 0 through 63. Similarly, the next byte describes 64 levels of green
    intensity, and the third byte describes 64 levels of blue intensity.
    Therefore, we can represent any of the 262,144 available colors by the
    following form:

    colorvalue = blue << 16 | green << 8 | red;

    The shift operators use type long values to place the intensity values in
    the correct bytes. For example, to generate a color that is "nearly" a
    blend of blue at half intensity, green at quarter intensity, and red at
    three-quarters intensity, use the following color value assignment:

    colorvalue = 32L << 16 | 16L << 8 | 48L

    We say "nearly" because, using 63 to represent full intensity, you can't
    exactly specify half, quarter, and three-quarter intensities with
    integers.

    The VGAMAP.C program (Listing 15-19) extends COL256.C to demonstrate the
    remapping techniques for mode 19. It starts by showing the default
    palette. Pressing a key initializes a 256-element array to a new palette.
    (This array is declared externally so that it won't use up stack space.)
    The first 64 elements are set to the 64 levels of blue; the second 64 are
    set to the green levels; and the third 64 elements are set to the red
    levels. The final 64 are set to some of the red-blue (magenta) blends.
    (With 64 choices for each, there are 64 x 64, or 4096, red-blue blends.)
    These illustrate the shadings possible by varying only one of the three
    color components. The program uses _remapallpalette() to reset the palette
    to the new color values in this array.

    When you press another key, the program uses rand() to generate a randomly
    placed rectangle. It then selects random blue, green, and red intensity
    levels, constructs a color value from them, and sets the rectangle to that
    color. This process continues until you press a key. Any key press (except
    the Esc key) now toggles the random remapping on and off. Press the Esc
    key to terminate the program. Note that _remappalette() uses a short
    argument for the palette value, palval, and a long argument for the color
    value, colval.

    A color intensity range of 64 levels makes extremely subtle color
    variations possible. The ALLVGA.C program (Listing 15-20, beginning on p.
    548) modifies the ALLCOLOR.C program (Listing 15-16, beginning on p. 539)
    so that you can investigate these color possibilities. Rather than using
    one key to step through the 262,144 values, we use one key to control the
    blue level, one to control the green level, and one to control the red
    level.

    ──────────────────────────────────────────────────────────────────────────
    /*  vgamap.c  -- remaps the vga mode 19 palette        */
    /*  Program list: vgamap.c (for rand())                */
    #include <stdio.h>
    #include <graph.h>
    #include <conio.h>
    #define ESC '\033'
    #define PALSIZE 256
    #define ROWS 16
    #define COLS 16
    #define MIDBLUE 0x190000L
    long newpal[PALSIZE]; /* array of color values */

    main()
    {
        struct videoconfig vc;
        int mode = _MRES256COLOR;
        short xmax, ymax;
        short xcs[ROWS][COLS];
        short ycs[ROWS][COLS];
        short row, col;
        long colorval;       /* VGA color value */
        long index;          /* looping index   */
        short palval;        /* palette value   */
        int c_base;  /* color base -- blue, green, or red */
        int ch;

        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "%d mode not supported\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        xmax = vc.numxpixels - 1;
        ymax = vc.numypixels - 1;
        for (col = 0; col < COLS; col++)
            for (row = 0; row < ROWS; row++)
                {
                xcs[row][col] =  col * xmax / COLS + 5;
                ycs[row][col] =  row * ymax / ROWS + 5;
                }
        _setcolor(1);
        _rectangle(_GBORDER, 0, 0, xmax, ymax);
        for (col = 1; col < COLS; col++)
            {
            _moveto(col * (xmax + 1) / COLS, 0);
            _lineto(col * (xmax + 1) / COLS, ymax);
            }
        for (row = 1; row < ROWS;  row++)
            {
            _moveto(0, row * (ymax + 1) / ROWS);
            _lineto(xmax, row * (ymax + 1) / ROWS);
            }
        for (col = 0; col < COLS; col++)
            for (row = 0; row < ROWS; row++)
                {
                _setcolor(row * ROWS + col);
                _floodfill(xcs[row][col], ycs[row][col], 1);
                }
        getch();

        /*  Initialize newpal[] to 64 shades of blue, 64
            shades of green, 64 shades of red, and 64 shades
            of magenta. */
        for (index = 0; index < 64; index++)
            {
            newpal[index] = index << 16;
            newpal[index + 64] = index << 8;
            newpal[index + 128] = index;
            newpal[index + 192] = index | MIDBLUE;
            }
        _remapallpalette(newpal);
        getch();

        /* Set squares and colors randomly -- ESC
            terminates loop, and other keystrokes toggle
            it on and off. */
        do
            {
            while (!kbhit())
                {
                palval = rand() % PALSIZE;
                colorval = 0L;
                for (c_base = 0; c_base < 3; c_base++)
                    colorval += ((long) rand() % 64) <<
                                (c_base * 8);
                _remappalette (palval, colorval);
                }
            ch = getch();
            if (ch != ESC)
                ch = getch();
            } while (ch != ESC);
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-19.  The VGAMAP.C program.

    ──────────────────────────────────────────────────────────────────────────
    /* allvga.c -- shows _MRES256COLOR 256K colors         */
    /* If you load graphics.qlb, no program list is needed.*/

    #include <stdio.h>
    #include <stdlib.h>
    #include <graph.h>
    #include <conio.h>
    #define FULLBRIGHT 64
    #define ESC '\033'
    char label[2][7] = {"ACTIVE", "      "};

    main(argc, argv)
    int argc;
    char *argv[];
    {
        struct videoconfig vc;
        int mode = _MRES256COLOR;
        int xmax, ymax;
        static long colors[2] = {_BLUE, _RED};
        char left[11];
        char right [11];
        int lpos, rpos;
        char ch;
        unsigned long blue = _BLUE >> 16;
        unsigned long green = 0L;
        unsigned long red = 0L;
        long color;
        short palnum = 0;

        if (argc > 1)
            mode = atoi(argv[1]);
        if (_setvideomode(mode) == 0)
            {
            fprintf(stderr, "%d mode not supported\n", mode);
            exit(1);
            }
        _getvideoconfig(&vc);
        _setlogorg(vc.numxpixels / 2, vc.numypixels / 2);

        xmax = vc.numxpixels / 2 - 1;
        ymax = vc.numypixels / 2 - 1;
        lpos = vc.numxpixels / 32 - 5;
        rpos = lpos + vc.numxpixels / 16;
        _remappalette(2, _RED);
        _setcolor(1);
        _rectangle(_GFILLINTERIOR, -xmax, -ymax, 0, ymax);
        _setcolor(2);
        _rectangle(_GFILLINTERIOR, 1, -ymax, xmax, ymax);
        sprintf(left, " %6lxH ", colors[0]);
        sprintf(right, " %6lxH ", colors[1]);
        _settextcolor(6);
        _settextposition(1, 1);
        _outtext("Press Tab to toggle panels, Esc to quit.");
        _settextposition(2, 1);
        _outtext("B increases blue level, b decreases it. ");
        _settextposition(3, 1);
        _outtext("G and g control green, R and r red.     ");
        _settextposition(24, lpos);
        _outtext(left);
        _settextposition(24, rpos);
        _outtext(right);
        _settextposition(5, 7);
        _outtext(label[0]);
        _settextposition(5, 27);
        _outtext(label[1]);
        while ((ch = getch()) != ESC)
            {
            switch (ch)
                {
                case '\t': _settextposition(5, 27);
                            _outtext(label[palnum]);
                            palnum ^= 1;
                            blue = (colors[palnum] << 16) & 0x3F;
                            green = (colors[palnum] << 8) & 0x3F;
                            red = colors[palnum] & 0x3F;
                            _settextposition(5, 7);
                            _outtext(label[palnum]);

                            break;
                case 'B':  blue = (blue + 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;
                case 'b':  blue = (blue - 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;
                case 'G':  green = (green + 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;
                case 'g':  green = (green - 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;
                case 'R':  red = (red + 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;
                case 'r':  red = (red - 1) % FULLBRIGHT;
                            colors[palnum] = blue << 16 |
                                    green << 8 | red;
                            _remappalette(palnum + 1, colors[palnum]);

                            break;

                }
            sprintf(left, " %6lxH ", colors[0]);
            sprintf(right, " %6lxH ", colors[1]);
            _settextposition(24, lpos);
            _outtext(left);
            _settextposition(24, rpos);
            _outtext(right);
            }
        _setvideomode(_DEFAULTMODE);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 15-20.  The ALLVGA.C program.



────────────────────────────────────────────────────────────────────────────
Chapter 16  Debugging

    Ever since that fateful day in the 1940s when a moth flew into the back of
    a computer and caused a vacuum tube to short circuit, programmers have
    been beset by bugs. Most errors, however, are not ex machina; they are
    caused by programmers themselves.

    Myriad are the ways in which a programmer can err. One common type of
    program error can be classed as the misuse of symbols. For example, you
    can mistype words and operators, misemploy keywords, and jumble syntax.
    You detect most of these errors before a program ever runs. Logic errors,
    the second major class of errors, are more insidious. These often carry
    through to an operational program, producing mysterious behavior.

    Fortunately, the situation is not hopeless. The compiler helps detect many
    kinds of errors, and the QuickC debugger can actually help trace errors in
    logic. In this chapter, we try to increase your awareness of common types
    of errors so that you can develop practices that reduce the likelihood of
    going astray in the first place. Many of the errors we show might seem
    obvious because we present them as the central attractions in short
    programs. However, in the context of a large and complex program, these
    errors are much more difficult to notice.

    Debugging, or the finding of errors, can be a challenging, frustrating,
    rewarding, and time-consuming process. It requires a different mind-set
    than programming. Programming is an inventive, creative process. Although
    debugging can require creativity, it is primarily an investigative
    process. You must transform yourself from a designer into a sleuth.


Keyboard-Entry Errors

    With each line of code you enter from the keyboard, you run the risk of
    mistyping a word and creating an error. Fortunately, the compiler detects
    most of these errors.

    The MISIDENT.C program (Listing 16-1) is an error-laden program. When you
    compile it, QuickC returns the following message:

    Fatal error C1021: (1 of 1)
    bad preprocessor command 'defne'

    If you use QuickC's integrated environment, the compiler places the screen
    cursor on the line containing this error. If you run QuickC with QCL
    instead of QC, the compiler labels the error with a line number.

    What does this error message mean? When you compile a program, QuickC
    first runs a preprocessor to process the # statements. The preprocessor
    recognizes a limited set of directives, and #defne is not one of them.
    Note that compilation stops immediately──before it checks for program
    errors.

    To correct the error, replace #defne with #define and compile the program
    again. This time the compiler generates the error messages on the
    following page.

    ──────────────────────────────────────────────────────────────────────────
    /* misident.c -- careless typing              */

    #defne BIG 3
    main()
    {
        char ltr;
        integer num;

        num = 2 + BIG;
        lrt = 'a';
        printf("%c %d\n", ltr, num);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-1.  The MISIDENT.C program.

    error C2065: (1 of 4)
    'integer' undefined

    error C2146: (2 of 4)
    syntax error: missing ';' before identifier 'num'

    error C2065: (3 of 4)
    'num' : undefined

    error C2065: (4 of 4)
    'lrt' : undefined

    When QuickC finds several errors, it displays one message at a time, using
    the cursor to mark the applicable line. Shift-F3 advances one error
    message, and Shift-F4 backs up a message.

    This example illustrates one of the virtues of declaring variables.
    Because lrt was undeclared, the compiler quickly spots the "typo" as an
    error. It also shows how a simple error can produce several different
    error messages. The compiler fails to recognize integer as a type;
    therefore, it fails to understand that the statement is a type
    declaration. It detects a syntax error and views num as undefined.

Defensive Programming

    One way to reduce errors such as typing lrt for ltr is to use recognizable
    words as identifiers. If you scan through a long program, you can easily
    misread lrt for the intended ltr. But if you use a name such as letter,
    mistypings such as lerret or lettre are much more likely to catch your
    eye.

An Anomalous Example

    The compiler does not catch all typos. The BADSIGN.C program (Listing
    16-2) is an interesting example of this.

    ──────────────────────────────────────────────────────────────────────────
    /* badsign.c -- uncaught typo                 */

    main()
    {
        int i;
        int j = 1;

        for (i = 0; i < 10; i++)
            {
            j =+ 10;         /* transposed += */
            printf("%4d ", j);
            }
        printf("\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-2.  The BADSIGN.C program.

    This program is meant to print the numbers 1, 11, 21, 31, and so on by
    incrementing j by 10 during each loop cycle. Instead, it prints the
    following:

    10   10   10   10   10   10   10   10   10   10

    What happens is that the program uses =+ instead of +=. In the early days
    of C, the addition assignment operator was written =+, so at that time,
    the program would have run as intended. Later, after the switch was made
    from =+ to +=, this program might have run correctly but with a compiler
    warning message. If the compiler was not tolerant of anachronisms, it
    might have rejected the program altogether for using an unknown operator.

    QuickC runs it with no complaints about anachronisms or unknown
    operators──but the program produces unwanted results. The reason the
    program runs is that under the new ANSI standards for C, the + operator is
    recognized as a valid unary operator, so QuickC reads the assignment
    statement as follows:

    j = +10;

    That is, assign positive 10 to j. Under the old standard, the + could be
    used only as a binary operator to indicate addition. The moral here is:
    Don't be certain that the compiler will catch all mistypings.

    This particular error was easy to localize because j was printed every
    loop cycle. If j were not printed, finding the problem could be more
    difficult.

    One final point: C, unlike many popular languages, is case sensitive, so
    porter, Porter, and PORTER are three distinct identifiers. If you aren't
    used to this convention and QuickC rejects what you consider a valid
    identifier, check your usage of case.


Syntax Errors

    Syntax errors occur when you use valid symbols in an invalid manner.
    Because the compiler must recognize and enforce valid syntax, it will
    always catch these errors. However, it might not correctly interpret what
    you were trying to do.

Misuse of Operators

    The POWER.C program (Listing 16-3) might be written by a FORTRAN
    programmer who is accustomed to using the exponentiation operator of that
    language.

    Interestingly enough, QuickC doesn't complain that the FORTRAN
    exponentiation operator (**) is unknown. Instead, it returns the following
    error message:

    error C2100 : (1 of 1)
    illegal indirection

    As it happens, ** is also a valid operator in C; however, its C meaning is
    quite different than the FORTRAN meaning. In C, ** means "the value at the
    address pointed to by another address." Thus, it should be applied only to
    a quantity that has been declared to be a pointer to a pointer, and the
    number 3 fails to satisfy that requirement. The illegal indirection
    message indicates that the compiler thinks you used a pointer incorrectly.

    ──────────────────────────────────────────────────────────────────────────
    /* power.c -- attempt to raise to a power      */

    main()
    {
        int number;

        number = 10**3; /* raise 10 to 3rd power? */
        printf("%d\n", number);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-3.  The POWER.C program.

A Scrambled Operator

    In the CONDITN.C program (Listing 16-4), we reverse the order of the :
    and the ? in the conditional operator. The compiler finds the error, but
    the error message is not very illuminating:

    error C2143
    syntax error : missing ';' before ':'

    If you follow QuickC's analysis blindly and insert a semicolon before the
    colon, you receive the same error message when you compile again:

    error C2143
    syntax error : missing ';' before ':'

    The moral here is: The compiler is much better at detecting syntax errors
    than it is at figuring out exactly what went wrong. QuickC is reliable in
    locating a syntax error; however, you must take the analysis with a grain
    of salt. If the problem doesn't seem to be in the indicated line,
    carefully read its immediate neighbors.

    ──────────────────────────────────────────────────────────────────────────
    /* conditn.c -- attempt to use conditional op */

    main()
    {
        int n, m;

        n = 2;
        m = (n != 2) : 0 ? 1;  /* almost right */
        printf("%d\n", m);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-4.  The CONDITN.C program.

Another Anomalous Example

    The human capacity to err far exceeds the capabilities of compilers to
    respond helpfully. The DOWHILE.C program (Listing 16-5) provides an
    example.

    QuickC places the cursor on the VOOOM line and returns the following
    message:

    error C2061 : (1 of 1)
    syntax error: identifier 'printf'

    Apparently, the compiler doesn't recognize printf() as the name of a
    function. Because the compiler accepted the first printf() without
    complaint, this is a puzzling message. Indeed, not one of the lines near
    the reported error seems wrong.

    The error lies in our misuse of the do while loop. DOWHILE2.C (Listing
    16-6) shows the correct version of the command.

    ──────────────────────────────────────────────────────────────────────────
    /* dowhile.c -- misuse of do while loop     */

    main()
    {
        int i = 0;

        do while (i < 10)
            {
            printf("Happy Fourth of July!\n");
            i++;
            }
        printf("VOOOM\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-5.  The DOWHILE.C program.

    ──────────────────────────────────────────────────────────────────────────
    /* dowhile2.c -- ok use of do while loop     */

    main()
    {
        int i = 0;

        do
            {
            printf("Happy Fourth of July!\n");
            i++;
            } while (i < 10) ;
        printf("VOOOM\n");
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-6.  The DOWHILE2.C program.

    The first version had no obvious error near the marked line. But sometimes
    it is not enough to look at nearby lines. The compiler thinks in terms of
    statements, and the entire do while loop counts as a single statement. In
    that sense, the error was near the marked line──it was in the immediately
    preceding statement, which happened to cover several lines. The moral here
    is: If QuickC shows an error in a line following an extended statement and
    you don't see a mistake in the marked line, check the syntax of the entire
    preceding statement.

Macro Problems

    When you use macros, the meaning of an error message might not be obvious.
    The compiler preprocessor first replaces the macros with the corresponding
    code. Next, it tries to compile the program. Thus, these compiler error
    messages refer to the substituted code, not your original code. This can
    be confusing, especially if you are using a system macro with which you
    are not familiar. The following BADPUTC.C program (Listing 16-7) looks
    innocent enough:

    ──────────────────────────────────────────────────────────────────────────
    /* badputc.c -- misuses putc()                   */

    #include <stdio.h>
    main()
    {
        FILE *fp;
        int ch;

        if ((fp = fopen("junk", "w")) == NULL)
            exit(1);

        while ((ch = getchar()) != EOF)
            putc(fp, ch);
        fclose(fp);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-7.  The BADPUTC.C program.

    However, the compiler places the cursor on the putc() line and delivers
    the following messages:

    error C2036: (1 of 9)
    left of '->_cnt' must have a struct/union type

    error C2105: (2 of 9)
    '──' needs lvalue

    error C2036: (3 of 9)
    left of '->_ptr' must have a struct/union type

    error C2105: (4 of 9)
    '++' needs lvalue
    error C2100: (5 of 9)
    illegal indirection

    warning C4047: (6 of 9)
    'argument': different levels of indirection

    warning C4024: (7 of 9)
    '_flsbuf': different types: parameter 1

    warning C4047: (8 of 9)
    '=' : different levels of indirection

    warning C4024: (9 of 9)
    '_flsbuf': different types: parameter 2

    These comments don't seem to relate to our code because we don't use
    ->_cnt and so on. However, putc() is a macro defined in stdio.h, and we
    can use the /P option of the QCL compiler-linker to see what a file looks
    like after the preprocessor finishes with it.

    Here's the proper command line:

    qcl /P badput.c

    QuickC gives the processed file the same basename as the original, but it
    adds a .I extension. Listing 16-8 shows part of that processed file. We
    add comments to indicate the location of the getchar() and putc() macros.

    ──────────────────────────────────────────────────────────────────────────
    /* badputc.i -- preprocessed putfile.c          */
    /*  Not shown are all the stdio.h contents that */
    /*  come at the top of the file.                */

    main()
    {
        struct _iobuf *fp;
        int ch;
        if ((fp = fopen("junk", "w")) == 0)
            exit(1);

        /* original was while ((ch = getchar()) != EOF) */
        while ((ch = (--((&_iob[0]))->_cnt >= 0 ? 0xff &
            *((&_iob[0]))->_ptr++ : _filbuf((&_iob[0]))))
            != (-1))

        /* original was putch(fp, ch); */
            (--(ch)->_cnt >= 0 ? 0xff &
            (*(ch)->_ptr++ = (fp)) : _flsbuf((fp),(ch)));

        fclose(fp);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-8.  The BADPUTC.I file.

    ──────────────────────────────────────────────────────────────────────────
    ──────────────────────────────────────────────────────────────────────────
    The getc() Macro

    The previous getc() and putc() macros might be a little obscure. Let's
    take a closer look at one of them to see what it does. The following is
    the macro definition for getc():

    #define  getc(f)  (--(f)->_cnt >= 0 ? 0xff & *(f)->_ptr++ : _filbuf(f))

    This defines getc() as a conditional expression having the form A ? B : C.
    If A is nonzero, the entire expression has the value of the B expression;
    if A is zero, the entire expression has the value of the C expression. The
    first operand (A) for the conditional expression is as follows:

    --(f)->_cnt >= 0

    Loosely translated, this means, "Decrement the _cnt member of the
    structure pointed to by f, and check if the result is greater than 0."
    From the definition, f is the argument to getc() and is a pointer to type
    file. Type file, in turn, is defined in stdio.h as a structure:

    extern FILE {
        char *_ptr;
        int   _cnt;
        char *_base;
        char  _flag;
        char  _file;
        } _NEAR _CDECL _iob[];

    This structure describes the file and the I/O buffer in use. The _cnt
    member describes the number of characters left in the buffer. If the
    number is greater than zero, then a character remains to be read, and the
    entire expression has the value of operand B, or:

    0xff & *(f)->_ptr++

    The first part (0xff ) is a mask to limit the final value to a byte. The
    second part (*(f)->_ptr++) is the value pointed to by the _ptr member of
    the structure pointed to by f. The _ptr member points to the current
    location in the buffer. Thus, if _cnt indicates that a character remains
    to be read, getc() evaluates to the buffer character currently pointed to.
    The increment operator then moves the pointer to the next buffer location.
    If, however, no characters remain, the entire expression has the value of
    the final operand:

    _filbuf(f)

    This is a "hidden" C function (one that the compiler uses but that is not
    part of the public library of C functions); it copies characters from the
    file to the buffer, reinitializes _ptr to point to the beginning of the
    buffer, and resets _cnt to the number of characters in the buffer. It also
    returns the value of the first character in the buffer or of EOF if the
    end of file has been reached. Therefore, if the buffer is empty, you can
    call this function to refill it.
    ──────────────────────────────────────────────────────────────────────────

    Now that we see the actual code that is passed to the compiler, the
    message

    error C2036: (1 of 6)
    left of '->_cnt' must have a struct/union type

    makes sense. The code contains the following expression:

    --(ch)->_cnt

    Because the macro uses the -> operator with a pointer to a structure, this
    code suggests that ch should be a pointer to a structure. It isn't, so the
    compiler reports an error. However, note that fp is a pointer to a
    structure. If we switch fp with ch in the code, the code makes sense. And
    that's our mistake──we should have used putc(ch, fp) rather than putc(fp,
    ch).

    This example illustrates one of the dangers of macros: They offer no
    provision for function prototyping or for checking the types of arguments.
    The example also illustrates the usefulness of viewing a file's
    preprocessor listing.


Run-Time Errors

    The most difficult errors to detect are those that the compiler misses.
    These errors can be inadvertent, such as the =+ error, or they can arise
    from logical errors in the design of the program.

Function Argument Problems

    If you don't use function prototypes, C does not try to match the types
    you pass to the expected types. Problems here can produce odd results, as
    the SUMNUMS.C program (Listing 16-9) points out.

    After the program initializes a to 10.0 and b to 20.0, it passes them to
    sums() to be added. The final output is as follows:

    sum of 10.0 and 20.0 is 0

    Clearly, this is false. To see what went wrong, compile in the Debug mode
    and set watch variables for a and x. Select the Trace feature from the
    Debug menu and use the F8 key to step through the program. At first, the
    value of a is undefined, but after the declaration is executed, variable a
    contains the correct value of 10. However, when the sums() function is
    entered, x is set to 0. Therefore, the problem must occur in the passing
    of arguments.

    ──────────────────────────────────────────────────────────────────────────
    /* sumnums.c -- type mismatch in function arguments  */
    /*              No function prototyping              */

    int sums();

    main()
    {
        float a = 10.0;
        float b = 20.0;
        int c;

        c = sums(a, b);
        printf("sum of %.1f and %.1f is %d\n", a, b, c); ;
    }

    int sums(x, y)
    int x, y;
    {
        return (x + y);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-9.  The SUMNUMS.C program.

    Knowing how C passes arguments can provide insight into the nature of this
    problem. Consider the call sums(a, b). First, QuickC converts float types
    to double when passing them as arguments. On a PC, this means that a and b
    are now 8-byte quantities. Next, the arguments are put into a memory area
    called the stack. The last argument, b, is pushed onto the stack first.
    (See Figure 16-1 on the following page.)

    When the called function executes, it reads the values off the stack and
    assigns them to its formal parameters. It uses the declared types of the
    formal parameters to determine how many bytes to read. And this is where
    the problem arises. Because the formal parameters x and y are declared to
    be type int, the sums() function reads two bytes off the stack for the
    value of x and the next two bytes for the value of y. (It should read
    eight bytes for x and eight bytes for y.)

    The net result of this process is that the first two bytes of a are
    assigned to x and the next two bytes of a are assigned to y. This leaves
    four bytes of a and all eight bytes of b unread, as illustrated in Figure
    16-2 on p. 565. Consequently, it is not surprising that the function gets
    the wrong values from the stack.

            b is 4-byte float
            ┌────────┬────────┬────────┬────────┐
            │        │        │        │   20   │
            └────────┴────────┴────────┴────────┘

    b is converted to double
    ┌────────┬────────┬────────┬────────┬────────┬────────┬────────┬────────┐
    │        │        │        │        │        │        │        │  20    │
    └────────┴────────┴────────┴────────┴────────┴────────┴────────┴────────┘


                        ┌─┌───────┐
                        │ │  20   │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
    B is placed────────►│ │       │
    in stack            │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        ├─│  10   │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
    Then a goes────────►│ ├───────┤
    through the         │ │       │
    same process        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        │ ├───────┤
                        │ │       │
                        └─└───────┘

    Figure 16-1. Passing arguments by means of the stack.

                            ┌─┌───────┐
                            │ │  20   │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                        b────►│ │       │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
    Function call places     │ │       │
    double arguments on      │ ├───────┤
    stack                    ├─│  10   │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                            │ │       │
                            │ ├───────┤
                            │ │       │
                        a────►│ ├───────┤─┐
                            │ │       │ │
                            │ ├───────┤ ├───►y
                            │ │       │ │
                            │ ├───────┤─┤      Called function removes
                            │ │       │ │      values from stack
                            │ ├───────┤ ├───►x
                            │ │       │ │
                            └─└───────┘─┘

    Figure 16-2. Misaligned data.

Function Prototyping to the Rescue

    The SUMNUMS2.C program (Listing 16-10) shows what happens when we add
    function prototyping to the previous example.

    ──────────────────────────────────────────────────────────────────────────
    /* sumnums2.c -- type mismatch in function arguments */
    /*               Function prototyping                */

    int sums(int, int);
    main()
    {
        float a = 10.0;
        float b = 20.0;
        int c;

        c = sums(a, b);
        printf("sum of %.1f and %.1f is %d\n", a, b, c);
    }
    int sums(x, y)
    int x, y;
    {
        return (x + y);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-10.  The SUMNUMS2.C program.

    The program now generates the correct output:

    sum of 10.0 and 20.0 is 30

    With function prototyping, a and b are converted to type int before going
    onto the stack, and sums() can retrieve the correct values. (See Figure
    16-3.)

                            ┌─┌───────┐─┐
                            │ │  20   │ │
                        b──►│ ├───────┤ ├──►y
    Function call          │ │       │ │     Called function removes
    places int argument    ├─├───────┤─┤     int values from stack
    on stack               │ │  10   │ │
                        a──►│ ├───────┤ ├──►x
                            │ │       │ │
                            └─└───────┘─┘

    Figure 16-3. Passing arguments with function prototyping.

    This is a convenient feature, but it can lead to another kind of error by
    not alerting you to a type mismatch. After all, the fact that you are
    passing the wrong type of argument can indicate a programming error. Or
    you may lose part of the data when converting from float to int. To use
    function prototyping without having to worry about missed errors, you
    merely need to select a higher warning level.

Warning Levels

    QuickC issues both error messages and warnings. Errors are mistakes that
    prevent compilation. Warnings alert you to usages that might be wrong but
    which don't prevent compilation. QuickC maintains four warning levels.
    Level 0 informs you only of errors. Level 1, the default, displays warning
    messages for those conditions most likely to cause problems, such as
    mixing levels of indirection in a pointer expression. Level 2 displays
    more warning messages, adding situations that are not usually serious
    problems, such as failing to declare as void a function without a return
    value. Level 3 warns of everything, including such "nonerrors" as non-ANSI
    keywords and omitted function prototypes.

    To see the effect of these warning levels, compile SUMNUMS2.C using all
    four levels. Levels 0 and 1 produce no warnings. Level 2, however,
    produces the following list of warnings:

    warning C4051: (1 of 6)
    data conversion

    warning C4051: (2 of 6)
    data conversion

    warning C4051: (3 of 6)
    data conversion

    warning C4051: (4 of 6)
    data conversion

    warning C4016: (5 of 6)
    'printf' : no function return type, using 'int' as default

    warning C4035: (6 of 6)
    'main' : no return value

    The first two warnings refer to the declarations for a and b. The reason
    for the warning is that C floating-point constants are always type double,
    yet a and b are declared type float. Because float uses fewer bytes, the
    potential exists for a partial loss of data.

    The second two warnings refer to the two conversions that take place in
    the sums() function call as a result of function prototyping. Because the
    values change from float to int, there is the possibility of a partial
    loss of data.

    The fifth warning tells us that we haven't declared the return type for
    the printf() function. Fix this by including the stdio.h file.

    The final warning informs us that main() has no return value. Because we
    did not explicitly declare a type for main(), the compiler assumes that
    main() is type int. Therefore, it expects main() to have an integer return
    value. To avoid this warning, declare main() as type void or provide a
    return value at the end of the function.

    Warning level 3 offers two new warnings, numbers 1 and 7:

    warning C4103: (1 of 8)
    'main' : function definition used as prototype

    warning C4051: (2 of 8)
    data conversion

    warning C4051: (3 of 8)
    data conversion
    warning C4051: (4 of 8)
    data conversion

    warning C4051: (5 of 8)
    data conversion

    warning C4016: (6 of 8)
    'printf' : no function return type, using 'int' as default

    warning C4071: (7 of 8)
    'printf' : no function prototype given

    warning C4035: (8 of 8)
    'main' : no return value

    The two new warnings lament the lack of function prototypes for the main()
    and printf() functions. If you want to check for the presence of function
    prototypes, go to warning level 3. To see if prototype conversions risk
    losing data or precision, level 2 is sufficient.


Common Run-Time Errors

    Some programming errors arise from the design of the C language itself.
    After we discuss how to avoid such errors in simple contexts, we will
    demonstrate how to detect them in more complex contexts.

The Misleading If Statement

    We begin with an error that almost every C programmer has made more than
    once. The LINE_CNT.C program (Listing 16-11) counts lines of input. It
    reads each character to EOF, which is signaled by Ctrl-Z with the
    getchar() function. A newline character increments the line count (lines).

    ──────────────────────────────────────────────────────────────────────────
    /*  line_cnt.c -- an overly active line counter       */

    #include <stdio.h>
    main()
    {
        int ch;
        int lines = 0;

        while ((ch = getchar()) != EOF)
            if (ch = '\n')
                lines++;
        printf("There were %d lines\n", lines);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-11.  The LINE_CNT.C program.

    Following is a sample run:

    Cat <Enter>
    Hat <Enter>
    Bat <Enter>
    Ctrl-Z <Enter>
    There were 12 lines

    The problem is that we used the assignment operator (=) instead of the
    is-equal-to relational operator (==). Consider this statement:

    if (ch = '\n')
        lines++;

    This assigns the character \n to ch, giving it the numeric value of 10
    (the corresponding ASCII code). The entire expression ch = '\n' now has
    the value 10, which, being nonzero, is interpreted as being true. Thus,
    lines is incremented for every character, regardless of value. Because
    this is a legal construction, QuickC returns no syntax error message.

    Knowing about this potential error is not enough to protect you from
    occasionally making it, especially if you are a Pascal programmer
    accustomed to using = for comparison. It is not an eye-catching error.
    How, then, can we detect it in a large program listing?

    The telltale sign is an if branch or while loop that always executes, even
    when you think it shouldn't. The use of = instead of == isn't the only
    possible cause, but it is the first you should look for.

    The Debug facility can help find this type of bug. Suppose we didn't know
    what was wrong with Listing 16-11. We could select the Debug option, set
    a watch on ch, and use the c modifier (separated from ch by a comma) to
    display the value of ch as an ASCII character.

    As we first trace through the program, ch is undefined. After it is
    declared (but not initialized), it has a garbage value. Next, if we enter
    the word Cat as input, we see ch take the value C as we enter the if
    statement. When control goes to the lines++; statement, we see that ch now
    is '\n'. Therefore, ch is being assigned a new value, and that tells us to
    look for an incorrect assignment statement.

Examining Arrays, Part 1

    One of the most common tasks for a for loop is to process the elements of
    an array. The INDEXER.C program (Listing 16-12 on the following page) is
    a simple example that is meant to initialize three arrays, calculate the
    number of elements in the second array, and display its contents.

    ──────────────────────────────────────────────────────────────────────────
    /* indexer.c -- uses indexes to display an array     */

    #include <stdio.h>
    int code1[] = {2, 4, 6, 8};
    int code2[] = {1, 3, 7, 9};
    int code3[] = {5, 10, 15, 20};
    main()
    {
        int index;
        int size = (sizeof code2) / (sizeof (int));

        for (index = 1; index <= size; size++)
            printf("%3d ", code2[index]);
        putchar('\n');
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-12.  The INDEXER.C program.

    Instead, it prints the character 3 until you press Ctrl-Break.

    The reason for this failure is that the program increments size instead of
    incrementing the index variable. Thus, index remains at 1, while the
    comparison limit grows. (Eventually, the loop halts when size exceeds the
    maximum int value and becomes negative.) This is a C error that is
    inherent in the language. Most languages increment the loop variable for
    you, thus preventing you from making the mistake. C, as usual, chooses
    flexibility over the more restrictive but trouble-free approach.

    If you don't catch this error in the code, however, how can you spot it
    later? One way is to monitor the program as it runs. The QuickC Debug
    option provides a quick and simple method. If you have problems in a loop,
    set a watch on the loop index. Making index a watch variable and tracing
    through the above program soon reveals that index does not change, and
    that directs your attention to the update portion of the for control
    statement.

Examining Arrays, Part 2

    Fixing the above size error results in the INDEXER2.C program (Listing
    16-13). Running the revised program produces the following ouput:

    3   7   9   5

    Because the values should be 1, 3, 7, and 9, the program still fails. The
    new problem is the array index range. Array numbering starts with 0, not
    1. Thus, the correct limits for the loop are as follows:

    for (index = 0; index < size; index++)

    The original limits print the last three members of the array and the
    first element of the array that followed code2 in memory.

    ──────────────────────────────────────────────────────────────────────────
    /* indexer2.c -- uses indexes to display an array     */

    #include <stdio.h>
    int code1[] = {2, 4, 6, 8};
    int code2[] = {1, 3, 7, 9};
    int code3[] = {5, 10, 15, 20};
    main()
    {
        int index;
        int size = (sizeof code2) / (sizeof (int));
                    /* get number of elements in array */

        for (index = 1; index <= size; index++)
            printf("%3d ", code2[index]);
        putchar('\n');
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-13.  The INDEXER2.C program.

    The index limit error is easy to make, especially if you are accustomed to
    languages such as BASIC and FORTRAN, which start numbering from 1. This
    error is difficult to detect because C does not check for bounds errors.
    (Pascal gives a run-time error message when an index becomes too large or
    small; C doesn't.) You can use Debug to trace the array index, but that
    doesn't help if you forget the proper limits.

    You can prevent this error by habitually using a standard form for the for
    loop. For example, if an array has size elements, use either

    for (index = 0; index < size; index++) /* OK */

    or

    for (index = 0; index <= size - 1; index++) /* OK */

    but don't alternate between the two forms. If you do, you increase your
    chances of producing an incorrect hybrid form, such as the following:

    for (index = 0; index <= size; index++)  /* NO */

Mirror Words

    The BACKWARD.C program (Listing 16-14 on the following page) is designed
    to print a word in normal order and then reverse the order of the letters.

    A quick look at the program suggests that its output should be as follows:

    trap backwards is part

    And, indeed, the program prints that out. But it doesn't stop there. It
    keeps on printing. Some of the output is garbage, some appears to be words
    and phrases spelled backward. What went wrong?

    ──────────────────────────────────────────────────────────────────────────
    /* backward.c -- the backwards word displayer         */

    #include <stdio.h>
    #define SIZE 5
    char word[SIZE] = "trap";
    main()
    {
        unsigned int index;

        printf("%s backwards is ", word);
        for (index = SIZE - 2; index >= 0; index--)
            putchar(word[index]);
        putchar('\n');
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-14.  The BACKWARD.C program.

    The first things to check when a loop unexpectedly becomes infinite are
    the loop's control statements. In this case, they look reasonable:

    (index = SIZE - 2; index >= 0; index--)

    SIZE is the number of elements, so SIZE - 1 is the index of the
    terminating null character, and SIZE - 2 is the index of the final
    character in the word. Each loop decrements the index by 1, thus moving
    back a character. When index reaches 0, the loop should display the last
    character and stop. But it doesn't. In this loop, the obvious variable to
    examine is index. Compile the program in the Debug mode, set a watch on
    index, and trace through the program step by step. The value of index
    follows this sequence: 3, 2, 1, 0, 65535, 65534, and so on. The index
    variable never becomes less than 0, so the loop never ends. Why not?
    Because index is declared an unsigned int. Change the type to int, and the
    program works properly. What at first looked like a looping error turns
    out to be a type error.

    The moral here is: Be careful when comparing unsigned quantities to zero.
    For example, if index is unsigned, index >= 0 is always true, and index <
    0 is always false. But index <= 0 can be either true or false.

Operator Priorities

    Because C has so many operators, you might at first have difficulty in
    remembering all the operator priorities. Certainly, multiplication has a
    higher priority than addition, but how do the increment operator and the
    indirect value operators compare? Does *ps++ mean (*ps)++ (use the
    pointed-to value and then increment the value) or *(ps++) (use the
    pointed-to value and then increment the pointer)? The second choice is the
    correct one; if you think the first interpretation is correct, you won't
    get the results you expect.

    For another example of priorities, consider the binary shift operator used
    in the SHIFTADD.C program (Listing 16-15).

    ──────────────────────────────────────────────────────────────────────────
    /* shiftadd.c -- shifts and adds numbers             */

    main()
    {
        int x = 0x12;
        int y;

        y = x << 8 + 2;
        printf("y is 0x%x\n", y);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-15.  The SHIFTADD.C program.

    From appearances, this program should left-shift x 8 places and then add
    2. Because each hex digit represents four binary digits, a left-shift of 8
    in binary is a 2-digit shift left in hex, so x << 8 is 0x1200. Adding 2
    gives 0x1202. But when you run the program, it prints a value of 0x4800.
    Addition has a higher priority than shifting; therefore, QuickC interprets
    the code as: Add 2 to 8 and do a 10-bit left-shift.

    You can find this type of error by using the Debug mode: Trace the values
    as they are calculated and compare them to calculations you perform by
    hand or with the assert() macro we discuss later.

    The best way to solve the problem is to avoid it in the first place. If
    you're not sure of priorities, look them up. The QuickC Help menu provides
    quick access to this information. Another method of avoiding the problem
    is always to use parentheses to clarify your intent, as follows:

    (x << 8) + 2

Scanning Problems

    The IBMIQ.C program (Listing 16-16) reveals one of the most common C
    errors.

    ──────────────────────────────────────────────────────────────────────────
    /* ibmiq.c -- a short dialogue                        */

    #include <stdio.h>
    main()
    {
        char name[80];
        int iq;

        printf("Enter your first name: -> ");
        scanf("%s", name);
        printf("Enter your IQ: -> ");
        scanf("%d", iq);
        printf("Well, %s, my IQ is %d!", name, 2 * iq - 1);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-16.  The IBMIQ.C program.

    The following is a sample dialogue produced by the program:

    Enter your first name: -> Mortimer
    Enter your IQ: -> 88
    Well, Mortimer, my IQ is -1!
    run-time error R6001
    -null pointer assignment

    Notice that the computer claims an IQ of -1 rather than the 175 you might
    expect from glancing at the program. Also, QuickC does not issue the
    run-time error message until after the program ends. These errors occur
    because in the second scanf() call, the program uses iq rather than the
    correct &iq.

    Most programmers make this kind of error through carelessness. For
    example, here we just used name as an argument. And because name didn't
    need the & operator, it's easy to forget that iq does.

    Let's look at the mechanics of this error and at the error message itself.
    The following sequence of events occurs. First, iq starts with a value of
    0. (We know this because that's the value that produces -1 for the machine
    IQ. You can also use the Debug mode to verify the iq value.) The scanf()
    function interprets 0 as the address 0; that is, it thinks it received a
    pointer to NULL. It then places the number 88 at that address rather than
    in iq. (See Figure 16-4.) Therefore, iq remains 0, and the program
    calculates the machine IQ as -1. Then the program ends. However, the C
    null pointer never points to valid data. To be sure of this, QuickC sets
    aside a "null block" which is to be untouched. QuickC also includes
    postmortem code in each program that checks the null block to see if it
    has been altered. If it has, something is wrong, and QuickC displays the
    null pointer error message.

    Note that the postmortem code doesn't detect data written to non-null
    locations. For example, if we initialize iq to 200, the program still
    fails to work properly, but, because address 200 is outside the null
    block, QuickC returns no error message. Thus, in general, you might not
    detect this kind of error unless you notice suspect data in the output.
    Unfortunately, this error can also overwrite legitimate data.

    Function prototyping won't help here. If you try an error level of 2 or 3,
    the compiler still doesn't complain about using a nonpointer as an
    argument. Looking at the function prototype in stdio.h shows us why:

    int _CDECL scanf(const char *, ...);

    The scanf() function is one of those rare functions that take a variable
    number of arguments. The first argument is always a string, but the others
    can be different types. Therefore, the prototype doesn't limit the nature
    and number of the remaining arguments: It type-checks only the first
    argument.

    (The _CDECL macro is set to cdecl when Microsoft enhancements are in
    effect. This keyword ensures that C functions use C calling conventions
    even if the /Gc compiler flag is set. That flag enables the Pascal and
    FORTRAN calling sequence for programs using mixed-language modules.)

    scanf ("%s", &iq) is the same as scanf
    ("%s", 3400), so an input of 88 is
    placed in location 3400.                         ADDRESS
                                        ┌───────┐
                    scanf ("%s", &iq)────►│  88   │   3400
                                        ├───────┤
                                        │       │
                                        ├───────┤
                                        │       │
                                        ├───────┤
                                        │       │
                                        ├───────┤
                                        │       │
                                        └───────┘

    scanf ("%s", iq) is the same as scanf
    ("%s", 0), so an input of 88 is      ┌───────┐
    placed in location 0.                │       │
                                        ├───────┤
                                        │       │
                                        ├───────┤
                    scanf ("%s", iq)─────►│  88   │   0000
                                        └───────┘

    Figure 16-4. Entering forbidden territory.

References

    The BADREF.C program (Listing 16-17) provides an example of a more subtle
    pointer error.

    ──────────────────────────────────────────────────────────────────────────
    /* badref.c -- misuses a pointer                      */
    main()
    {
        char name[81];
        char *pt_ch;

        printf("Enter your first name: -> ");
        scanf("%s", name);
        *pt_ch = name[1];
        printf("The second letter of your name is %c\n",
                *pt_ch);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-17.  The BADREF.C program.

    The following is a sample run of the program:

    Enter your first name: -> Fillmore
    The second letter of your name is i
    run-time error R6001
    -null pointer assignment

    This program gives the correct answer, but it also produces the null
    pointer error message, which means that data has been written in the null
    block. The program uses two pointers to assign data. First, in the call

    scanf("%s", name);

    the name variable is a pointer. This variable isn't a pointer to the null
    block, however, because the compiler allocates the space for name.

    Now look at the following assignment:

    *pt_ch = name[1];

    This tells QuickC to copy the contents of name to the location pointed to
    by pt_ch. The program allocates space for the pointer pt_ch, but it never
    specifies where pt_ch points. Therefore, pt_ch becomes an "unreferenced"
    pointer──it doesn't refer to a known location. As in the last example,
    because pt_ch has an initial value of 0, the data is copied to the null
    block.

    The unreferenced pointer is an insidious error. If such a pointer is
    initialized to a value that doesn't point to the null block, the
    postmortem program will not detect it. However, there is an effective
    method for preventing this kind of error. When you compile a program,
    enable the "Pointer Check" feature in the Compile dialog box. This adds
    code to your program that checks to see if each pointer is referenced
    before it is used. Recompile BADREF.C using this option. Following is a
    sample run:

    Enter your first name: -> Fillmore

    run-time error R6012
    -Illegal Near pointer assignment

    This time, instead of generating incorrect results, the program halts when
    it attempts to use the unreferenced pointer. If you compiled in the Debug
    mode, when you return to the editing screen, the cursor marks the guilty
    line of code.

    Obviously, the Pointer Check mode makes for safer programs. Unfortunately,
    it slows program execution, thus sacrificing one of C's strengths.
    Typically, you use this mode only when you suspect pointer problems (such
    as when you receive a null pointer warning). Once you rectify the problem
    by initializing the pointer to point to previously allocated memory, turn
    off the pointer check and recompile.


Design Errors

    So far we have focused on the misuse of the C language. However, you can
    write programs that use nothing but valid C statements but that fail
    because of design errors. The DIGSUM.C program (Listing 16-18), for
    example, contains a design flaw.

    The program examines input. Characters that are less than 0 and greater
    than 9 are considered nondigits, and the program lists them as others; the
    program lists values within that range as digits. Following is a sample
    dialogue:

    2b
    4c
    Ctrl-Z
    digits = 6, others = 0

    The program counts all input, including the newlines, as digits. The
    problem lies in the following test condition:

    if (ch <= '0' && ch >= '9')

    The expression is always false because it asks if ch is simultaneously
    less than '0' and greater than '9', which is impossible. You must use the
    logical OR operator (||) to express the test condition properly.

    Other than its flaw in logic, this program contains no C errors, and
    QuickC compiles it with no objections. The error becomes apparent when you
    analyze the output.

    You can ferret out this kind of logic error (expressing a test condition
    incorrectly) by using Debug to trace the program step by step. When you
    see that the program takes the wrong branch each time, you know to inspect
    the test condition.

    ──────────────────────────────────────────────────────────────────────────
    /* digsum.c -- sums digits in input                   */
    #include <stdio.h>
    main()
    {
        int ch;
        int digits = 0;  /* number of digits in input */
        int others = 0;  /* number of nondigits in input  */

        while ((ch = getchar()) != EOF)
        if (ch <= '0' && ch >= '9')
                others++;
            else
                digits++;
        printf("digits = %d, others = %d", digits, others);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-18.  The DIGSUM.C program.

Small Programs

    Often, with small programs, you can locate errors merely by examining the
    code, especially if the behavior of the program suggests where to look. By
    becoming familiar with common errors and the effects they produce, you
    will learn to detect them quickly.

    If a simple inspection fails to turn up the error, don't spend a lot of
    time poring over the code. Instead, gather more evidence. First, trace the
    values of key variables as a program runs. This gives you an inside view
    of program execution and can help uncover clues that let you deduce what
    is wrong. Traditionally, C programmers do this by embedding printf()
    statements at key locations. However, you do not need to use this method──
    QuickC's Debug mode is a much quicker and more powerful technique.

    If your problem seems array related, check your use of indexes. If it's
    loop related, check your loop conditions, paying close attention to the
    starting and ending values.

    If pointers are involved, check to see if they are referenced properly.
    You should always assign a pointer an address before using it with the
    indirect value operator (*).

    Test conditions that combine two or more relationships are breeding
    grounds for errors, and you should put them high on your priority list of
    items to be checked.

Large Programs

    The difficulty of finding errors rapidly increases with program size. The
    Debug mode lets you watch the progress of several variables, but a large
    program can have so many things going on that Debug is of little help.
    Also, large programs often use complex interdependencies that make it
    difficult for you to visualize details.

    The key to debugging large programs is the same as the key to writing
    large programs──modularity. If you don't use a modular approach when you
    begin developing a large program, you greatly decrease your chances of
    debugging the result. Using the modular approach, you break a program into
    smaller, more manageable pieces; as a result, you can localize most
    problems with only a few debugging techniques.

    Stub Functions

    The "stub function" debugging technique is based on the top-down method of
    programming. You begin debugging by testing the highest level of program
    organization. Suppose that your main() function contains the overall
    organization of the program and calls upon other functions to handle the
    details. These second-level functions, in turn, might subdivide the work
    into further functions.

    With this method, you replace the second-level functions with simple,
    error-free routines called stub functions. For example, suppose one
    second-level function takes an array and an array size as arguments and
    performs a complex calculation.

    Replace it with the following stub function:

    void complex_calc(array, size)
    double array;
    int size;
    {
        printf("Function complex_calc was called with "
                "arguments %u and %d\n", array, size);
    }

    Do this with all second-level functions to concentrate on how main()
    works. Does it perform the proper sequence of steps? Does it pass the
    correct arguments? Use the Debug mode to trace the order in which the
    statements execute. If main() works properly, replace the stub functions
    with the originals one by one until a problem occurs. If necessary, you
    can use stubs within stubs.

    Drivers

    Another debugging method is the bottom-up approach. Here you start with
    the most basic functions. However, instead of testing them in the complex
    final environment, you use small programs called "drivers" to test the
    function.

    For convenience, design your driver so that it can feed a variety of
    values to the function to be tested. The MATHTEST.C program (Listing
    16-19) offers an example of such a driver.

    ──────────────────────────────────────────────────────────────────────────
    /* mathtest.c -- driver for do_math()                 */
    /* Program list: mathtest.c (to link math functions)  */

    #include <stdio.h>
    double do_math(double);
    main()
    {
        double input, result;

        printf("Enter a number: ");
        while (scanf("%lf", &input) == 1)
            {
            result = do_math(input);
            printf("input = %.2e, result = %.2e\n", input,
                    result);
            printf("Next number (q to quit): ");
            }
    }

    #include <math.h>
    double do_math(x)
    double x;
    {
        return (sin(x) * exp(-x));
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-19.  The MATHTEST.C program.

    The following is a sample run:

    Enter a number: 0
    input = 0.00e+000, result = 0.00e+000
    Next number (q to quit): 1
    input = 1.00e+000, result = 3.10e-001
    Next number (q to quit): -2p
    input = -2.00e+000, result = -6.72e+000
    Next number (q to quit): q

    Actually, any non-numeric input (not just q) causes scanf() to return a
    value other than 1 and thus terminates the loop.

    Notice that the driver function echoes the input value so that you know
    the do_math() function receives the intended value. That is good
    programming practice when using scanf() for input, because that function
    gives you two opportunities to make mistakes: You can omit the & operator
    or use the wrong format specifier. (For example, because input is a double
    value, we must use %lf for input rather than %f.)

    The assert() Routine

    The assert() macro is another tool for locating logic errors in a large
    program. Use this macro to test whether certain conditions are in fact
    true. The assert() macro takes an expression as an argument. If the
    expression is true, the program continues. If it is false, the program
    halts and prints a message identifying the file and line number of the
    incorrect assertion. The TESTER.C program (Listing 16-20) is a short
    example that illustrates how assert() works.

    ──────────────────────────────────────────────────────────────────────────
    /* tester.c -- demonstrates the assert() macro        */
    /* Program list: tester.c (to link math functions)    */

    #include <assert.h>
    #include <stdio.h>
    #include <math.h>
    main()
    {
        float s1 = 3.0;
        float s2 = 4.0;
        float sumsq;
        float hypot;

        sumsq = s1*s1 - s2*s2;
        assert(sumsq >= 0);
        hypot = sqrt(sumsq);
        printf("hypotenuse is %.2f\n", hypot);
    }
    ──────────────────────────────────────────────────────────────────────────

    Listing 16-20.  The TESTER.C program.

    The program calculates the hypotenuse of a right triangle by finding the
    square root of the sum of the squares of the remaining sides. We
    deliberately introduce an error into the calculation so that sumsq
    contains a negative value. Because sumsq must always be positive or zero,
    we specify that with the assert() statement. When you run the program, it
    halts at the asssert() statement and displays the following message:

    Assertion failed: sumsq >= 0, file c:\qc\tester.c, line 15

    By placing assert() statements at strategic locations in a large program,
    you can localize logic errors, which enables you either to find the error
    or to use Debug more productively.

    One convenient feature of assert() statements is that once you fix your
    mistakes, you can recompile and eliminate the statements from the compiled
    program without altering your source code. To do so, merely place the
    following definition in your program:

    #define DEBUG /* turns off assert() macros */

    This causes the assert() macro to be defined as a blank.

    A Final Word of Advice

    QuickC offers you many powerful tools for debugging your programs.
    However, the ultimate tool in debugging is your own mind. Become familiar
    with common programming errors and study how to detect or prevent them.
    Most importantly, write your programs in a structured, modular form so
    that you can easily trace or localize the errors.



────────────────────────────────────────────────────────────────────────────
Appendix A  Some Resources for C Programmers

    Much has been published on C for every level from beginner to expert. The
    following is a list of a few books and magazines that we have found to be
    useful and that can supplement and extend your grasp of the topics we have
    discussed in this book.


Fundamental and Comprehensive Books

    Costales, Bryan. C From A To Z. Englewood Cliffs, N.J.: Prentice─Hall,
    1985.
    Recommended by Allen Holub as "the best introduction to the language that
    [he] has seen." One of the few books used in the University of California
    at Berkeley's course on C.

    Kernighan, Brian W., and Dennis M. Ritchie. The C Programming Language.
    Englewood Cliffs, N.J.: Prentice─Hall, 1978.
    This classic book established the "K & R standard" for C, which is only
    now being superseded by the ANSI draft standard. The writing is terse but
    very clear, providing a synopsis of C language features and their typical
    use.

    LaFore, Robert / The Waite Group. Microsoft C Programming for the IBM.
    Indianapolis, Ind.: Howard W. Sams, 1987.
    This book is specific to the Microsoft C Compiler and the IBM PC. Its
    approach is example driven, with chapters on using C for graphics,
    telecommunications, and other applications.

    Waite, Mitchell, Stephen Prata, and Donald Martin / The Waite Group. C
    Primer Plus. Revised edition. Indianapolis, Ind.: Howard W. Sams, 1987.
    This new edition of the world's best-selling introductory primer on C is
    fully compatible with UNIX-based C and the Microsoft C Compiler. It covers
    the new ANSI standard and C++, a new dialect of C featuring object-
    oriented programming. Contains quizzes and exercises.


Books on Advanced Topics

    Chesley, Harry, and Mitchell Waite / The Waite Group. Supercharging C with
    Assembly Language. Reading, Mass.: Addison─Wesley, 1987.
    This book shows you how to determine which parts of a program would
    benefit from recoding in assembly language for the ultimate in speed and
    efficiency. Includes many specific code examples.

    Feuer, Alan. The C Puzzle Book. Englewood Cliffs, N.J.: Prentice─Hall,
    1982.
    This book poses tricky and interesting questions that challenge your
    understanding of the subtle points of C.

    Hansen, Augie. Proficient C. Redmond, Wash.: Microsoft Press, 1987.
    Insightful advice on solving real problems with C. Written for
    advanced-level and intermediate-level programmers.

    Jaeschke, Rex. Solutions in C. Reading, Mass.: Addison─Wesley, 1986.
    This book covers the finer points of C that programmers must master to
    avoid subtle pitfalls in the handling of pointers, structures, and other
    elements. For advanced programmers.

    Kernighan, Brian W., and P. J. Plauger. Software Tools. Reading, Mass.:
    Addison─Wesley, 1976.
    A classic and a cornerstone of modern program design. This book shows how
    to create versatile software tools that can be combined to solve
    programming problems. The language used, RATFOR, can easily be translated
    into C. If you are familiar with Pascal, you might want to try their later
    book, Software Tools in Pascal. Reading, Mass.: Addison─Wesley, 1981.

    Prata, Stephen / The Waite Group. Advanced C Primer ++. Indianapolis,
    Ind.: Howard W. Sams, 1986.
    This sequel to C Primer Plus provides a complete tutorial that covers
    advanced topics, such as I/O operations, memory management, and the use of
    assembly language with C. Focuses on the IBM PC family.


Periodicals

    Don't neglect the many programmer-oriented magazines that feature C
    projects. Among these publications are the following:

    Byte. Subscription Dept., P.O. Box 6807, Piscataway, NJ 08855-9940.
    This monthly magazine often has articles about the use of C for various
    applications and reviews of products of interest to C programmers. Source
    code available on its BIX electronic network.

    Computer Language. P.O. Box 11333, Des Moines, IA 50347- 1333.
    This monthly has many feature articles on C programming and reviews of
    commercial C libraries and other add-on products. Source code available on
    CompuServe and other bulletin boards.

    Dr. Dobb's Journal. P.O. Box 3713, Escondido, CA 92025-9843.
    Dr. Dobb's is the hacker's delight with the funny name. Material on C,
    assembly language, unusual programming tricks, and so forth. Source code
    available on CompuServe.

    PC Magazine. P.O. Box 51524, Boulder, CO 80321-1524.
    This IBM PC─specific magazine has a languages column that often features C
    programs. This monthly magazine also maintains a bulletin board offering
    many utilities and other programs.

    PC Tech Journal. P.O. Box 52077, Boulder, CO 80321-2077.
    This monthly is, as the name suggests, specific to the IBM PC and MS-DOS.
    Besides having material on C, it also offers much news and technical
    features about MS-DOS, OS/2, and other aspects of the PC programming
    environment.

    Programmer's Journal. P.O. Box 3000, Department EE, Denville, NJ 07834.
    This IBM PC─specific magazine often features C programs. Published
    bimonthly.



────────────────────────────────────────────────────────────────────────────
Appendix B  Built-in QuickC Functions

    If your program has only a single module, you can call any of the standard
    library functions without using a program list. These functions, listed
    alphabetically in the table on the next page, are part of the file QC.EXE
    and are loaded into memory when you start up QuickC. They comprise the
    built-in, or core, library functions for QuickC. Function names that begin
    with an underline character are non-ANSI functions developed by Microsoft.

    Note that this list updates the list published in the Microsoft QuickC
    Programmer's Guide by including a few changes made subsequent to the
    printing of that manual. See pp. 125─31 of the same manual for information
    about compiling multiple-module programs and using program lists.

    QuickC's Core Library Functions
    ──────────────────────────────────────────────────────────────────────────
    abort          _fmalloc       ltoa          segread        strnset
    access         _fmsize        malloc        setbuf         strpbrk
    atexit         fopen          memavl        setjmp         strrchr
    atof           fprintf        memccpy       setmode        strrev
    atoi           fputc          memchr        setvbuf        strset
    atol           fputs          memcmp        signal         strspn
    bdos           fread          _memmax       sopen          strstr
    brk            free           memmove       spawnl         _strtime
    calloc         _freect        memset        spawnle        strtok
    chdir          fscanf         mkdir         spawnlp        strupr
    chmod          fseek          movedata      spawnlpe       system
    clearerr       fstat          _msize        spawnv         tell
    close          ftell          _nfree        spawnvpe       time
    cputs          fwrite         _nmalloc      sprintf        tmpfile
    creat          getch          _nmsize       sscanf         tmpnam
    dosexterr      getche         onexit        stackavail     tolower
    _dos_read      getcwd         open          strcat         toupper
    _dos_write     _getdate       printf        strchr         tzset
    eof            getenv         putch         strcmp         ultoa
    _exit          gets           puts          strcmpi        ungetc
    exit           gettime        raise         strcpy         unlink
    _expand        int86          read          strcspn        vfprintf
    fclose         int86x         realloc       _strdate       vprintf
    fflush         intdosx        remove        strdup         vsprintf
    _ffree         isatty         rewind        stricmp        write
    fgets          itoa           rmdir         strlwr
    filelength     kbhit          rmtmp         strncat
    flushall       longjmp        sbrk          strncmp
                    lseek          scanf         strncpy
    ──────────────────────────────────────────────────────────────────────────



    Mitchell Waite is President of the Waite Group, a San Francisco,
    California-based developer of technical and computer books. He is also an
    experienced programmer, fluent in a variety of computer languages. Waite
    is a coauthor of MICROSOFT MACINATIONS, published by Microsoft Press, and
    of UNIX Primer Plus and C Primer Plus, published by Howard W. Sams.

    Stephen Prata, Ph.D., is Professor of Physics and Astronomy at the College
    of Marin in Kentfield, California, where he teaches C and UNIX. Prata is
    coauthor of several Waite Group books, including UNIX Primer Plus, C
    Primer Plus, and UNIX System V Primer, all published by Howard W. Sams.

    Bryan Costales is Senior Systems Programmer at EEG Systems Laboratory. He
    is the author of C From A to Z, from Prentice-Hall (Simon & Schuster), and
    coauthor of UNIX Communications, published by Howard W. Sams.

    Harry Henderson is a freelance technical writer and editor. He has edited
    and contributed to computer books for the Waite Group, Blackwell
    Scientific, and Wadsworth. In addition, he is the editor for Tricks of the
    UNIX Masters and The UNIX Bible, Waite Group books published by Howard W.
    Sams.



────────────────────────────────────────────────────────────────────────────
Index


Symbols

! (reverse operator) 113, 138
" " (quotation marks)
    preprocessor macros and 379
    search current directory 101
#. See preprocessor, C/QuickC, directives
# (stringizing operator) 379
% (modulus operator) 75, 144, 168. See also format specifiers
%= (get division remainder, assign) 86 (table)
%[range] directive 272
& (address operator) 58, 82, 225─26, 233, 340, 343, 356─57, 580
& (bitwise AND operator) 218, 220─21
&& (AND operator) 91─92, 144, 373
'\0'. See string(s)
( ) (operator precedence) 77
* (multiplication) 75, 85, 238
* (pointer declaration) 226─27, 261, 578
*= (multiply, assign) 86 (table), 103
+ (addition) 75, 85, 237, 556
++ (increment operator) 87, 95, 109, 236
+= (add, assign) 86 (table), 556
- (subtraction) 75, 85, 237
-- (decrement operator) 87, 257─58
-= (subtract, assign) 86 (table)
-> (pointer to structures, unions) 341, 346, 357, 562
. (dot operator with structures, unions) 334, 353, 357
.. (return to parent directory) 38
/ (division) 75, 85, 238
/= (divide, assign) 86
/* */ (comment lines) 39, 54
: (conditional assignment statements) 129, 557
: (goto labels) 141
; (semicolon) 51, 557
    avoid in #define directives 105
    in for loops 97, 101, 102
    in functions 150
    in if statements 124
    in Pascal 52
    preprocessor macros and 378─79
    with switch statements 132
< (relational operator) 89
<< (bitwise unary left-shift operator) 218, 222, 223─24
< > (angle bracket for include files) 99
= (assign values to variables) 59, 85, 335
    vs relational == 90, 569
== (relational operator) 89
    vs assignment = 90, 569
> (relational operator) 89
>> (bitwise unary right-shift operator) 218, 223─24
>> (redirection) 386
? (conditional assignment statement) 129─31, 557
@ (linking to files) 386
[] (array offsets) 193, 198, 226
^ (ASCII value char) format specifier 73 (table)
^ (bitwise exclusive OR operator) 218, 222, 460, 468
^ (first character in a range) 272─73
{} (main function statements) 40, 51
    in for loops 94, 96
    with functions 151, 162
    with if statements 123─24
| (bitwise OR operator) 218, 221, 310, 460, 471
|| (OR operator) 91─92, 373, 577
    structure members 332
~ (bitwise unary inversion operator) 218, 222, 223, 516

A
\a (alert) 67, 68, 151, 266
ACME.C program 275─77
Adapt() function 497
addition 75, 85, 237
address(es) 225─26. See also pointer(s)
    assigning, to a pointer 227
    functions that return 243─47
    operator (&) 58, 82, 225, 233
    string 266─67
    type casting pointers and 241
Add Watch (Debug menu) 119, 120
ALERT.C program 163
ALLCOLOR.C program 538, 539─40, 546
ALLVGA.C program 546, 548─51
American National Standards Institute (ANSI), C standards 4─5, 14
    function declarations and 151
    signed/unsigned variables 60
AND operator (&&) 91─92
    bitwise 218, 220─21
ANIMATE.C program 110, 111
animation
    in CGA graphics mode 524─28
    using while loops for character 110─11
ANSI.SYS, keyboard control with 413─20
argc argument to main() 284─86
argv argument to main() 284─86
arithmetic operators 75─82
    assignment shortcuts using 85, 86 (table)
    with mixed data types 78─81
    on pointers 237─38
    precedence of 76─78
    type casting before evaluating 81─82
array(s) 189─224
    advancing offsets of 205
    bounds checking 195─98
    debugging 569─71
    declaring 191─92, 206, 213
    dynamic 247─52
    functions and 201─4, 209─10, 215
    initializing 198─201, 207─9, 214, 264
    interchangeability of * amts and amts [] 239─40
    large and huge 217
    lvalue/rvalue vs names of 240
    memory storage of 190
    negative subscripting of 216─17
    passing pieces of 217
    of pointers 253─55
    pointers and 233─37
    of pointers to structures 346, 347
    referencing and using array items 193─95
    strings and 280─84
    of structures 343─46
    three-dimensional (or more) 212─15
    two-dimensional 206─12, 281
ARRAY1.C program 192
ARROW.C program 517─18
arrow keys, using with graphics in text mode 462
ASCII character(s) 64, 406─7. See also string(s)
    reading scan codes and 426─29
ASCII character set 103, 461
ASCII extended character set 104, 436, 461
ASGNKEY.C program 414, 415
ASIMOV.C program 199
aspect ratios, calculating 524
assembly language, interrupts and registers 422─23
assert() macro 580─81
assert.h file 22 (table)
assignment statements 59
    conditional ? 129─31, 557
    vs relational operators 90 (table)
    shortcut 85─86
atoi() function 500
ATTRIB.C program 457, 458─59
AUTOEXEC.BAT file 27─29, 33
automatic variables 155─57
auto storage class 157
AVGTEMP.C program 84, 85

B
\b (backspace) 266
Backspace key 403
BACKWARD.C program 571, 572
BADPUTC.C program 559, 560
BADPUTC.I file 560, 562
BADREF.C program 575─76
BADSIGN.C program 555─56
BASICA language 4, 15
BASIC language 15, 142
    array declarations in 191
    assignment vs relational = in 90 (table)
    vs C 3─5, 7, 10 (table), 11
    character and string input in, vs C 405
    control variables for loops in 95
    error handling in, vs C 326
    file access in, vs C 294
    keyboard control with, vs C 419
    LEFT$, MID$ and RIGHT$ 391
    string functions in, vs C 279─80
    variables used in 55, 155
beep() function 163, 166
BIFFRED.C program 268
binary file routine mode 292, 296
    for fopen() 297 (table)
    getch() and getche() in 402
    putch() in 410
BIOS (Basic Input/Output System)
    background for IBM 420─21
    cursor and screen control with 413 (table), 429─48
        character attributes 454─59
        library of C functions for 431─46
        in ROAMSCRN.C text program 446─48
        video input/output interrupts for 429─31
    graphics mode and 492─93, 499
    using QuickC to access 420─23
        int86() function 423─25
        interrupt 0x16 425─26
        operation of interrupts 421
        reading ASCII and scan codes 426─29
bios.h file 422
bit(s) 55, 56
    keyboard status 426 (table)
bit fields 361─65
BITOUT.C program 371─72
BITWISE.C program 218─20
bitwise operators 218─24, 238, 361, 523
    character attributes and 460
BLANK.C program 488
block input/output 300─303
BOX.C program 215
BREAK.C program 134, 135
breakpoint 120
break statement 132─38
BUBSORT.C program 202─3
BUG.C program 369, 370
BUGS.C program 116─21
Build Program command (Compile menu) 117, 383, 387
bytes 55, 56

C
C:\QC (base directory) 30
C:\TMP (current working directory) 319
calloc() function 248 (table), 250
CARD.C program 334─35
CARD2.C program 337, 338─39
CARD3.C program 341─43
case, upper/lower 58, 556
    conversion of 286─89
CCOPY.C program 298─99
CCOPY2.C program 303─4
CGA. See Color Graphics Adapter (CGA)
cgets() function 409, 411─13
CH2000.C program 474
CH2001.C program 474, 475
CHANGE.C program 235─36
CHANGE2.C program 238─39
character(s). See also ASCII character set; string(s)
    animating with while 110─11
    BASIC vs C input of strings and 405
    classification and transformation of 286─89
    for loop using 103
    graphics box produced by GRAPHBOX.C 107
    graphics character set in text mode 461─67
    input functions 403 (table), 404─5
    manipulation of, in RAM 473 (table)
    output functions 410─11
    reading/writing 435─38
    representation in text mode 451, 452, 453
character attributes 416
    BIOS video input/output interrupts and 429─31, 454─59
    bitwise operators and 460
    manipulation of, in RAM 473 (table)
    reading/writing to the screen 435─38
character boxes 452, 453
character constants (' ') 64
character escape sequences 67, 68
character generator 451
char data type 57, 64─65, 175, 205. See also unsigned char data type
    strings as arrays of 263, 264
CHARS.C program 64, 65
chdir() function 318 (table), 319
Check Keyboard Buffer interrupt 425─26
CHOOSE.C program 258, 259, 260
circle() function 159
C language. See Microsoft C language; QuickC/C language
Clearscr() function 435
_clearscreen() function 500
clock, system 528
close() function 312
COBOL language, assignment vs relational = 90 (table)
CodeView debugger 116
COL256.C program 544─45
color(s)
    in CGA graphics mode
        background 498, 501 (table)
        basics 500─501
        palette 498 (table), 501 (table)
    in EGA graphics mode
        access to all 534─41
        palette 529─34, 541─43
    values, for text 456 (table)
    VGA graphics mode palette 544─45
        changing 546─51
    video screen mixing 456
Color Graphics Adapter (CGA) 451
    eliminating direct memory access snow 488─89
    resolution of 452, 453 (table)
    screen memory with 242
    video controller for 451 (table)
    video mode using 144
Color Graphics Adapter (CGA) graphics mode 497─528
    animation with 524─28
    changing modes using 499─500
    color basics for 500─501
    drawing lines using 505─6
    drawing rectangles using 507─10
    EGA and VGA considerations for 505
    _ellipse() function with 510─12
    filling figures with _setfillmask() and _floodfill() using 512─16
    filling other shapes with 517─19
    Graphics Library for 499
    graphics palette and background with 498
    logical coordinates and 506─7
    physical coordinates and 501─4
    replicating images using 519─24
color.h include file 481, 483, 498
command interpreter 282, 284
Command Line 299
comment lines 39, 54
compilation, C/QuickC program 25, 26
    batch 21
    conditional 368, 369─74
    to .EXE file 40─41
    #pragma instructions in 375─76
    steps in 11
Compile command (Run menu) dialog box 41
compiler(s) 4─5, 20
    array size supplied by 200
    #pragma instructions to 375─76
    QC.EXE and QCL.EXE 21
conditional assignment statement 129─31, 557
CONDITN.C program 557
CONFIG.SYS file 27─29, 32─34
    expand environment space in 29
conio.h file 22 (table), 402, 409, 485
console input/output functions 409─13
    character output functions 410─11
    string input/output 411─13
const keyword 105, 192
Continue command (Run menu) 120
CONTINUE.C program 138, 139
continue statement 138─41
CONTROL.C program 269, 270
conventions and style 15─17
CONVERT.C program 83, 84
coordinates, graphics mode screen
    logical 506─7
    physical 501─4
countline() function 157
cprintf() function 409, 413
The C Programming Language 4
cputs() function 411─13
    storage of an array read by 412
cscanf() 413
Ctrl-Break 97, 408
Ctrl-Z 292, 296, 402
ctype.h file 22 (table), 410
    character classification in 286, 287 (table)
    character transformation in 288 (table)
cube_root() function 175
Cursdn() 468
Cursdn_lim() 468
cursor
    movement 438─39
    setting 432─33
cursor and screen control 413 (table)
    with ANSI.SYS 415─20
    with BIOS calls 429─48
Cursrt() function 448
Cursrt_lim() function 438, 439, 448

D
data synchronization 309, 310
data types 55─66. See also bit fields; structure(s); variable(s)
    arithmetic with mixed 78─81
    char (see char data type)
    enum 358─60
    float 62─64
    format specifiers and 73 (see also format specifiers)
    help screen 67
    int (see int data type)
    long 61─62
    type casting 81─82
    union (see union data type)
    unsigned char 65
    using typedef 65─66, 365─66
    wrong/incompatible 115
DBLBAR.C program 152, 154
    flow control in 153
Debug command (Compile menu) 117, 118, 153
debugging 553─81. See also error(s), correction
    controlling from the keyboard 118
    with #define 369─70
    design errors 577─81
    keyboard entry errors 554─56
    loops and 115─21
    run-time errors 562─68 (see also run-time errors)
    syntax errors 556─62
        macro problems 559─62
        operator misuse 556─59
decisions and branching 123─46
    break statements 134─38
    complex branching conditions 142─46
    conditional assignment statement ? 129─31, 557
    continue statement 138─41
    goto statement 141─42
    if statement 123─28
    multipath branching 131─32
    switch statement 132─34
#define directive 368 (table)
    creation of array stack alias using 198
    debugging with 369─70
    macros 377
    strings 265
    vs typedef 365─66
    use in nested loops 104─7
    vs variables 105
defined keyword, and #ifdef directive 370─72
definition files. See include files
delay() function 164, 166
Delete All/Last Watch command (Debug menu) 120
device-independent programming 453─69
DIALOG.C program 274─75
DIGSUM.C 577
direct.h file 22 (table), 318─19
direct memory access (DMA) 469─80
compatibility of, increasing 474─75
example 472─74
graphics modes and 499
manipulation of characters and attributes in 473 (table)
segmented memory and 469─72
        near and far pointers 471─72
        segments and offsets 470, 471
    storing/displaying a screen using 476─80
directories 20─26, 318─21
    base, and subdirectories 20─21, 30
    library functions which handle 318 (table)
    return to parent (..) 38
    search current 101
    typical structure of 25
DIRX.C program 320
display. See also screen(s); video monitor(s)
    40-by-25 435
    80-by-25 424, 435─36, 451
Display() function 497
division 75, 85, 238
DMA. See direct memory access (DMA)
DO.C program 113
do loop 113─15
    vs Pascal's repeat until 114
do_math() function 580
do_move() function 186
dos.h file 22 (table), 422, 423
DOS Shell (File menu) 42
DOTS.C program 502, 503─4
    modify to create moire patterns 506
    modify to speed up 505─6
    program notes for 504
double data type 57, 63, 64, 72, 81, 174
double words 55, 56
DOWHILE.C program 558
DOWHILE2.C program 558, 559
Draw_char() function 465, 467
Draw_planes() function 215
driver programs 579─80

E
edit mode 39
Edit Program List command (File menu) 99, 100, 382
EGA. See Enhanced Graphics Adapter (EGA)
egacolor.h 536
Ega_to_vga() function 538, 541
EGGS.C program 510, 511, 512
#elif directive 368 (table), 373
    #else and 372
_ellipse() function 510─12
else and if statements 126─28
    matching 128
#else directive 368 (table)
    #elif and 372
#endif directive 368 (table), 373
    and #if 369─70
Enhanced Graphics Adapter (EGA) 505, 528─43
    accessing EGA colors with VGA code 534─41
        automatic color value conversion 538
        defining nonpalette colors 535─37
        EGA bit to VGA byte conversion 535 (table)
        example 538─41
    BIOS routines to control 455
    palette of 529─34
        color values for 531
        default values for 530 (table)
        four intensities for blue using 531
        remapping with _remapallpalette() 541
        setting the 529─34
        specifying color values of 529
    screen memory and 242
    text modes using 489─90
Enter() function 341
Enter key 274
enum data type 358─60
envp argument to main() 285
ERR.C program 374
errno.h file 22 (table)
errno variable 324, 326
error(s)
    advanced handling of, in files 325─29
    correction 44, 45 (see also debugging)
    detection 300
    error message warning levels 566─68
    philosophies of handling, BASIC vs C 326
error() function 150, 151, 152
escape sequences for printf() 67─70
event-driven programming 419
examine() function 177, 178, 179
.EXE files 24, 25
    compiling to 40─41
expo() function 169─71, 175, 178
EXPO.C program 169, 170
expressions, in statements 51
EXTERNAL.C program 159─60
external variables 158─60
extern keyword 246, 390

F
\f (formfeed) 266
factorial() function 173─74, 178
factorial expression, calculation of 172, 173
far pointers 242─43
    in direct memory access 471─72
fclose() function 297
fcntl.h file 22 (table), 310
    values for oflag declared in 310 (table)
feof() function 300, 301
ferror() function 300, 326
fgetc() function 295─97, 310
fgetpos() function 294
fgets() function 297─99
FIELDS.C program 74, 75
field width specifiers for printf() 74─75
file(s) 20─26, 38, 291─329. See also .EXE files; header files; include
        files; make files; map files; program list files
    advanced error handling with 325─29
    block input/output for 300─303
    changing source to object 25, 26
    closing 297, 312
    determining position in 308─9
    dividing programs into smaller 381
    error detection with 300
    finding current position in 318
    formatted input/output of 305
    line input/output of 297─99
    mid-level unbuffered input/output of 309─18
    moving to the beginning of 308
    opening 293─94, 310─11
    pointers 303─4
    positioning 315─17
    random access to 305─8
    reading 295─97, 314─15
    three levels of file input/output 291, 292
    top-level buffered input/output 292─309
    video input/ouput functions as 439─46
    writing to 312─13
FILE.C file 382
file descriptor 310, 312
File menu 37
filenames 321─24
file pointers 303─4, 312
file system 318─25
    directories 318─21
    manipulating files by name 321─24
    printing clear diagnostics 324─25
fill mask 512, 513
float data type 57, 62, 72, 81, 205
    function argument problems using 178, 563─64
    unions and 352, 356, 357
float.h file 22 (table)
floating-point arithmetic 31, 32, 204
floating-point data types 62─64
FLOATS.C program 62, 63
_floodfill() function
    filling figures with 512─16
    filling other shapes with 517─19
floppy-disk systems, setting up 32─34
    vs hard-disk 34 (table)
FMENU.C program 322─24, 325
fonts, EGA/VGA vs MDA/CGA 489
fopen() function 293─94, 296, 318
    possible modes/activities for 293 (table)
    text vs binary mode for 296, 297 (table)
for loop 94─107
    breaking out of, with Ctrl-Break 97
    character processing with 103
    combining with while loops 112─13
    control variable in 95
    multiple initializations/calculations in 101─3
    multistatement 97─101
    nesting 104─7
    style 96─97
FORLOOP.C program 94, 95, 96
FORMATS.C program 73
format specifiers
    %c 65, 73
    %d 60, 65, 69, 70, 72, 73, 83, 177
    %e 63, 72, 73
    %f 63, 72, 73, 81, 84, 355, 580
    %ld 72, 73
    %lf 580
    %s 69, 264
    %x 72
    ^ (ASCII value char) 73
    compatibility of data types and 73 (table)
    specifying with printf() 71─72
FORTRAN language 95, 142
    assignment vs relational = 90 (table)
fputs() function 297─99
fread() function 300─303, 309, 478
free() function 248 (table), 249, 250
fscanf() function 305
fseek() function 294, 305─8
    origin positions for 305 (table)
fsetpos() function 294
ftell() function 308─9
ftime() function 528
funct() function 328
function(s) and function calls. See also library functions
    addresses returned by 243─47
    advantage/disadvantage of 153
    arrays and 201─4, 209─10, 215
    calling itself (see recursion)
    calling user-defined 152─53
    core library 52, 587─88
    debugging argument problems in 562─65
    declaring 150─51
    definition of 50─53, 150─53
    external variables and 158─60
    information-returning 168─71
    interrupt-accessing 422 (table)
    large program using 180─86
    local and automatic variables and 154─57
    many parameters on 167─68
    multiple user-written 164─66
    noninteger 175─76
    passing information to, with parameters 161─66
    passing pointers to 230─33
    passing structures to 337─39
    pointers to 258─60
    program design and 147─50
    prototypes for 177─80, 565─66
    recursion of 171─75
    register variables and 160─61
    static variables and 157─58
    stub 578─79
    unions and 355, 356
    of video control registers 487 (table)
    video I/O interrupt 0x10, library using 430─31 (table), 431─46
function libraries 5, 9─10
    of video input/output interrupt functions 431─46
fwrite() function 300─303, 478

G
GALAX.C program 145─46
General help 42─43
getc() macro 561
getch() function 400─401, 425, 447, 448
    console input/output with 409, 410
    vs getchar() and getche() 403 (table)
    reading extended scan code with 406, 407
    typical input uses for 403─5
getchar() function 378, 400─401
    buffer 402
    vs getche() and getch() 403 (table)
    typical input uses for 403─5
Get Character interrupt 425
GETCHAR.C program 400, 401
GETCH.C program 400, 401
getche() function 112, 113, 137, 168, 175, 400─401
    console input/output with 409
    vs getchar() and getch() 403 (table)
    reading extended scan code 406
    typical input uses for 403─5
GETCHE.C program 400, 401
GETCLOSE.C program 180─84
    main() function in 186
    modifying 186
    overview of game created by 184─85
Getcurs() function 432─33, 438
getcwd function 318 (table), 319─20
_getimage() function 519
    for animation 524
Get Keyboard Status interrupt 426
getmesg() function 420
Getpage() function 433, 434, 480
GETPUT.C program 520─23, 524
gets() function 274─75, 298, 402
_getvideoconfig() function 494─97, 500, 504
Getvmode() function 474, 475
getyn() function 175─76
GETYN.C program 176
global variables 154, 155
    structures 351─52
Go_left() function 260
goto statement 141─42
GRAFCHAR.C program 462, 463
    drawchar.c module 465─66
    drawing with 467
    initstuf.c module 463, 464─65
    program details/limitations of 467─69
grafchar.h file 462, 463, 476, 477
GRAPHBOX.C program 104─5, 106, 107
graph.h file 22 (table), 142, 143, 493
    mode constants from 494
    _putimage() action verbs from 519, 520 (table)
    video configuration information from 495
graphic(s), text mode
    compatibility 460─61
    programming with the graphics character set 461─67
GRAPHICS.LIB. See Graphics Library
Graphics Library 23, 31, 142
    graphics modes and 493─97, 499
graphics mode and video monitors 491─551
    available 491, 492 (table)
    BIOS and 492─93, 499
    CGA 497─528
    EGA 528─43
    Graphics Library and 493─97
    VGA 544─51

H
hard-disk systems, setting up 30─31, 34
HARDWARE.C program 66
hardware manipulation, C/QuickC 4─5, 7, 8
hardware requirements 14─15
header files 21─23, 387─90
    dependencies in 390
    variables in 389─90
HELLO.C program 264, 265
help 42─44
    general screens 42, 43
        arithmetic operator precedence 77
        data type 67
    topic 42, 43
        keyword 44
        library function 98
HELP.C program 481─82, 484
HEXOUT.C program 204, 205
hot keys 36 (table)
huge keyword 217
HyperTalk language 11

I
IBMIQ.C program 573, 574─75
IF.C program 124, 125
#ifdef directive 368 (table)
    defined keyword 370─72
#if directive 368 (table)
    #endif and 369─70
    logical operators and 373
    nesting and indenting 373
IFELSE.C program 127, 128
#ifndef directive 368 (table)
if statement 123─28, 405
    debugging misleading 568─69
    else adjunct to 126─28
    flowchart 125
    matching else to 128
    nested 126
    switch vs if-else 137─38
    using groups of statements with 126
    vs while 125─26
_imagesize() function 519
INCDEC.C program 87
Include command (View menu) 98
#include directive 99, 368 (table)
include files 5, 9─10, 21, 22 (table)
    compiling/linking with 25, 26
    including in programs 99
    putting user functions in 166
    video input/output functions in 439, 440
increment/decrement operators 87, 95, 109
    pre-increment vs post-increment 87─88
    used with pointers in an array 236─37
    used with pointers to pointers 257, 258
INDEXER.C program 569, 570
INDEXER2.C program 570, 571
indirection operator 228
INFLATE.C program 102, 103
inp() function, reading ports with 485─87
input/output
    console 409─13
    input with scanf() 82─85, 271─73
    keyboard 400─405, 554─56
    mid-level unbuffered 309─18
    strings 271─80
    text lines, with gets() and puts() 274─75
    top-level buffered file 292─309
    video input/output interrupts 429─46
INPUT statement (BASIC) 405
int86() function 422, 423─25, 426
int data type 57, 60─61, 175, 361─62, 375
    array of 190
    union and 352, 355, 356, 357
integer constant expression 191, 206, 213, 247
interpreted languages 11─12
interrupts
    assembly language, registers and 422─23
    functions which access 422 (table)
    int86 function 423─25
    interrupt 0x10 functions 430─31 (table)
    interrupt 0x16 (keyboard I/O) 425─26
    operation of 421
    reading ASCII and scan codes with 426─29
    software 422
INTVARS.C program 60, 61
INVERT.C program 288─89
io.h file 22 (table), 322

K
kbhit() function 109─10, 425
Kernighan, Brian W. 4
keyboard 399─429
    accessing the BIOS to control 420─29
    ANSI.SYS control of 413─20
    console input/output functions 409─13
    controlling debugging from 118
    input errors 554─56
    input functions 400─405
    processor and scan codes 406─9
    reading non-ASCII keys 406─9
    shortcuts 36 (table)
keyboard buffer 406
keyboard macro 419, 420
keyboard status bits 426 (table)
KEYS.C file 381
    modifying to use with header file 388, 389
keys.h 408, 409, 447, 462
Keyword help 44

L
L2WORDS.C program 282─83, 284
label 141, 240
large programs 379─95
    design problems with 578─81
    header files and 387─90
    keeping track of changes in 387
    libraries and 391─94
    program lists and 380─83, 383─87
    Quick Libraries for 394─95
leftstr() function 280
LEFTSTR.C subroutine 392, 394
libraries 23─24, 25, 26. See also function libraries; Graphics Library;
        Quick Libraries
    standard 31
library files (.LIB) 391─94
library functions. See also function(s) and function calls
    abnormal condition handlers and diagnostic routines 325 (table)
    core 52, 587─88
    directory handling 318 (table)
    filename manipulation 321 (table)
    help window 98
    memory allocation 248─52
    string manipulation 279 (table)
    using in for loops 97─98
    for video input/output interrupt 431─46
limits.h 22 (table)
line(s), drawing in CGA graphics mode 505─6
    mask 510
    styles 508, 510
line() function 152─53, 154
    parameters for 161, 162, 167─68
Line2words() function 282
LINE_CNT.C program 568, 569
#line directive 368 (table)
line input/output, with fgets() and fputs() 297─99
LINES.C program 167, 168
LINES43.C program 489, 490
_lineto() function 168, 505, 506, 517, 544
linked lists 346─51
LINK program 21, 385─87, 394
LOCAL.C program 155, 156
local variables 154─57
locking.h file 22 (table)
logical operators 91─92. See also AND operator (&&); OR operator (||)
LOGO language 11
    assignment vs relational = 90 (table)
long data type 61─62, 375
longjmp() function 325 (table), 328, 329
loops. See repetition and looping
lseek() function 315─17
lvalue 240, 268

M
machine code optimization 7, 161
Macintosh computer and C 4
macros
    debugging 559─62
    preprocessor 377─79
MAGIC.C program 207─8
main() function 39─40, 567
    arguments to 284─86
    flow of execution begins with 53
    in GETCLOSE.C 186
    vs printf() 51─52
    program design and 147─50
    variables declared in 156─57
make files 13, 21, 24, 382
malloc() function 248─50, 252, 271, 320, 519
malloc.h file 22 (table), 247
map files 24
MASKS.C program 514─16
MATH.C program 75, 76
math.h file 22 (table), 99, 101, 169
MATHTEST.C program 579, 580
MCGA. See Multi-Color Graphics Array (MCGA)
M.C program 138, 139─40
memory 7, 55, 56
    direct access to (see direct memory access)
    dynamic allocation/reallocation of 247─52
    stack (see stack)
    storage of arrays in 190, 205, 206
    video (see video memory)
memory allocation library routines 248 (table), 248─52
memory.h file 22 (table)
memory models 7, 8, 23
    setting up 31, 32
menu bar 35
MENU.C program 416─18, 420
    operation of 419
microprocessors
    BIOS, interrupts and 421─23
    data types of different 63
    #pragma pack 376
    registers for 8086 family 160
Microsoft C language. See also QuickC/C language
    powerful capabilities of 7─10
    programming fundamentals (see
    programming in C and QuickC)
    QuickC vs 11─14
    reasons for learning 3─6
    standards 4─5, 14
    used with QuickC 30
Microsoft C Optimizing Compiler 14, 20. See also compilation, C/QuickC
        program; compiler(s)
Microsoft QuickC Language Reference 19─20
Microsoft QuickC Programmer's Guide 32, 36, 46, 79, 121, 325
Microsoft QuickC Run-Time Library Reference 19─20, 98, 275
MIDSTR.C subroutine 392
MISIDENT.C program 554
MIXED.C program 79, 80
MIXLOOPS.C program 112, 113
MIXTYPES.C program 177, 178
mkdir() function 318 (table), 319
mktemp() function 321 (table), 322
MODEINFO.C program 495, 496─97
modules C 5, 13
MOIRE.C program 506
monitors. See screen(s); video monitor(s)
Monochrome Display Adapter (MDA) 450
    attribute bits 454, 455 (table)
    resolution 452, 453
    video controller for 451 (table)
mouse 14
    selections with 36─37
mouse driver 37
_moveto() function 168, 505, 544
MS-DOS 13
    out of environment space 29
    escaping to 42
    interrupts and 421
    signals defined for 328 (table)
    variables, and C 27─29, 33
Multi-Color Graphics Array (MCGA) 450
    video controller 451 (table)
multiplication 75, 85, 238

N
\n (newline) 40, 67, 68, 266, 274, 402, 410
    in declarations of string constants 265
    vs \r (return) 70
name :bits 362
NARROW.C program 53
near pointers 471
Newchar[] 410
NEW-CONF.SYS 27─29, 32, 33
newline. See \n (newline)
NEW-VARS.BAT 27─29, 32, 33
Next_search() function 260
noninteger functions 175─76
numbers
    data types (see double data type; float data type; int data type; long
        data type)
    formatting with printf() 70─71
O
object (.OBJ) files 24, 384, 391, 394
    combined/linked (see .EXE files)
offsets, array 193
    advances in memory 205
    bounds checking 195─98
    consequences of referencing beyond arrays 196
ONELINE.C program 68
ON KEY (in BASIC) 419
open() function 310, 312, 318
Open command (File menu) 12, 37, 38
OPEQUAL.C program 86
operators
    arithmetic (see arithmetic operators)
    bitwise (see bitwise operators)
    increment/decrement (see increment/decrement operators)
    logical 91─92 (see also AND operator (&&); OR operator (||))
    misuse of 556─59
    priority errors with 572─73
    relational 89─91
optimization of machine code 7, 161
OR operator (||) 91─92, 577
    bitwise (|) 218, 221, 310, 460, 471
    bitwise exclusive (^), 218, 222, 460, 468
outp() function, writing to a port with 487─88
outtext() function 512

P
PACK.C program 376
pages/paging 429, 480─84
    getting/setting 433─34
parameters, function 161─66
    actual vs formal 162
    multiple 167─68
    multiple user-written 164─66
    operation of 163─64
    passing, in Pascal and C 164
Pascal language 15
    array declarations in 191
    assignment and relational = 90 (table)
    vs C 3─5, 7, 10 (table)
    comment lines in 54
    passing parameters in 164
    repeat until, vs C's do 114
    type checking in 178
    use of the semicolon (;) in 52
    variables defined in functions using 155
PASSWORD.C program 403, 404, 405
PATH command 28─29
PEEK and POKE (in BASIC) 7
PEEK.C program 241
perror() function 324─25
PHONE.C program 305, 306─8
PHWORD.C program 245─46
pixels 450
    generating random 142─46
    logical coordinates for 506, 507
    physical coordinates for 501, 502, 503─4
PIXELS.C program 142
    running 142─44
    screen coordinates and output 144
    variations of 145─46
pmode (permissions mode), values for 311 (table)
pointer(s) 7, 8, 164. See also address(es)
    accessing structure members with 341─43
    accessing variables with 228─30
    arithmetic for 237─39
    arrays and 233─37
    arrays of 253─55
    arrays of, to structures 346, 347
    assigning addresses to 227
    casting, to integers 242
    debugging errors resulting from use of 575─76
    defining/declaring 226─27
    direct memory access use of 469─74
    dynamic arrays and 247─52
    elements not addressed by 257
    far 242─43
    to functions 258─60
    initialized strings and 267─68
    interchangeability of *amts and amts[] 239─40
    lvalue vs rvalue 240
    passing, to functions 230─33
    passing, to structures 339─41
    to pointers 255─58
    type casting addresses and 241
    to unions 356─58
    value of 234
Pointer Check 237
POINTER.C program 228─29, 229─30
polling loop 109
port(s) 485─89
    eliminating CGA snow using 488─89
    reading, with inp() 485─87
    writing to, with outp() 487─88
portability
    of C/QuickC 4─5, 6
    text modes and 451─53
PORTINFO.C program 486
postmortem code 574
pow() function 169
POWER.C program 556, 557
#pragma directive 368 (table)
    #pragma pack 375, 376
PREPOST.C program 88
preprocessor, C/QuickC 368─79
    conditional compilation 368, 369─74
    directives 368 (table), 369─73
    #pragma instructions to the compiler 375─76
    preprocessor macros 377─79
Print_attr() function 457, 463
printf() function 40, 67─75, 558, 567
    escape sequences for 67─70
    field width specifiers for 74─75
    formatting numbers with 70─71
    formatting strings with 269─70
    relationship to main() 51─52
    specifying formats with 71─72 (see also format specifiers)
Print_row() function 217
Printval() function 356
process.h file 22 (table)
program list 13, 23, 142, 143, 380─83
    creating a 99─101
program list files 383─87
    dependency lines in 384─85
    other arguments to LINK 386─87
    production rules for 383─84
    running the linker with 385
    using LINK from a text file 386
programming environment, C/QuickC 12─13
programming in C and QuickC 37─41
    addresses (see address(es))
    arithmetic operators 75─82
    basic elements of 49─53
    comments in 39, 54
    compilation (see compilation, C/QuickC program)
    cursor (see cursor and screen control)
    data types and variable declaration 55─66
    debugging (see debugging; error(s), correction)
    decisions and branching (see decisions and branching)
    device-independent (see device-independent programming)
    functions (see function(s) and function calls; library functions)
    graphics (see graphics mode and video monitors)
    keyboard control (see keyboard)
    large programs (see large programs)
    logical operators 91─92
    pointers (see pointer(s))
    preprocessor (see preprocessor, C/QuickC)
    printf() function 67─75
    punctuation and spacing in 53
    relational operators in 89─91
    repetition and looping in (see repetition and looping)
    scanf() input 82─85
    shortcut assignment statements, increments, and decrements 85─88
    in text mode (see text mode and video monitors)
    written resources for 583─85
PROTO.C program 179, 180
prototypes, function 177─80, 565─66
punctuation in C/QuickC programs 53
putc() function 559, 560
putch() function 104, 410─11, 436, 448
putchar() function 410─11
_putimage() function 519─24
    for animation 524
puts() function 274─75, 298
    action verbs for 520 (table)

Q
QC.EXE program 21
QCHELLO.C program 39
    parts of 50
    running 40
    statements calling functions in 51, 52
    as typed into edit window 39
QCL.EXE program 21
QuickBASIC 15
QuickC/C language
    C language compared to Pascal and BASIC 3─10 (table), 15
    directories and files used by 20─26
    error correction 44─45 (see also debugging)
    getting help 42─44
    hardware requirements 14─15
    initial screen 12
    knowledge requirements 15
    programming fundamentals (see
    programming in C and QuickC)
    reasons for using 11─14
    setting up 29─34
    starting/learning to use, 34─42
    used with Microsoft C 30
    user manual vs this book 19─20
Quick Libraries 325, 394─95
Quit() function 258

R
\r (return) 266, 274, 402, 410
    vs \n (newline) 70
RACE.C program 524, 525─28
RAM, memory models of 7, 8, 23, 31, 32
rand() function 134, 168, 524, 528, 546
Range() function 244─47
read() function 309, 310, 314─15
Read Character and Attribute function 431 (table)
Read_ch_atr() function 436, 437
Read Cursor Position interrupt 430 (table)
Readkey() function 426, 427, 428
realloc() function 248 (table), 249, 250, 252
RECALL.C program 478, 479
RECEIPTS.C program 77, 78
_rectangle() function 507─10, 512
rectangles, drawing in CGA graphics mode 507─10
RECT.C program 508, 509, 510
RECURSE.C program 172, 173
recursion 171─75
    stack size and 174─75
    structure 346─51
register(s) 7, 8
    AX 423, 424
        AH 423, 425, 426, 430, 435
        AL 423, 425, 426, 434, 490
    BP 423
    BX 423
        BH 423, 434
        BL 423, 424
    CS 423, 470
    CX 423
    DI 423
    DS 423, 470
    DX 423
        DL 435
    ES 423
    functions of video control 487 (table)
    for Intel 8086 family 160─61
    interrupts, assembly language and 422─23
    ports and 485
    SI 423
    SP 423
    SS 423
register variables 160─61
REKEY.C program 410, 411
relational operators 89─91
    precedence shown by () 91
    relational == vs assignment = 90, 569
RELATION.C program 89, 90
_remapallpalette() function 529, 530, 541─43, 546
_remappalette() function 529─30, 538, 546
REMOIRE.C program 541─43
remove() function 321 (table)
rename() function 321 (table)
repetition and looping 93─121
    debugging and loops 115─21, 571─72
    do loop 113─15
    for loop 94─107
    incorrectly specified loops 115
    while loop 108─13
replication of images on screen 519─24
return. See \r (return)
return statement 168─71
return value 49, 171
REVERSE.C program 253, 254
REVERSE2.C program 256─57
rewind() function 308
Rewrite() function 437, 438, 448
RIGHTSTR.C subroutine 392─93
RINGS.C program 530, 532─34, 541
    output 532
Ritchie, Dennis M. 4
rmdir() function 318 (table), 319
ROAMSCRN.C program 446─47
"robust" programs 325
ROLO.C program 344─46
ROLO2.C program 348, 349─51
Run menu 40
run-time errors
    with arrays 569─71
    function argument problems 562─66
    mirror words 571─72
    misleading if statements 568─69
    operator priorities 572─73
    references 575─76
    scanning problems 573─75
    warning levels 566─68
rvalue 240, 268, 359

S
SADD.C program 195, 196
SADD2.C program 197, 198
Save As command (File menu) 40
Save command (File menu) 40
SAVEGRAF.C program 476─77
Save_screen() function 477─78
sbrk() function 248 (table), 250─52
scan code 406, 407
    debugging errors in 573─75
    example of 407, 408
    reading ASCII and 426─29
    values 408─9
SCANCODE.C program 408
scanf(), getting user input with 82─85, 271─73, 336, 402, 405, 486,
        574, 580
SCANLINE.C program 271, 272
SCAPE.C program 536─37
SCORE.C program 61, 62
    formatting numbers in 70
SCRANGE.C program 273
screen(s). See also video monitor(s)
    clearing 434─35
    console input/output functions to control 409─13
    control of cursor and (see cursor and screen control)
    coordinates
        logical 506─7
        physical 501─4
    initial QuickC 12
    manipulating text screen with far pointers 242─43
    modifying with bit fields 363─65
    overview 35─36
    storing and display 476─80
Screen Swapping On command (Debug menu) 118, 446, 467
SCRFUN.C program 439, 441─46
    functions included 441
    include file 440
SCRINV.C program 242, 243
SCRMENU.C program 363─65
scrn.h file 439, 440, 441, 447, 462, 474, 480
scroll bars 35
Scroll Up an Area of the Screen interrupt 431 (table)
SCRREST.C program 314─15
SCRSAVE.C program 312─13
search.h file 22 (table)
Select Active Display Page interrupt 430 (table)
Select Cursor Position interrupt 430 (table)
selections, making in QuickC 36─37
_selectpalette() function 505
    values for 500 (table)
_setbkcolor() function 501
_setcolor() function 168, 501, 502, 508, 529, 544
SET command (MS-DOS) 286
Setcurs() function 432, 433, 438, 477, 478
SETCURS.C program 433
Set Display Mode interrupt 430 (table)
_setfillmask() function 508
    filling figures with 512─16
setjmp() function 325 (table), 328, 329
setjmp.h file 22 (table)
_setlinestyle() function 508, 510
_setlogorg() function 506
setmode() function 402
Setpage() function 433, 434, 480
_setpixel() function 502─4, 505, 506
Set Program List command (File menu) 99, 382, 391
Set Runtime Options command (Run menu) 284, 295, 299
    dialog box 296
_settextcolor() function 512
_settextposition() 512
SETUP program 27─29
    with floppy-disk systems 32─34
    with hard-disk systems 30─31
_setvideomode() function 144, 493─94
Setvmode() function 492─93
share.h file 22 (table)
SHIFTADD.C program 572, 573
SHORTIF.C program 129, 130
SHOWARGS.C program 284, 285
SHOWARGS2.C program 285, 286
Showcard() function 337─39, 341
Show_change() function 239
SHOWCODE.C program 426, 427─28
showmenu() function 420
signal(s) 326─28
    analysis of 327
    defined for MS-DOS 328 (table)
signal() function 325 (table)
signal.h file 22 (table), 326, 327
sizeof operator 55, 56, 200
size_t bytes 248, 301
small programs, esign problems with 578
source code files 24
    to object 25, 26
spacing in C/QuickC programs 53
SPECS.C program 71, 72
sqrt() function 97─98
square() function 159, 230─32
SQUARE.C program 232─33
squares, calculating aspect ratios for 524
srand() 528
stack 153
    debugging problems with arguments
        passed to 563, 564, 565
    how parameters work on the 163, 164
    recursion and size of 174─75
standards. See American National Standards Institute (ANSI), C standards
Start command (Run menu) 40, 120
statements
    assignment (see assignment statements)
    break 134─38
    continue 138─41
    do 113─15
    expressions in 51
    for (see for loop)
    in function definitions 40, 51─53
    goto 141─42
    if 123─28
    multistatement for loops 97─103
    switch 132─34
    typedef 65─66
    while (see while loop)
stat.h file 22 (table)
    values for pmode in 311 (table)
STATIC.C program 157, 158
static variables 157─58
    structures initialized as 351─53
status line 36
stdarg.h file 22 (table)
stdaux file pointer 303 (table)
stddef.h file 22 (table)
stderr file pointer 303 (table), 305
stdin file pointer 303 (table)
stdio.h file 22 (table), 292, 301, 315, 322, 402, 406, 409
stdlib.h file 22 (table)
stdout file pointer 303 (table)
stdprn file pointer 303 (table)
strdup() function 278
string(s) 263─89
    arguments to main() 284─86
    arrays and 280─84
    character classification and transformation of 286─89
    console input/output of 411─13
    declaring and initializing 264─65
    formatting with printf() 269─70
    input (in BASIC) 405
    input/output for 271─75
    manipulation routines for 275─80
    pointers and initialized 267─68
    printing, with printf() 68─69
string.h file 22 (table)
string pool 266─67
STRINGS.C program 69, 295, 296
STRIO.C program 412
strlen() function 278─79
STRPOOL.C program 266, 267
struct keyword 332, 333
structure(s), 331, 332─51
    accessing members 334─35
    accessing members with a pointer 341─43
    arrays of 343─46
    arrays of pointers to 346
    assignment 336─37
    bit fields inside 363
    initializing, with starting values 351─52
    members (variables) defined/declared 332, 333
    passing, to functions 337─39
    pointers to 339─41
    recursion and linked lists 346─51
    shorthand declaration of 336
stub functions 578─79
subtraction 75, 85, 237
SUMNUMS.C program 562, 563
SUMNUMS2.C program 565, 566
sums() function 562, 567
SWITCH.C program 135, 136, 137
switch statement 132─33, 134
    vs if-else 137─38
    vs pointers to functions 259─60
syntax, C/QuickC 10
    error in 115
system code 406

T
\t (tab) 67, 68, 69─70, 266, 410
tab. See \t (tab)
TABLE.C program 99, 100, 101
TABS.C program 69, 70
tell() function 318
TEST.C program 391, 393
TESTER.C program 580─81
TEXED.C file 380
    with header file 388, 389
    keeping track of changes in 387
    program list files for 383─87
text color values 456 (table)
text file routine mode 292, 296
    for fopen() 297 (table)
    getchar() in 402
text mode and video monitors
    device-independent programming 453─69
    direct video memory access 469─80
    EGA and VGA 489─90
    graphics compatibility and 460─69
    paging in 480─84
    portability and 451─53
    ports 485─89
then keyword 124
three-dimensional (or more) arrays 212─15
    declaring 213
    initializing 214
    using in functions 215
time() function 114─15, 528
timeb.h file 22 (table)
time.h file 22 (table)
TIMER.C program 114
TIMER2.C program 164, 165─66
TINY.C program 49, 50
title bar 35
TODAY.C program 359, 360
tolower() macro 410
TOTAL.C program 249
TOTAL2.C program 251, 252
toupper() macro 288, 410
Trace On command (Debug menu) 117, 153
triangle() function 159
Triple() function 203
TRUTH.C program 91, 92
TTT.C program 209, 210─12
Turbo Pascal 4, 15
two-dimensional arrays 206─12
    functions and 209─10
    initializing 207─9
    strings 281
type casting 81─82
    pointers and addresses 241
type checking in Pascal and C 178
TYPE command 299
typedef statement
    advanced use of 365─66
    renaming data types with 65─66
types.h file 22 (table)

U
UDEMO.C program 353─55
ultime.h file 22 (table)
unary inversion (ones complement) operator (~) 218, 223, 238, 516
unary shift operators (>> <<) 218, 223─24
UNDOVER.C program 200, 201
union(s) 352─58
    functions and 355
    with int data type 355
    pointers to 356─58
    received by functions 356
    REGS 423, 424, 425, 426
    union data type 331, 352
UNIX System V, and C 4, 5, 14, 15, 326, 328
unlink() function 321 (table)
unsigned char data type 65, 205
unsigned int data type 60, 217
unsigned long data type 61, 301
UPPITY.C program 301─2, 303

V
values
    assigning to variables 59, 85
    assigning truth 129─31
    lvalue vs rvalue 240
    using #define to assign names to 104─7
    warning 171
VARADDRS.C program 57, 58, 82─83
varargs.h file 22 (table)
variable(s) 55─66. See also data types; statements
    accessing with pointers 228─30
    assignment statements 59
    automatic 155─57
    declaring 57─58
    vs #define 105
    external 158─60
    for loop control 95
    in header files 389─90
    initializing 59
    local 154─57
    naming rules for 58─59
    register 160─61
    scope of 154, 155
    shortcuts with 85─88
    static 157─58
    structure (see structure(s), members (variables) defined/declared)
    uninitialized 115
    watch 119, 120
VARSIZE.C program 55, 56, 57
VGA. See Video Graphics Array (VGA)
VGAMAP.C program 546, 547─48
video, reverse 436, 448
video controller 436, 449, 450, 451 (table), 491, 492 (table)
    ports to access registers on 452
    similarities 451─53
    summary of text mode differences 453 (table)
Video Graphics Array (VGA) 505, 544─51
    color code for accessing all EGA colors 534─41
        automatic color value conversion 538
        color value storage 535
        defining nonpalette colors 535─37
        EGA bit to VGA byte conversion 535 (table)
        example 538─41
    color values equivalent to EGA default palette 530 (table)
    palette (256-color) 544─51
    screen memory, accessing 242
    in text mode 489─90
    video controller 451 (table)
video input/output interrupts 429─31
    library of C functions that use 43─46
video memory 433, 435
    BIOS routines used to place bytes into 454─59
    direct access to 452, 469─80
    EGA 529
video mode, set 144
video monitor(s) 449. See also console input/output functions
    color and 456
    controllers and 450─51
    in graphics mode (see graphics mode and video monitors)
    in text mode (see text mode and video monitors)
VIEW.C program 316─17
W
Wait() function 528
WHATCHAR.C program 287─88
WHILE.C program 108, 109
while loop 108─13, 116
    animating characters with 110─11
    combining, with for loops 112─13
    comparing if statements with 125─26
    flowchart 109
    vs if statements 125─26
    using break to exit from 134─35
words 55, 56
write() function 309, 312─13
Write Character and Attribute interrupt 431 (table)
Write_chars() function 484
Write_ch_atr() function 436, 437, 448
writechr.c module 481, 482─83
Write_str() function 484

X
XENIX system 328
XMAS.C program 194, 195