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 ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄ¿ ÀÄÄÄÄij ³ Graphics ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ÀÄÄÄÄij ³ ³ Included ÄÅÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄij ³ ³ 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 (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() { } rather than function_name() { } 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 ³ ----------- ³ÄÄÄÄÄÄÄÄij (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:/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 ÚÄij ÄÄ¿ ³ ÄÄÄÄÄÄÄÄ ³ ± 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 . 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: . 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 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 /* 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 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 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 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(<ime); /* get system elapsed seconds */ /* since 1-1-70 */ end = start + seconds; /* calculate alarm time */ do {;} /* null statement for loop body */ while (time(<ime) < 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(<ime), 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(<ime) < 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() \ÄÄij continue ÃÄÄij 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. ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄij 't' ³ÄÄijprintf... ³ ³ ³ ³ ³break; ³ ³ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ ÚÄÄÄÄÄij 'h' ³ÄÄijprintf... ³ ³ ³ ³ ³ ³break; ³ Value of   ÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄ¿   ÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³  ÄÄÄÄÄÄÄÄÄÄÄÄij 'p' ³ÄÄijprintf... ³ switch ³ ( ch ) ³ *  ³ ³ ³break; ³ ³ ³  ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÙ  ÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄ¿   ÄÄÄÄÄÄ¿ ÀÄÄÄÄÄij 'w' ³ÄÄijprintf... ³ ³ ³ ³ ³break; ³ ³ ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÄÙ ³ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄ¿ ÀÄÄÄÄÄÄÄÄÄÄijdefault³ÄÄijprintf... ³ ³ ³ ³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 #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 /* 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 /* 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 /* for graphics */ #include /* for sqrt() */ #include /* 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 #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) ³ ³ } ³ ÄÄÄÄ ÀÄij 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(<ime); /* get system-elapsed seconds */ /* since 1-1-70 */ end = start + seconds; /* calculate alarm time */ do {;} /* null statement for loop body */ while (time(<ime) < 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 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 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 <': 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<> 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; 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 = # 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 = # ÄÄÄ¿ ³±±±±±±±±±± 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 = # *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 = # 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 /* 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 . 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 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄFor NULL #include ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ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 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 /* for NULL */ #include /* 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 /* 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 /* for NULL */ #include /* 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ÄÄÄij ³ÄÄÄÄÄÄÄÄÄThis is the text ÃÄÄÄÄÄ´ Pointer to second lineÄÄÄij ³ÄÄÄÄÄ¿ 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 /* for NULL */ #include /* 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("------------------\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 = #ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ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 /* for NULL */ #include /* 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("------------------\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ÄÄÄij ³ÄÄÄÄÄÄÄÄÄThis is the text ³ ÃÄÄÄÄÄ´ ³ --ppÄÄÄij ³ÄÄÄÄÄ¿ that we typed in. ³ ÃÄÄÄÄÄ´ ³ pp ³ --ppÄÄÄij ³ÄÄÄ¿ ÀÄÄÄÄÄIt will be reve ÚÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÄ´ ³ ³ ³ ÄÄ´ --ppÄÄÄij ³ÄÄ¿ÀÄÄÄÄÄrsed. Line 3 is ÀÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄ´ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Points to ³ --ppÄÄÄij ³ÄÄÄÄÄ¿ here. Line 4 is pointer ³ ÃÄÄÄÄÄ´ ÀÄÄÄÄÄÄÄÄÄÄÙ ³ --ppÄÄÄij ³ÄÄÄÄÄ¿ here. Line 5 is ³ ÃÄÄÄÄÄ´ ÀÄÄÄÄÄÄÄÄÄÄÙ ÀÄstartÄÄÄij ³ÄÄÄÄÄ¿ 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 to incorporate the definition of NULL. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ /* dialog.c -- a conversation using gets() and puts() */ #include /* 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 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 /* for NULL */ #include /* for strchr(), et al */ #define NAME_PATTERN \ "firstlast or\n\ firstmiddlelast" #define ADDRESS_PATTERN \ "numberstreetcity" char Buf[BUFSIZ]; /* global I/O buffer */ main() { char *ocp, *cp, *first, *last, *street, *city; void Prompt(), Cant(); printf("Acme Employment Questionnaire\n"); /* * Expect firstlast or * firstmiddlelast */ 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 numberstreetcity */ 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 (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 /* for NULL */ #include /* 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 /* for NULL */ #include /* 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). ÚÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ¿ ³ ³ÄÄÄÄÄij 'J' ³ 'o' ³ 'e' ³ '\O' ³ ÃÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ¿ ³ ³ÄÄÄÄÄij 'D' ³ 'u' ³ 'k' ³ 'e' ³ '\O' ³ ÃÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄÅÄÄÄÄÄÄÄ¿ ³ ³ÄÄÄÄÄij '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 /* 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 /* 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 /* 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 /* for NULL and BUFSIZ */ #include /* 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 /* for NULL */ #include /* 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 ³ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÚÄÄÄÄÄÄÄÄijTop-level ³ ³ ³ ³functions like ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³fopen() and fgetc() ³ ³ ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ Your ³ ³ Disk ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ program ³ ³ ³ ³Mid-level ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³functions like ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄijopen() and read() ³ÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³Low-level ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄijfunctions 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 . 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 ... FILE *fp; Remember, always use #include 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 ... 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 /* for FILE, BUFSIZ, & EOF */ #include /* 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 #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 /* for FILE, BUFSIZ, NULL */ #include /* 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 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 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 /* for strrchr() */ #include /* for NULL */ #include /* for malloc() */ #include /* 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. 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 /* for FILE, BUFSIZ, NULL */ #include /* 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 /* 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 ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ÃÄÄÄÄÄÄij 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 (not , as you would with fopen()), as follows: #include ... 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 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄFor oflag values #include ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄFor stat.h #include ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄFor pmode values Note that #include must always precede #include 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 #include #include 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 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 /* for stderr */ #include /* for O_CREAT | O_BINARY */ #include /* for stat.h */ #include /* 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 /* for stderr */ #include /* 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 /* defines lseek() */ #include /* 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 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 /* for open() */ #include /* 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<>\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 , 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 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 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 #include /* 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 #include /* 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 #include #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 /* defines mktemp() */ #include /* for NULL */ static char template[] = "C:\\TMP\\XXXXXX"; if (mktemp(template) == NULL) { /* No unique name possible */ } First we specify #include 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 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 #include #include #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 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 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 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 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 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 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 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 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 /* for NULL and stdin */ #include /* 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 /* for NULL and stdin */ #include /* 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 ³ÄÄÄÄÄÄÄÄÄÄÄij *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 /* for NULL and stdin */ #include /* 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 ith 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 /* for NULL and stdin */ #include /* 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\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." ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ Úij first [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ last [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ middle [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÙ ³ ³street_num ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄ¿ ³ ³ street [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ city [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ state [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÙ ³ ³ zip ³ ³ ÃÄÄÄÄÄÄÂÄÄÄÄÙ ³ ³area ³ ÚÄÄÄÄÂÄÄÄÄ¿ ³ ÃÄÄÄÄÄÄÁÄÄÄÄ¿ ³ address ³ÄÄÄÙ ³ phone ³ ÃÄÄÄÄÅÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄ¿ ³ address ³ÄÄÄÄij first [] ³ ÃÄÄÄÄÅÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ address ³ÄÄÄ¿ ³ last [] ³ ÀÄÄÄÄÁÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ cardps [3] ³ ³ middle [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÙ ³ ³street_num ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄ¿ ³ ³ street [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ city [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ state [] ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÙ ³ ³ zip ³ ³ ÃÄÄÄÄÄÄÂÄÄÄÄÙ ³ ³area ³ ³ ÃÄÄÄÄÄÄÁÄÄÄÄ¿ ³ ³ phone ³ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄ¿ Àij 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. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ Úij ³ Úij ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ÃÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ³ ÃÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÙ ÃÄÄÄÄÂÄÄÄÄÙ ³ ÃÄÄÄÄÂÄÄÄÄÙ ³ ÃÄÄÄÄÂÄÄÄÄÙ ÃÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ÃÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄ¿ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 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 /* for NULL and stdin */ #include /* for strdup() */ #include /* 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 (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 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) ÄÄÄÄij 7 ³ ³ ÚÄÃÄ Ä´ ³ ³ ³ 6 ³ ³ ³ ÃÄ Ä´ ³ Background (3 bits) ÄÄÄ´ ³ 5 ³ ³ ³ ÃÄ Ä´ ³ ³ ³ 4 ³ ³ ÀÄÃÄ Ä´ ÃÄÄÄAttributes (1 byte)Ä¿ Intensity (1 bit) ÄÄÄÄij 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 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 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 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 *)# #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 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ÄÄij ³ 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 ), 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 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 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 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 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 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.. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ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 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 /* 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 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? 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 #include #include #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 #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 #include #include #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 #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 #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 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 #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, ®, ®); /* 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 #include #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, ®, ®); 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 #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, ®, ®); } /* 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, ®, ®); *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 #include #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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 #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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Listing 13-15. The Setpage() function. ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ /* Getpage() -- obtains the currently active page */ #include #define VIDEO 0x10 #define GETMODE 15 unsigned char Getpage() { union REGS reg; reg.h.ah = GETMODE; int86(VIDEO, ®, ®); 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 #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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 #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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 #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, ®, ®); *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 #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, ®, ®); } /* 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, ®, ®); *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, ®, ®); } /* 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, ®, ®); } /* 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, ®, ®); } /* 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, ®, ®); *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, ®, ®); } /* 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, ®, ®); return reg.h.al; } /* obtains the current video page */ unsigned char Getpage() { union REGS reg; reg.h.ah = GETMODE; int86(VIDEO, ®, ®); 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 #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 #include #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 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 #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 #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 #include "scrn.h" /* Getvmode() -- obtains the current video mode */ unsigned char Getvmode() { union REGS reg; reg.h.ah = GETMODE; int86(VIDEO, ®, ®); 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 #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? "); 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 /* 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 #include #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, ®, ®); 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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 #include #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 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 #include 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 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 #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 #include #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.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, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 #include "scrn.h" void Setvmode(mode) unsigned char mode; { union REGS reg; reg.h.ah = SETMODE; reg.h.al = mode; int86(VIDEO, ®, ®); } ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ 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 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 #include 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 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 #include #include #include #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 ÀÄij 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 #include #include #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 #include #include #include #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 #include #include #include 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 #include #include #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 to change background."); _settextposition(24, 0); _outtext("Press 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 #include /* declares malloc() */ #include #include #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 #include #include #include #include #include #include #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 . 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 #include #include #include #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 #include #include "egacolor.h" #include #include #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 to advance left palette, to go back. */ /* Press to advance right palette, to go back.*/ /* Press to quit. */ #include #include #include #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 #include #include #include #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 #include #include #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 #include #include #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 #include #include #include #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 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ÄÄÄÄÄÄÄij ³ ³ in stack ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ Ãij 10 ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ Then a goesÄÄÄÄÄÄÄij ÃÄÄÄÄÄÄÄ´ through the ³ ³ ³ same process ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ÀÄÀÄÄÄÄÄÄÄÙ Figure 16-1. Passing arguments by means of the stack. ÚÄÚÄÄÄÄÄÄÄ¿ ³ ³ 20 ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ bÄÄÄij ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ Function call places ³ ³ ³ double arguments on ³ ÃÄÄÄÄÄÄÄ´ stack Ãij 10 ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ³ ³ ³ aÄÄÄij ÃÄÄÄÄÄÄÄ´Ä¿ ³ ³ ³ ³ ³ ÃÄÄÄÄÄÄÄ´ ÃÄÄÄ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Äij ÃÄÄÄÄÄÄÄ´ ÃÄÄy Function call ³ ³ ³ ³ Called function removes places int argument ÃÄÃÄÄÄÄÄÄÄ´Ä´ int values from stack on stack ³ ³ 10 ³ ³ aÄij ÃÄÄÄÄÄÄÄ´ ÃÄÄ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 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 Hat Bat Ctrl-Z 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 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 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 #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 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)ÄÄÄij 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)ÄÄÄÄij 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 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 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 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 #include #include 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