Robbins - Linux Programming by Example - 2005
Robbins - Linux Programming by Example - 2005
by Example
Arnold Robbins
PRENTICE HALL
Professional Technical Reference
Upper Saddle River, NJ 07458
www.phptr.com
Prentice Hall
Open Source Software Development Series
Arnold Robbins, Series Editor
Open Source technology has revolutionized the computing world. Many large-scale projects are
in production use worldwide, such as Apache, MySQL, and Postgres, with programmers writing
applications in a variety of languages including Perl, Python, and PHP. These technologies are in
use on many different systems, ranging from proprietary systems, to Linux systems, to traditional
UNIX systems, to mainframes.
The Prentice Hall Open Source Software Development Series is designed to bring you the
best of these Open Source technologies. Not only will you learn how to use them for your
projects, but you will learn from them. By seeing real code from real applications, you will learn
the best practices of Open Source developers the world over.
Prentice Hall PTR offers discounts on this book when ordered in quantity for bulk purchases or special sales. For
more information, please contact: U.S. Corporate and Government Sales, 1-800-382-3419,
[email protected]. For sales outside of the United States, please contact: International Sales,
1-317-581-3793, [email protected].
Portions of Chapter 1, Copyright © 1994 Arnold David Robbins, first appeared in an article in Issue 16 of Linux
Journal, reprinted by permission.
Portions of the documentation for Valgrind, Copyright © 2003 Julian Seward, reprinted by permission.
Portions of the documentation for the DBUG library, by Fred N. Fish, reprinted by permission.
The GNU programs in this book are Copyright © 1985-2003, Free Software Foundation, Inc.. The full list of files
and copyright dates is provided in the Preface. Each program is “free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by the Free Software Foundation; either version
2 of the License, or (at your option) any later version.” Appendix C of this book provides the text of the GNU
General Public License.
All V7 Unix code and documentation are Copyright © Caldera International Inc. 2001-2002. All rights reserved.
They are reprinted here under the terms of the Caldera Ancient UNIX License, which is reproduced in full in
Appendix B.
Cover image courtesy of Parks Sabers, Inc. The Arc-Wave(tm) saber is manufactured by Parks Sabers, Inc., Copyright
© 2001, www.parksabers.com. Parks Sabers is not associated with any Lucasfilm Ltd. property, film, or franchise.
The programs and applications presented in this book have been included for their instructional value. They have
been tested with care but are not guaranteed for any particular purpose. The publisher does not offer any warranties
or representations, nor does it accept any liabilities with respect to the programs or applications. UNIX is a registered
trademark of The Open Group in the United States and other countries.
Microsoft, MS, and MS-DOS are registered trademarks, and Windows is a trademark of Microsoft Corporation in
the United States and other countries. Linux is a registered trademark of Linux Torvalds.
All company and product names mentioned herein are the trademarks or registered trademarks of their respective
owners.
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License,
v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/), with License
Option B.
Printed in the United States of America
ISBN 0-13-142964-7
Text printed on recycled paper
First printing
Pearson Education LTD.
Pearson Education Australia PTY, Limited
Pearson Education Singapore, Pte. Ltd.
Pearson Education North Asia Ltd.
Pearson Education Canada, Ltd.
Pearson Educación de Mexico, S.A. de C.V.
Pearson Education—Japan
Pearson Education Malaysia, Ptd. Ltd.
To my wife Miriam,
and my children,
Chana, Rivka, Nachum, and Malka.
Contents
v
vi Contents
Audience
This book is intended for the person who understands programming and is familiar
with the basics of C, at least on the level of The C Programming Language by Kernighan
and Ritchie. (Java programmers wishing to read this book should understand C pointers,
since C code makes heavy use of them.) The examples use both the 1990 version of
Standard C and Original C.
In particular, you should be familiar with all C operators, control-flow structures,
variable and pointer declarations and use, the string management functions, the use of
exit(), and the <stdio.h> suite of functions for file input/output.
You should understand the basic concepts of standard input, standard output, and
standard error and the fact that all C programs receive an array of character strings
representing invocation options and arguments. You should also be familiar with the
fundamental command-line tools, such as cd, cp, date, ln, ls, man (and info if you
xvii
xviii Preface
have it), rmdir, and rm, the use of long and short command-line options, environment
variables, and I/O redirection, including pipes.
We assume that you want to write programs that work not just under GNU/Linux
but across the range of Unix systems. To that end, we mark each interface as to its
availability (GLIBC systems only, or defined by POSIX, and so on), and portability
advice is included as an integral part of the text.
The programming taught here may be at a lower level than you’re used to; that’s
OK. The system calls are the fundamental building blocks for higher operations and
are thus low-level by nature. This in turn dictates our use of C: The APIs were designed
for use from C, and code that interfaces them to higher-level languages, such as C++
and Java, will necessarily be lower level in nature, and most likely, written in C. It may
help to remember that “low level” doesn’t mean “bad,” it just means “more challenging.”
We have purposely kept the list of topics short. We believe that it is intimidating to
try to learn “all there is to know” from a single book. Most readers prefer smaller, more
focused books, and the best Unix books are all written that way.
So, instead of a single giant tome, we plan several volumes: one on Interprocess
Communication (IPC) and networking, and another on software development and
code portability. We also have an eye toward possible additional volumes in a Linux
Preface xix
Programming by Example series that will cover topics such as thread programming and
GUI programming.
The APIs we cover include both system calls and library functions. Indeed, at the C
level, both appear as simple function calls. A system call is a direct request for system
services, such as reading or writing a file or creating a process. A library function, on the
other hand, runs at the user level, possibly never requesting any services from the oper-
ating system. System calls are documented in section 2 of the reference manual (viewable
online with the man command), and library functions are documented in section 3.
Our goal is to teach you the use of the Linux APIs by example: in particular, through
the use, wherever possible, of both original Unix source code and the GNU utilities.
Unfortunately, there aren’t as many self-contained examples as we thought there’d be.
Thus, we have written numerous small demonstration programs as well. We stress
programming principles: especially those aspects of GNU programming, such as “no
arbitrary limits,” that make the GNU utilities into exceptional programs.
The choice of everyday programs to study is deliberate. If you’ve been using
GNU/Linux for any length of time, you already understand what programs such as ls
and cp do; it then becomes easy to dive straight into how the programs work, without
having to spend a lot of time learning what they do.
Occasionally, we present both higher-level and lower-level ways of doing things.
Usually the higher-level standard interface is implemented in terms of the lower-level
interface or construct. We hope that such views of what’s “under the hood” will help
you understand how things work; for all the code you write, you should always use the
higher-level, standard interface.
Similarly, we sometimes introduce functions that provide certain functionality and
then recommend (with a provided reason) that these functions be avoided! The primary
reason for this approach is so that you’ll be able to recognize these functions when you
see them and thus understand the code using them. A well-rounded knowledge of a
topic requires understanding not just what you can do, but what you should and should
not do.
Finally, each chapter concludes with exercises. Some involve modifying or writing
code. Others are more in the category of “thought experiments” or “why do you
think …” We recommend that you do all of them—they will help cement your under-
standing of the material.
xx Preface
Standards
Throughout the book we refer to several different formal standards. A standard is a
document describing how something works. Formal standards exist for many things,
for example, the shape, placement, and meaning of the holes in the electrical outlet in
1 This famous statement was made at The International Workshop on Efficient Production of Large Programs in
Jablonna, Poland, August 10–14, 1970.
Preface xxi
your wall are defined by a formal standard so that all the power cords in your country
work in all the outlets.
So, too, formal standards for computing systems define how they are supposed to
work; this enables developers and users to know what to expect from their software and
enables them to complain to their vendor when software doesn’t work.
Of interest to us here are:
1. ISO/IEC International Standard 9899: Programming Languages — C, 1990.
The first formal standard for the C programming language.
2. ISO/IEC International Standard 9899: Programming Languages — C, Second
edition, 1999. The second (and current) formal standard for the C programming
language.
3. ISO/IEC International Standard 14882: Programming Languages — C++, 1998.
The first formal standard for the C++ programming language.
4. ISO/IEC International Standard 14882: Programming Languages — C++, 2003.
The second (and current) formal standard for the C++ programming language.
5. IEEE Standard 1003.1–2001: Standard for Information Technology — Portable
Operating System Interface (POSIX®). The current version of the POSIX stan-
dard; describes the behavior expected of Unix and Unix-like systems. This
edition covers both the system call and library interface, as seen by the C/C++
programmer, and the shell and utilities interface, seen by the user. It consists
of several volumes:
• Base Definitions. The definitions of terms, facilities, and header files.
• Base Definitions — Rationale. Explanations and rationale for the choice of
facilities that both are and are not included in the standard.
• System Interfaces. The system calls and library functions. POSIX terms them
all “functions.”
• Shell and Utilities. The shell language and utilities available for use with shell
programs and interactively.
Although language standards aren’t exciting reading, you may wish to consider pur-
chasing a copy of the C standard: It provides the final definition of the language. Copies
xxii Preface
can be purchased from ANSI2 and from ISO.3 (The PDF version of the C standard is
quite affordable.)
The POSIX standard can be ordered from The Open Group.4 By working through
their publications catalog to the items listed under “CAE Specifications,” you can find
individual pages for each part of the standard (named “C031” through “C034”). Each
one’s page provides free access to the online HTML version of the particular volume.
The POSIX standard is intended for implementation on both Unix and Unix-like
systems, as well as non-Unix systems. Thus, the base functionality it provides is a subset
of what Unix systems have. However, the POSIX standard also defines optional exten-
sions—additional functionality, for example, for threads or real-time support. Of most
importance to us is the X/Open System Interface (XSI) extension, which describes facilities
from historical Unix systems.
Throughout the book, we mark each API as to its availability: ISO C, POSIX, XSI,
GLIBC only, or nonstandard but commonly available.
2 http://www.ansi.org
3 http://www.iso.ch
4 http://www.opengroup.org
5 http://www.gnu.org
Preface xxiii
By using GNU programs, we want to meet both goals: show you well-written,
modern code from which you will learn how to write good code and how to use the
APIs well.
We believe that GNU software is better because it is free (in the sense of “freedom,”
not “free beer”). But it’s also recognized that GNU software is often technically better
than the corresponding Unix counterparts, and we devote space in Section 1.4, “Why
GNU Programs Are Better,” page 14, to explaining why.
A number of the GNU code examples come from gawk (GNU awk). The main
reason is that it’s a program with which we’re very familiar, and therefore it was easy
to pick examples from it. We don’t otherwise make any special claims about it.
Summary of Chapters
Driving a car is a holistic process that involves multiple simultaneous tasks. In many
ways, Linux programming is similar, requiring understanding of multiple aspects
of the API, such as file I/O, file metadata, directories, storage of time information,
and so on.
The first part of the book looks at enough of these individual items to enable studying
the first significant program, the V7 ls. Then we complete the discussion of files and
users by looking at file hierarchies and the way filesystems work and are used.
Chapter 1, “Introduction,” page 3,
describes the Unix and Linux file and process models, looks at the differences be-
tween Original C and 1990 Standard C, and provides an overview of the principles
that make GNU programs generally better than standard Unix programs.
Chapter 2, “Arguments, Options, and the Environment,” page 23,
describes how a C program accesses and processes command-line arguments and
options and explains how to work with the environment.
Chapter 3, “User-Level Memory Management,” page 51,
provides an overview of the different kinds of memory in use and available in a
running process. User-level memory management is central to every nontrivial
application, so it’s important to understand it early on.
xxiv Preface
The second part of the book deals with process creation and management, interprocess
communication with pipes and signals, user and group IDs, and additional general
programming interfaces. Next, the book first describes internationalization with GNU
gettext and then several advanced APIs.
We round the book off with a chapter on debugging, since (almost) no one gets
things right the first time, and we suggest a final project to cement your knowledge of
the APIs covered in this book.
Chapter 15, “Debugging,” page 567,
describes the basics of the GDB debugger, transmits as much of our programming
experience in this area as possible, and looks at several useful tools for doing dif-
ferent kinds of debugging.
Chapter 16, “A Project That Ties Everything Together,” page 641,
presents a significant programming project that makes use of just about everything
covered in the book.
Several appendices cover topics of interest, including the licenses for the source code
used in this book.
Appendix A, “Teach Yourself Programming in Ten Years,” page 649,
invokes the famous saying, “Rome wasn’t built in a day.” So too, Linux/Unix ex-
pertise and understanding only come with time and practice. To that end, we
have included this essay by Peter Norvig which we highly recommend.
Appendix B, “Caldera Ancient UNIX License,” page 655,
covers the Unix source code used in this book.
Appendix C, “GNU General Public License,” page 657,
covers the GNU source code used in this book.
xxvi Preface
Typographical Conventions
Like all books on computer-related topics, we use certain typographical conventions
to convey information. Definitions or first uses of terms appear in italics, like the word
“Definitions” at the beginning of this sentence. Italics are also used for emphasis, for
citations of other works, and for commentary in examples. Variable items such as argu-
ments or filenames, appear like this. Occasionally, we use a bold font when a point
needs to be made strongly.
Things that exist on a computer are in a constant-width font, such as filenames
(foo.c) and command names (ls, grep). Short snippets that you type are additionally
enclosed in single quotes: ‘ls -l *.c’.
$ and > are the Bourne shell primary and secondary prompts and are used to display
interactive examples. User input appears in a different font from regular computer
output in examples. Examples look like this:
$ ls -1 Look at files. Option is digit 1, not letter l
foo
bar
baz
We prefer the Bourne shell and its variants (ksh93, Bash) over the C shell; thus, all
our examples show only the Bourne shell. Be aware that quoting and line-continuation
rules are different in the C shell; if you use it, you’re on your own!6
When referring to functions in programs, we append an empty pair of parentheses
to the function’s name: printf(), strcpy(). When referring to a manual page (acces-
sible with the man command), we follow the standard Unix convention of writing the
command or function name in italics and the section in parentheses after it, in regular
type: awk(1), printf (3).
6 See the csh(1) and tcsh(1) manpages and the book Using csh & tcsh, by Paul DuBois, O’Reilly & Associates, Se-
bastopol, CA, USA, 1995. ISBN: 1-56592-132-1.
Preface xxvii
Unix Code
Archives of various “ancient” versions of Unix are maintained by The UNIX Heritage
Society (TUHS), http://www.tuhs.org.
Of most interest is that it is possible to browse the archive of old Unix source code
on the Web. Start with http://minnie.tuhs.org/UnixTree/. All the example code
in this book is from the Seventh Edition Research UNIX System, also known as “V7.”
The TUHS site is physically located in Australia, although there are mirrors of the
archive around the world—see http://www.tuhs.org/archive_sites.html.
This page also indicates that the archive is available for mirroring with rsync.
(See http://rsync.samba.org/ if you don’t have rsync: It’s standard on
GNU/Linux systems.)
You will need about 2–3 gigabytes of disk to copy the entire archive. To copy the
archive, create an empty directory, and in it, run the following commands:
mkdir Applications 4BSD PDP-11 PDP-11/Trees VAX Other
You may wish to omit copying the Trees directory, which contains extractions of
several versions of Unix, and occupies around 700 megabytes of disk.
You may also wish to consult the TUHS mailing list to see if anyone near you can
provide copies of the archive on CD-ROM, to avoid transferring so much data over
the Internet.
The folks at Southern Storm Software, Pty. Ltd., in Australia, have “modernized” a
portion of the V7 user-level code so that it can be compiled and run on current systems,
most notably GNU/Linux. This code can be downloaded from their web site.7
It’s interesting to note that V7 code does not contain any copyright or permission
notices in it. The authors wrote the code primarily for themselves and their research,
leaving the permission issues to AT&T’s corporate licensing department.
7 http://www.southern-storm.com.au/v7upgrade.html
xxviii Preface
GNU Code
If you’re using GNU/Linux, then your distribution will have come with source code,
presumably in whatever packaging format it uses (Red Hat RPM files, Debian DEB
files, Slackware .tar.gz files, etc.). Many of the examples in the book are from the
GNU Coreutils, version 5.0. Find the appropriate CD-ROM for your GNU/Linux
distribution, and use the appropriate tool to extract the code. Or follow the instructions
in the next few paragraphs to retrieve the code.
If you prefer to retrieve the files yourself from the GNU ftp site, you will find them
at ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz.
You can use the wget utility to retrieve the file:
$ wget ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz Retrieve the distribution
… lots of output here as file is retrieved …
Alternatively, you can use good old-fashioned ftp to retrieve the file:
$ ftp ftp.gnu.org Connect to GNU ftp site
Connected to ftp.gnu.org (199.232.41.7).
220 GNU FTP server ready.
Name (ftp.gnu.org:arnold): anonymous Use anonymous ftp
331 Please specify the password.
Password: Password does not echo on screen
230-If you have any problems with the GNU software or its downloading,
230-please refer your questions to <[email protected]>.
... Lots of verbiage deleted
230 Login successful. Have fun.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> cd /gnu/coreutils Change to Coreutils directory
250 Directory successfully changed.
ftp> bin
200 Switching to Binary mode.
ftp> hash Print # signs as progress indicators
Hash mark printing on (1024 bytes/hash mark).
ftp> get coreutils-5.0.tar.gz Retrieve file
local: coreutils-5.0.tar.gz remote: coreutils-5.0.tar.gz
227 Entering Passive Mode (199,232,41,7,86,107)
150 Opening BINARY mode data connection for coreutils-5.0.tar.gz (6020616 bytes)
#################################################################################
#################################################################################
...
226 File send OK.
6020616 bytes received in 2.03e+03 secs (2.9 Kbytes/sec)
ftp> quit Log off
221 Goodbye.
Preface xxix
In the hands of a Jedi Knight, a light saber is both a powerful weapon and a thing
of beauty. Its use demonstrates the power, knowledge, control of the Force, and arduous
training of the Jedi who wields it.
The elegance of the light saber mirrors the elegance of the original Unix API design.
There, too, the studied, precise use of the APIs and the Software Tools and GNU design
principles lead to today’s powerful, flexible, capable GNU/Linux system. This system
demonstrates the knowledge and understanding of the programmers who wrote all its
components.
And, of course, light sabers are just way cool!
Acknowledgments
Writing a book is lots of work, and doing it well requires help from many people.
Dr. Brian W. Kernighan, Dr. Doug McIlroy, Peter Memishian, and Peter van der
Linden reviewed the initial book proposal. David J. Agans, Fred Fish, Don Marti, Jim
Meyering, Peter Norvig, and Julian Seward provided reprint permission for various
items quoted throughout the book. Thanks to Geoff Collyer, Ulrich Drepper, Yosef
Gold, Dr. C.A.R. (Tony) Hoare, Dr. Manny Lehman, Jim Meyering, Dr. Dennis M.
Ritchie, Julian Seward, Henry Spencer, and Dr. Wladyslaw M. Turski, who provided
much useful general information. Thanks also to the other members of the GNITS
gang: Karl Berry, Akim DeMaille, Ulrich Drepper, Greg McGary, Jim Meyering,
François Pinard, and Tom Tromey, who all provided helpful feedback about good
programming practice. Karl Berry, Alper Ersoy, and Dr. Nelson H.F. Beebe provided
valuable technical help with the Texinfo and DocBook/XML toolchains.
Good technical reviewers not only make sure that an author gets his facts right, they
also ensure that he thinks carefully about his presentation. Dr. Nelson H.F. Beebe,
Geoff Collyer, Russ Cox, Ulrich Drepper, Randy Lechlitner, Dr. Brian W. Kernighan,
Peter Memishian, Jim Meyering, Chet Ramey, and Louis Taber acted as technical re-
viewers for the entire book. Dr. Michael Brennan provided helpful comments on
Chapter 15. Both the prose and many of the example programs benefited from their
reviews. I hereby thank all of them. As most authors usually say here, “Any remaining
errors are mine.”
I would especially like to thank Mark Taub of Pearson Education for initiating this
project, for his enthusiasm for the series, and for his help and advice as the book moved
xxxii Preface
through its various stages. Anthony Gemmellaro did a phenomenal job of realizing my
concept for the cover, and Gail Cocker’s interior design is beautiful. Faye Gemmellaro
made the production process enjoyable, instead of a chore. Dmitry Kirsanov and
Alina Kirsanova did the figures, page layout, and indexing; they were a pleasure to
work with.
Finally, my deepest gratitude and love to my wife, Miriam, for her support and en-
couragement during the book’s writing.
Arnold Robbins
Nof Ayalon
ISRAEL
I
1
This page intentionally left blank
1
Introduction
In this chapter
3
f there is one phrase that summarizes the primary GNU/Linux (and therefore
I Unix) concepts, it’s “files and processes.” In this chapter we review the Linux
file and process models. These are important to understand because the system calls
are almost all concerned with modifying some attribute or part of the state of a file
or a process.
Next, because we’ll be examining code in both styles, we briefly review the major
difference between 1990 Standard C and Original C. Finally, we discuss at some
length what makes GNU programs “better,” programming principles that we’ll see
in use in the code.
This chapter contains a number of intentional simplifications. The full details are
covered as we progress through the book. If you’re already a Linux wizard, please
forgive us.
4
1.1 The Linux/Unix File Model 5
record sizes, no indexed files, nothing. The interpretation of file contents is entirely up
to the application. (This isn’t quite true, as we’ll see shortly, but it’s close enough for
a start.)
Once you have a file, you can do three things with the file’s data: read them, write
them, or execute them.
Unix was designed for time-sharing minicomputers; this implies a multiuser environ-
ment from the get-go. Once there are multiple users, it must be possible to specify a
file’s permissions: Perhaps user jane is user fred’s boss, and jane doesn’t want fred
to read the latest performance evaluations.
For file permission purposes, users are classified into three distinct categories: user:
the owner of a file; group: the group of users associated with this file (discussed shortly);
and other: anybody else. For each of these categories, every file has separate read, write,
and execute permission bits associated with it, yielding a total of nine permission bits.
This shows up in the first field of the output of ‘ls -l’:
$ ls -l progex.texi
-rw-r--r-- 1 arnold devel 5614 Feb 24 18:02 progex.texi
Here, arnold and devel are the owner and group of progex.texi, and -rw-r--r--
are the file type and permissions. The first character is a dash for regular file, a d for
directories, or one of a small set of other characters for other kinds of files that aren’t
important at the moment. Each subsequent group of three characters represents read,
write, and execute permission for the owner, group, and “other,” respectively.
In this example, progex.texi is readable and writable by the owner, and readable
by the group and other. The dashes indicate absent permissions, thus the file is not ex-
ecutable by anyone, nor is it writable by the group or other.
The owner and group of a file are stored as numeric values known as the user ID
(UID) and group ID (GID); standard library functions that we present later in the book
make it possible to print the values as human-readable names.
A file’s owner can change the permission by using the chmod (change mode)
command. (As such, file permissions are sometimes referred to as the “file mode.”)
A file’s group can be changed with the chgrp (change group) and chown (change
owner) commands.1
1 Some systems allow regular users to change the ownership on their files to someone else, thus “giving them away.”
The details are standardized by POSIX but are a bit messy. Typical GNU/Linux configurations do not allow it.
6 Chapter 1 • Introduction
Group permissions were intended to support cooperative work: Although one person
in a group or department may own a particular file, perhaps everyone in that group
needs to be able to modify it. (Consider a collaborative marketing paper or data from
a survey.)
When the system goes to check a file access (usually upon opening a file), if the UID
of the process matches that of the file, the owner permissions apply. If those permissions
deny the operation (say, a write to a file with -r--rw-rw- permissions), the operation
fails; Unix and Linux do not proceed to test the group and other permissions.2 The
same is true if the UID is different but the GID matches; if the group permissions deny
the operation, it fails.
Unix and Linux support the notion of a superuser: a user with special privileges. This
user is known as root and has the UID of 0. root is allowed to do anything; all bets
are off, all doors are open, all drawers unlocked.3 (This can have significant security
implications, which we touch on throughout the book but do not cover exhaustively.)
Thus, even if a file is mode ----------, root can still read and write the file. (One
exception is that the file can’t be executed. But as root can add execute permission,
the restriction doesn’t prevent anything.)
The user/group/other, read/write/execute permissions model is simple, yet flexible
enough to cover most situations. Other, more powerful but more complicated, models
exist and are implemented on different systems, but none of them are well enough
standardized and broadly enough implemented to be worth discussing in a general-
purpose text like this one.
2 The owner can always change the permission, of course. Most users don’t disable write permission for themselves.
3 There are some rare exceptions to this rule, all of which are beyond the scope of this book.
1.1 The Linux/Unix File Model 7
page 83. They are also special in that the operating system dictates the format of direc-
tory entries.
Filenames may contain any valid 8-bit byte except the / (forward slash) character
and ASCII NUL, the character whose bits are all zero. Early Unix systems limited file-
names to 14 bytes; modern systems allow individual filenames to be up to 255 bytes.
The inode contains all the information about a file except its name: the type, owner,
group, permissions, size, modification and access times. It also stores the locations on
disk of the blocks containing the file’s data. All of these are data about the file, not the
file’s data itself, thus the term metadata.
Directory permissions have a slightly different meaning from those for file permissions.
Read permission means the ability to search the directory; that is, to look through it to
see what files it contains. Write permission is the ability to create and remove files in
the directory. Execute permission is the ability to go through a directory when opening
or otherwise accessing a contained file or subdirectory.
NOTE If you have write permission on a directory, you can remove files in that
directory, even if they don’t belong to you! When used interactively, the rm
command notices this, and asks you for confirmation in such a case.
The /tmp directory has write permission for everyone, but your files in /tmp
are quite safe because /tmp usually has the so-called sticky bit set on it:
$ ls -ld /tmp
drwxrwxrwt 11 root root 4096 May 15 17:11 /tmp
Note the t is the last position of the first field. On most directories this position
has an x in it. With the sticky bit set, only you, as the file’s owner, or root may
remove your files. (We discuss this in more detail in Section 11.5.2, “Directories
and the Sticky Bit,” page 414.)
Although the kernel will only run a file laid out in the proper format, it is up to user-
level utilities to create these files. The compiler for a programming language (such as
Ada, Fortran, C, or C++) creates object files, and then a linker or loader (usually named
ld) binds the object files with library routines to create the final executable. Note that
even if a file has all the right bits in all the right places, the kernel won’t run it if the
appropriate execute permission bit isn’t turned on (or at least one execute bit for root).
Because the compiler, assembler, and loader are user-level tools, it’s (relatively) easy
to change object file formats as needs develop over time; it’s only necessary to “teach”
the kernel about the new format and then it can be used. The part that loads executables
is relatively small and this isn’t an impossible task. Thus, Unix file formats have evolved
over time. The original format was known as a.out (Assembler OUTput). The next
format, still used on some commercial systems, is known as COFF (Common Object
File Format), and the current, most widely used format is ELF (Extensible Linking
Format). Modern GNU/Linux systems use ELF.
The kernel recognizes that an executable file contains binary object code by looking
at the first few bytes of the file for special magic numbers. These are sequences of two
or four bytes that the kernel recognizes as being special. For backwards compatibility,
modern Unix systems recognize multiple formats. ELF files begin with the four characters
"\177ELF".
Besides binary executables, the kernel also supports executable scripts. Such a file also
begins with a magic number: in this case, the two regular characters #!. A script is a
program executed by an interpreter, such as the shell, awk, Perl, Python, or Tcl. The
#! line provides the full path to the interpreter and, optionally, one single argument:
#! /bin/awk -f
Let’s assume the above contents are in a file named hello.awk and that the file is
executable. When you type ‘hello.awk’, the kernel runs the program as if you had
typed ‘/bin/awk -f hello.awk’. Any additional command-line arguments are also
passed on to the program. In this case, awk runs the program and prints the universally
known hello, world message.
The #! mechanism is an elegant way of hiding the distinction between binary exe-
cutables and script executables. If hello.awk is renamed to just hello, the user typing
1.1 The Linux/Unix File Model 9
‘hello’ can’t tell (and indeed shouldn’t have to know) that hello isn’t a binary exe-
cutable program.
1.1.4 Devices
One of Unix’s most notable innovations was the unification of file I/O and device
I/O.4 Devices appear as files in the filesystem, regular permissions apply to their access,
and the same I/O system calls are used for opening, reading, writing, and closing them.
All of the “magic” to make devices look like files is hidden in the kernel. This is just
another aspect of the driving simplicity principle in action: We might phrase it as no
special cases for user code.
Two devices appear frequently in everyday use, particularly at the shell level:
/dev/null and /dev/tty.
/dev/null is the “bit bucket.” All data sent to /dev/null is discarded by the oper-
ating system, and attempts to read from it always return end-of-file (EOF) immediately.
/dev/tty is the process’s current controlling terminal—the one to which it listens
when a user types the interrupt character (typically CTRL-C) or performs job control
(CTRL-Z).
GNU/Linux systems, and many modern Unix systems, supply /dev/stdin,
/dev/stdout, and /dev/stderr devices, which provide a way to name the open files
each process inherits upon startup.
Other devices represent real hardware, such as tape and disk drives, CD-ROM drives,
and serial ports. There are also software devices, such as pseudo-ttys, that are used for
networking logins and windowing systems. /dev/console represents the system console,
a particular hardware device on minicomputers. On modern computers, /dev/console
is the screen and keyboard, but it could be a serial port.
Unfortunately, device-naming conventions are not standardized, and each operating
system has different names for tapes, disks, and so on. (Fortunately, that’s not an issue
for what we cover in this book.) Devices have either a b or c in the first character of
‘ls -l’ output:
4 This feature first appeared in Multics, but Multics was never widely used.
10 Chapter 1 • Introduction
$ ls -l /dev/tty /dev/hda
brw-rw---- 1 root disk 3, 0 Aug 31 02:31 /dev/hda
crw-rw-rw- 1 root root 5, 0 Feb 26 08:44 /dev/tty
The initial b represents block devices, and a c represents character devices. Device files
are discussed further in Section 5.4, “Obtaining Information about Files,” page 139.
When the main() function begins execution, all of these things have already been
put in place for the running program. System calls are available to query and change
each of the above items; covering them is the purpose of this book.
New processes are always created by an existing process. The existing process is termed
the parent, and the new process is termed the child. Upon booting, the kernel handcrafts
the first, primordial process, which runs the program /sbin/init; it has process ID
5 Processes can be suspended, in which case they are not “running”; however, neither are they terminated. In any
case, in the early stages of the climb up the learning curve, it pays not to be too pedantic.
1.2 The Linux/Unix Process Model 11
1 and serves several administrative functions. All other processes are descendants of
init. (init’s parent is the kernel, often listed as process ID 0.)
The child-to-parent relationship is one-to-one; each process has only one parent,
and thus it’s easy to find out the PID of the parent. The parent-to-child relationship
is one-to-many; any given process can create a potentially unlimited number of children.
Thus, there is no easy way for a process to find out the PIDs of all its children. (In
practice, it’s not necessary, anyway.) A parent process can arrange to be notified when
a child process terminates (“dies”), and it can also explicitly wait for such an event.
Each process’s address space (memory) is separate from that of every other. Unless
two processes have made explicit arrangement to share memory, one process cannot
affect the address space of another. This is important; it provides a basic level of security
and system reliability. (For efficiency, the system arranges to share the read-only exe-
cutable code of the same program among all the processes running that program. This
is transparent to the user and to the running program.)
The current working directory is the one to which relative pathnames (those that
don’t start with a /) are relative. This is the directory you are “in” whenever you issue
a ‘cd someplace’ command to the shell.
By convention, all programs start out with three files already open: standard input,
standard output, and standard error. These are where input comes from, output goes
to, and error messages go to, respectively. In the course of this book, we will see how
these are put in place. A parent process can open additional files and have them already
available for a child process; the child will have to know they’re there, either by way of
some convention or by a command-line argument or environment variable.
The environment is a set of strings, each of the form ‘name=value’. Functions exist
for querying and setting environment variables, and child processes inherit the environ-
ment of their parents. Typical environment variables are things like PATH and HOME in
the shell. Many programs look for the existence and value of specific environment
variables in order to control their behavior.
It is important to understand that a single process may execute multiple programs
during its lifetime. Unless explicitly changed, all of the other system-maintained
attributes (current directory, open files, PID, etc.) remain the same. The separation of
“starting a new process” from “choosing which program to run” is a key Unix innovation.
12 Chapter 1 • Introduction
It makes many operations simple and straightforward. Other operating systems that
combine the two operations are less general and more complicated to use.
...
struct my_struct s, t;
int j;
...
/* Function call, somewhere else: */
j = my_func(& s, & t, 3.1415, 42);
Consider again the same erroneous function call: ‘j = my_func(-1, -2, 0);’. In
Original C, the compiler has no way of knowing that you’ve (accidentally, we assume)
passed the wrong arguments to my_func(). Such erroneous calls generally lead to hard-
to-find runtime problems (such as segmentation faults, whereby the program dies), and
the Unix lint program was created to deal with these kinds of things.
So, although function prototypes were a radical departure from existing practice,
their additional type checking was deemed too important to be without, and they were
added to the language with little opposition.
14 Chapter 1 • Introduction
In 1990 Standard C, code written in the original style, for both declarations and
definitions, is valid. This makes it possible to continue to compile millions of lines of
existing code with a standard-conforming compiler. New code, obviously, should be
written with prototypes because of the improved possibilities for compile-time
error checking.
1999 Standard C continues to allow original style declarations and definitions.
However, the “implicit int” rule was removed; functions must have a return type, and
all parameters must be declared.
Furthermore, when a program called a function that had not been formally declared,
Original C would create an implicit declaration for the function, giving it a return type
of int. 1990 Standard C did the same, additionally noting that it had no information
about the parameters. 1999 Standard C no longer provides this “auto-declare” feature.
Other notable additions in Standard C are the const keyword, also from C++, and
the volatile keyword, which the committee invented. For the code you’ll see in this
book, understanding the different function declaration and definition syntaxes is the
most important thing.
For V7 code using original style definitions, we have added comments showing the
equivalent prototype. Otherwise, we have left the code alone, preferring to show it ex-
actly as it was originally written and as you’ll see it if you download the code yourself.
Although 1999 C adds some additional keywords and features beyond the 1990
version, we have chosen to stick to the 1990 dialect, since C99 compilers are not yet
commonplace. Practically speaking, this doesn’t matter: C89 code should compile and
run without change when a C99 compiler is used, and the new C99 features don’t affect
our discussion or use of the fundamental Linux/Unix APIs.
6 This section is adapted from an article by the author that appeared in Issue 16 of Linux Journal. (See
http://www.linuxjournal.com/article.php?sid=1135.) Reprinted and adapted by permission.
1.4 Why GNU Programs Are Better 15
it’s much better.” GNU software is generally more robust, and performs better, than
standard Unix versions. In this section we look at some of the reasons why, and at the
document that describes the principles of GNU software design.
The GNU Coding Standards describes how to write software for the GNU
project. It covers a range of topics. You can read the GNU Coding Standards online at
http://www.gnu.org/prep/standards.html. See the online version for pointers
to the source files in other formats.
In this section, we describe only those parts of the GNU Coding Standards that relate
to program design and implementation.
7 This statement refers to the HURD kernel, which is still under development (as of early 2004). GCC and GNU
C Library (GLIBC) development take place mostly on Linux-based systems today.
16 Chapter 1 • Introduction
pick. The GNU Coding Standards also makes this point. (Sometimes, there is no de-
tectable consistent coding style, in which case the program is probably overdue for a
trip through either GNU indent or Unix’s cb.)
What we find important about the chapter on C coding is that the advice is good
for any C coding, not just if you happen to be working on a GNU program. So, if
you’re just learning C or even if you’ve been working in C (or C++) for a while, we
recommend this chapter to you since it encapsulates many years of experience.
8 This situation occurred circa 1993; the truism is even more obvious today, as users process gigabytes of log files
with gawk.
18 Chapter 1 • Introduction
It is also well known that Emacs can edit any arbitrary file, including files containing
binary data!
Whenever possible, try to make programs work properly with sequences of
bytes that represent multibyte characters, using encodings such as UTF-8
and others.9 Check every system call for an error return, unless you know
you wish to ignore errors. Include the system error text (from perror or
equivalent) in every error message resulting from a failing system call, as well
as the name of the file if any and the name of the utility. Just “cannot open
foo.c” or “stat failed” is not sufficient.
Checking every system call provides robustness. This is another case in which life is
harder for the programmer but better for the user. An error message detailing what ex-
actly went wrong makes finding and solving any problems much easier.10
Finally, we quote from Chapter 1 of the GNU Coding Standards, which discusses
how to write your program differently from the way a Unix program may have
been written.
For example, Unix utilities were generally optimized to minimize memory
use; if you go for speed instead, your program will be very different. You
could keep the entire input file in core and scan it there instead of using
stdio. Use a smarter algorithm discovered more recently than the Unix pro-
gram. Eliminate use of temporary files. Do it in one pass instead of two (we
did this in the assembler).
Or, on the contrary, emphasize simplicity instead of speed. For some appli-
cations, the speed of today’s computers makes simpler algorithms adequate.
Or go for generality. For example, Unix programs often have static tables or
fixed-size strings, which make for arbitrary limits; use dynamic allocation
instead. Make sure your program handles NULs and other funny characters
in the input files. Add a programming language for extensibility and write
part of the program in that language.
9 Section 13.4, “Can You Spell That for Me, Please?”, page 521, provides an overview of multibyte characters and
encodings.
10 The mechanics of checking for and reporting errors are discussed in Section 4.3, “Determining What Went
Wrong,” page 86.
1.5 Portability Revisited 19
ISO standards for C and the 2003 standard for C++ since most Linux programming
is done in one of those two languages.
Also, the POSIX standard for library and system call interfaces, while large, has
broad industry support. Writing to POSIX greatly improves the chances of suc-
cessfully moving your code to other systems besides GNU/Linux. This standard
is quite readable; it distills decades of experience and good practice.
Pick the best interface for the job.
If a standard interface does what you need, use it in your code. Use Autoconf to
detect an unavailable interface, and supply a replacement version of it for deficient
systems. (For example, some older systems lack the memmove() function, which
is fairly easy to code by hand or to pull from the GLIBC library.)
Isolate portability problems behind new interfaces.
Sometimes, you may need to do operating-system-specific tasks that apply on
some systems but not on others. (For example, on some systems, each program
has to expand command-line wildcards instead of the shell doing it.) Create a new
interface that does nothing on systems that don’t need it but does the correct thing
on systems that do.
Use Autoconf for configuration.
Avoid #ifdef if possible. If not, bury it in low-level library code. Use Autoconf
to do the checking for the tests to be performed with #ifdef.
This book is also a classic. It covers Original C as well as the 1990 and 1999
standards. Because it is current, it makes a valuable companion to The C Pro-
gramming Language. It covers many important items, such as internationaliza-
tion-related types and library functions, that aren’t in the Kernighan and
Ritchie book.
3. Notes on Programming in C, by Rob Pike, February 21, 1989. Available
on the Web from many sites. Perhaps the most widely cited location is
http://www.lysator.liu.se/c/pikestyle.html. (Many other useful
articles are available from one level up: http://www.lysator.liu.se/c/.)
Rob Pike worked for many years at the Bell Labs research center where C and
Unix were invented and did pioneering development there. His notes distill
many years of experience into a “philosophy of clarity in programming” that
is well worth reading.
4. The various links at http://www.chris-lott.org/resources/cstyle/.
This site includes Rob Pike’s notes and several articles by Henry Spencer. Of
particular note is the Recommended C Style and Coding Standards, originally
written at the Bell Labs Indian Hill site.
1.7 Summary
• “Files and processes” summarizes the Linux/Unix worldview. The treatment of
files as byte streams and devices as files, and the use of standard input, output,
and error, simplify program design and unify the data access model. The permis-
sions model is simple, yet flexible, applying to both files and directories.
• Processes are running programs that have user and group identifiers associated
with them for permission checking, as well as other attributes such as open files
and a current working directory.
• The most visible difference between Standard C and Original C is the use of
function prototypes for stricter type checking. A good C programmer should be
able to read Original-style code, since many existing programs use it. New code
should be written using prototypes.
• The GNU Coding Standards describe how to write GNU programs. They provide
numerous valuable techniques and guiding principles for producing robust, usable
22 Chapter 1 • Introduction
software. The “no arbitrary limits” principle is perhaps the single most important
of these. This document is required reading for serious programmers.
• Making programs portable is a significant challenge. Guidelines and tools help,
but ultimately experience is needed too.
Exercises
1. Read and comment on the article “The GNU Project”,11 by Richard M.
Stallman, originally written in August of 1998.
11 http://www.gnu.org/gnu/thegnuproject.html
2
Arguments,
Options,
and
the Environment
In this chapter
23
ommand-line option and argument interpretation is usually the first task of
C any program. This chapter examines how C (and C++) programs access their
command-line arguments, describes standard routines for parsing options, and takes
a look at the environment.
Here, the user typed four “words.” All four words are made available to the program
as its arguments.
The second definition is more informal: Arguments are all the words on the command
line except the command name. By default, Unix shells separate arguments from each
other with whitespace (spaces or TAB characters). Quoting allows arguments to include
whitespace:
$ echo here are lots of spaces
here are lots of spaces The shell ‘‘eats’’ the spaces
$ echo "here are lots of spaces"
here are lots of spaces Spaces are preserved
Quoting is transparent to the running program; echo never sees the double-quote
characters. (Double and single quotes are different in the shell; a discussion of the rules
is beyond the scope of this book, which focuses on C programming.)
Arguments can be further classified as options or operands. In the previous two exam-
ples all the arguments were operands: files for ls and raw text for echo.
Options are special arguments that each program interprets. Options change a pro-
gram’s behavior, or they provide information to the program. By ancient convention,
(almost) universally adhered to, options start with a dash (a.k.a. hyphen, minus sign)
and consist of a single letter. Option arguments are information needed by an option,
as opposed to regular operand arguments. For example, the fgrep program’s -f option
means “use the contents of the following file as a list of strings to search for.” See
Figure 2.1.
24
2.1 Option and Argument Conventions 25
Command name
Option
Option argument
Operands
FIGURE 2.1
Command-line components
Thus, patfile is not a data file to search, but rather it’s for use by fgrep in defining
the list of strings to search for.
The standard, however, does allow for historical practice, whereby sometimes
the option and the operand could be in the same string: ‘fgrep -fpatfile’.
In practice, the getopt() and getopt_long() functions interpret ‘-fpatfile’
as ‘-f patfile’, not as ‘-f -p -a -t ...’.
7. Option arguments should not be optional.
This means that when a program documents an option as requiring an option
argument, that option’s argument must always be present or else the program
will fail. GNU getopt() does provide for optional option arguments since
they’re occasionally useful.
8. If an option takes an argument that may have multiple values, the program
should receive that argument as a single string, with values separated by commas
or whitespace.
For example, suppose a hypothetical program myprog requires a list of users
for its -u option. Then, it should be invoked in one of these two ways:
myprog -u "arnold,joe,jane" Separate with commas
myprog -u "arnold joe jane" Separate with whitespace
In such a case, you’re on your own for splitting out and processing each value
(that is, there is no standard routine), but doing so manually is usually
straightforward.
9. Options should come first on the command line, before operands. Unix versions
of getopt() enforce this convention. GNU getopt() does not by default,
although you can tell it to.
10. The special argument ‘--’ indicates the end of all options. Any subsequent ar-
guments on the command line are treated as operands, even if they begin with
a dash.
11. The order in which options are given should not matter. However, for mutu-
ally exclusive options, when one option overrides the setting of another, then
(so to speak) the last one wins. If an option that has arguments is repeated, the
program should process the arguments in order. For example, ‘myprog -u
arnold -u jane’ is the same as ‘myprog -u "arnold,jane"’. (You have
to enforce this yourself; getopt() doesn’t help you.)
12. It is OK for the order of operands to matter to a program. Each program should
document such things.
2.1 Option and Argument Conventions 27
13. Programs that read or write named files should treat the single argument ‘-’ as
meaning standard input or standard output, as is appropriate for the program.
Note that many standard programs don’t follow all of the above conventions. The
primary reason is historical compatibility; many such programs predate the codifying
of these conventions.
7. Programs can choose to allow long options to begin with a single dash. (This
is common with many X Window programs.)
FIGURE 2.2
Memory for argv
By convention, argv[0] is the program’s name. (For details, see Section 9.1.4.3,
“Program Names and argv[0],” page 297.) Subsequent entries are the command line
arguments. The final entry in the argv array is a NULL pointer.
2.2 Basic Command-Line Processing 29
argc indicates how many arguments there are; since C is zero-based, it is always true
that ‘argv[argc] == NULL’. Because of this, particularly in Unix code, you will see
different ways of checking for the end of arguments, such as looping until a counter is
greater than or equal to argc, or until ‘argv[i] == 0’ or while ‘*argv != NULL’ and
so on. These are all equivalent.
Only 23 lines! There are two points of interest. First, decrementing argc and simul-
taneously incrementing argv (lines 12 and 13) are common ways of skipping initial
arguments. Second, the check for -n (line 10) is simplistic. -no-newline-at-the-
end also works. (Compile it and try it!)
Manual option parsing is common in V7 code because the getopt() function hadn’t
been invented yet.
Finally, here and in other places throughout the book, we see use of the register
keyword. At one time, this keyword provided a hint to the compiler that the given
variables should be placed in CPU registers, if possible. Use of this keyword is obsolete;
modern compilers all base register assignment on analysis of the source code, ignoring
the register keyword. We’ve chosen to leave code using it alone, but you should be
aware that it has no real use anymore.2
The arguments argc and argv are normally passed straight from those of main().
optstring is a string of option letters. If any letter in the string is followed by a colon,
then that option is expected to have an argument.
2 When we asked Jim Meyering, the Coreutils maintainer, about instances of register in the GNU Coreutils,
he gave us an interesting response. He removes them when modifying code, but otherwise leaves them alone to
make it easier to integrate changes submitted against existing versions.
2.3 Option Parsing: getopt() and getopt_long() 31
To use getopt(), call it repeatedly from a while loop until it returns -1. Each time
that it finds a valid option letter, it returns that letter. If the option takes an argument,
optarg is set to point to it. Consider a program that accepts a -a option that doesn’t
take an argument and a -b argument that does:
int oc; /* option character */
char *b_opt_arg;
People being human, it is inevitable that programs will be invoked incorrectly, either
with an invalid option or with a missing option argument. In the normal case, getopt()
32 Chapter 2 • Arguments, Options, and the Environment
prints its own messages for these cases and returns the '?' character. However, you
can change its behavior in two ways.
First, by setting opterr to 0 before invoking getopt(), you can force getopt()
to remain silent when it finds a problem.
Second, if the first character in the optstring argument is a colon, then getopt()
is silent and it returns a different character depending upon the error, as follows:
Invalid option
getopt() returns a '?' and optopt contains the invalid option character. (This
is the normal behavior.)
Missing option argument
getopt() returns a ':'. If the first character of optstring is not a colon, then
getopt() returns a '?', making this case indistinguishable from the invalid
option case.
Thus, making the first character of optstring a colon is a good idea since it allows
you to distinguish between “invalid option” and “missing option argument.” The cost
is that using the colon also silences getopt(), forcing you to supply your own error
messages. Here is the previous example, this time with error message handling:
int oc; /* option character */
char *b_opt_arg;
A word about flag or option variable-naming conventions: Much Unix code uses
names of the form xflg for any given option letter x (for example, nflg in the V7
echo; xflag is also common). This may be great for the program’s author, who happens
to know what the x option does without having to check the documentation. But it’s
unkind to someone else trying to read the code who doesn’t know the meaning of all
the option letters by heart. It is much better to use names that convey the option’s
meaning, such as no_newline for echo’s -n option.
As for standard getopt(), if the first character of optstring is a ':', then GNU
getopt() distinguishes between “invalid option” and “missing option argument” by
returning '?' or ':', respectively. The ':' in optstring can be the second character
if the first character is '+' or '-'.
Finally, if an option letter in optstring is followed by two colon characters, then
that option is allowed to have an optional option argument. (Say that three times fast!)
Such an argument is deemed to be present if it’s in the same argv element as the option,
34 Chapter 2 • Arguments, Options, and the Environment
and absent otherwise. In the case that it’s absent, GNU getopt() returns the option
letter and sets optarg to NULL. For example, given—
while ((c = getopt(argc, argv, "ab::")) != 1)
...
—for -bYANKEES, the return value is 'b', and optarg points to "YANKEES", while
for -b or ‘-b YANKEES’, the return value is still 'b' but optarg is set to NULL. In the
latter case, "YANKEES" is a separate command-line argument.
The first three arguments are the same as for getopt(). The next option is a pointer
to an array of struct option, which we refer to as the long options table and which
is described shortly. The longindex parameter, if not set to NULL, points to a variable
which is filled in with the index in longopts of the long option that was found. This
is useful for error diagnostics, for example.
struct option {
const char *name;
int has_arg;
int *flag;
int val;
};
Each long option has a single entry with the values appropriately filled in. The last
element in the array should have zeros for all the values. The array need not be sorted;
getopt_long() does a linear search. However, sorting it by long name may make it
easier for a programmer to read.
36 Chapter 2 • Arguments, Options, and the Environment
TABLE 2.1
Values for has_arg
The use of flag and val seems confusing at first encounter. Let’s step back for a
moment and examine why it works the way it does. Most of the time, option processing
consists of setting different flag variables when different option letters are seen, like so:
while ((c = getopt(argc, argv, ":af:hv")) != -1) {
switch (c) {
case 'a':
do_all = 1;
break;
case 'f':
myfile = optarg;
break;
case 'h':
do_help = 1;
break;
case 'v':
do_verbose = 1;
break;
... Error handling code here
}
}
When flag is not NULL, getopt_long() sets the variable for you. This reduces the
three cases in the previous switch to one case. Here is an example long options table
and the code to go with it:
int do_all, do_help, do_verbose; /* flag variables */
char *myfile;
Notice that the value passed for the optstring argument no longer contains 'a',
'h', or 'v'. This means that the corresponding short options are not accepted. To allow
both long and short options, you would have to restore the corresponding cases from
the first example to the switch.
Practically speaking, you should write your programs such that each short option
also has a corresponding long option. In this case, it’s easiest to have flag be NULL and
val be the corresponding single letter.
With this change, -Wall is the same as --all and -Wfile=myfile is the same as
--file=myfile. The use of a semicolon makes it possible for a program to use -W as
a regular option, if desired. (For example, GCC uses it as a regular option, whereas
gawk uses it for POSIX conformance.)
TABLE 2.2
getopt_long() return values
Finally, we enhance the previous example code, showing the full switch statement:
int do_all, do_help, do_verbose; /* flag variables */
char *myfile, *user; /* input file, user name */
#if 0
case 1:
/*
* Use this case if getopt_long() should go through all
* arguments. If so, add a leading '-' character to optstring.
* Actual code, if any, goes here.
*/
break;
#endif
case ':': /* missing option argument */
fprintf(stderr, "%s: option `-%c' requires an argument\n",
argv[0], optopt);
break;
case '?':
default: /* invalid option */
fprintf(stderr, "%s: option `-%c' is invalid: ignored\n",
argv[0], optopt);
break;
}
}
In your programs, you may wish to have comments for each option letter explaining
what each one does. However, if you’ve used descriptive variable names for each option
letter, comments are not as necessary. (Compare do_verbose to vflg.)
3 See http://sources.redhat.com.
40 Chapter 2 • Arguments, Options, and the Environment
You may be wondering, “Gee, I already use GNU/Linux. Why should I include
getopt_long() in my executable, making it bigger, if the routine is already in the C
library?” That’s a good question. However, there’s nothing to worry about. The source
code is set up so that if it’s compiled on a system that uses GLIBC, the compiled files
will not contain any code! Here’s the proof, on our system:
$ uname -a Show system name and type
Linux example 2.4.18-14 #1 Wed Sep 4 13:35:50 EDT 2002 i686 i686 i386 GNU/Linux
$ ls -l getopt.o getopt1.o Show file sizes
-rw-r--r-- 1 arnold devel 9836 Mar 24 13:55 getopt.o
-rw-r--r-- 1 arnold devel 10324 Mar 24 13:55 getopt1.o
$ size getopt.o getopt1.o Show sizes included in executable
text data bss dec hex filename
0 0 0 0 0 getopt.o
0 0 0 0 0 getopt1.o
The size command prints the sizes of the various parts of a binary object or exe-
cutable file. We explain the output in Section 3.1, “Linux/Unix Address Space,” page 52.
What’s important to understand right now is that, despite the nonzero sizes of the files
themselves, they don’t contribute anything to the final executable. (We think this is
pretty neat.)
Of course, the disadvantage to using environment variables is that they can silently
change a program’s behavior. Jim Meyering, the maintainer of the Coreutils, put it
this way:
It makes it easy for the user to customize how the program works without
changing how the program is invoked. That can be both a blessing and a
curse. If you write a script that depends on your having a certain environment
variable set, but then have someone else use that same script, it may well fail
(or worse, silently produce invalid results) if that other person doesn’t have
the same environment settings.
Occasionally, environment variables exist, but with empty values. In this case, the
return value will be non-NULL, but the first character pointed to will be the zero byte,
which is the C string terminator, '\0'. Your code should be careful to check that the
return value pointed to is not NULL. Even if it isn’t NULL, also check that the string is
not empty if you intend to use its value for something. In any case, don’t just blindly
use the returned value.
42 Chapter 2 • Arguments, Options, and the Environment
It’s possible that a variable already exists in the environment. If the third argument
is true (nonzero), then the supplied value overwrites the previous one. Otherwise, it
doesn’t. The return value is -1 if there was no memory for the new variable, and 0
otherwise. setenv() makes private copies of both the variable name and the new value
for storing in the environment.
A simpler alternative to setenv() is putenv(), which takes a single "name=value"
string and places it in the environment:
if (putenv("PATH=/bin:/usr/bin:/usr/ucb") != 0) {
/* handle failure */
}
putenv() blindly replaces any previous value for the same variable. Also, and perhaps
more importantly, the string passed to putenv() is placed directly into the environment.
This means that if your code later modifies this string (for example, if it was an array,
not a string constant), the environment is modified also. This in turn means that you
should not use a local variable as the parameter for putenv(). For all these reasons
setenv() is preferred.
#include <stdio.h>
if (environ != NULL)
for (i = 0; environ[i] != NULL; i++)
printf("%s\n", environ[i]);
return 0;
}
Although it’s unlikely to happen, this program makes sure that environ isn’t NULL
before attempting to use it.
Variables are kept in the environment in random order. Although some Unix shells
keep the environment sorted by variable name, there is no formal requirement that this
be so, and many shells don’t keep them sorted.
44 Chapter 2 • Arguments, Options, and the Environment
You can then use envp as you would have used environ. Although you may see this
occasionally in old code, we don’t recommend its use; environ is the official, standard,
portable way to access the entire environment, should you need to do so.
$ env - PATH=/bin:/usr/bin myprog arg1 Clear environment, add PATH, run program
$ env -u IFS PATH=/bin:/usr/bin myprog arg1 Unset IFS, add PATH, run program
The code begins with a standard GNU copyright statement and explanatory comment.
We have omitted both for brevity. (The copyright statement is discussed in Appendix C,
“GNU General Public License,” page 657. The --help output shown previously is
enough to understand how the program works.) Following the copyright and comments
2.4 The Environment 45
are header includes and declarations. The ‘N_("string")’ macro invocation (line 93)
is for use in internationalization and localization of the software, topics covered in
Chapter 13, “Internationalization and Localization,” page 485. For now, you can treat
it as if it were the contained string constant.
80 #include <config.h>
81 #include <stdio.h>
82 #include <getopt.h>
83 #include <sys/types.h>
84 #include <getopt.h>
85
86 #include "system.h"
87 #include "error.h"
88 #include "closeout.h"
89
90 /* The official name of this program (e.g., no `g' prefix). */
91 #define PROGRAM_NAME "env"
92
93 #define AUTHORS N_ ("Richard Mlynarik and David MacKenzie")
94
95 int putenv ();
96
97 extern char **environ;
98
99 /* The name by which this program was run. */
100 char *program_name;
101
102 static struct option const longopts[] =
103 {
104 {"ignore-environment", no_argument, NULL, 'i'},
105 {"unset", required_argument, NULL, 'u'},
106 {GETOPT_HELP_OPTION_DECL},
107 {GETOPT_VERSION_OPTION_DECL},
108 {NULL, 0, NULL, 0}
109 };
The GNU Coreutils contain a large number of programs, many of which perform
the same common tasks (for example, argument parsing). To make maintenance easier,
many common idioms are defined as macros. GETOPT_HELP_OPTION_DECL and
GETOPT_VERSION_OPTION (lines 106 and 107) are two such. We examine their defini-
tions shortly. The first function, usage(), prints the usage information and exits.
The _("string") macro (line 115, and used throughout the program) is also for
internationalization, and for now you should also treat it as if it were the contained
string constant.
46 Chapter 2 • Arguments, Options, and the Environment
111 void
112 usage (int status)
113 {
114 if (status != 0)
115 fprintf (stderr, _("Try `%s --help' for more information.\n"),
116 program_name);
117 else
118 {
119 printf (_("\
120 Usage: %s [OPTION]... [-] [NAME=VALUE]... [COMMAND [ARG]...]\n"),
121 program_name);
122 fputs (_("\
123 Set each NAME to VALUE in the environment and run COMMAND.\n\
124 \n\
125 -i, --ignore-environment start with an empty environment\n\
126 -u, --unset=NAME remove variable from the environment\n\
127 "), stdout);
128 fputs (HELP_OPTION_DESCRIPTION, stdout);
129 fputs (VERSION_OPTION_DESCRIPTION, stdout);
130 fputs (_("\
131 \n\
132 A mere - implies -i. If no COMMAND, print the resulting environment.\n\
133 "), stdout);
134 printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
135 }
136 exit (status);
137 }
The first part of main() declares variables and sets up the internationalization. The
functions setlocale(), bindtextdomain(), and textdomain() (lines 147–149)
are all discussed in Chapter 13, “Internationalization and Localization,” page 485. Note
that this program does use the envp argument to main() (line 140). It is the only one
of the Coreutils programs to do so. Finally, the call to atexit() on line 151 (see Sec-
tion 9.1.5.3, “Exiting Functions,” page 302) registers a Coreutils library function that
flushes all pending output and closes stdout, reporting a message if there were problems.
The next bit processes the command-line arguments, using getopt_long().
139 int
140 main (register int argc, register char **argv, char **envp)
141 {
142 char *dummy_environ[1];
143 int optc;
144 int ignore_environment = 0;
145
146 program_name = argv[0];
147 setlocale (LC_ALL, "");
148 bindtextdomain (PACKAGE, LOCALEDIR);
149 textdomain (PACKAGE);
150
151 atexit (close_stdout);
2.4 The Environment 47
152
153 while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1)
154 {
155 switch (optc)
156 {
157 case 0:
158 break;
159 case 'i':
160 ignore_environment = 1;
161 break;
162 case 'u':
163 break;
164 case_GETOPT_HELP_CHAR;
165 case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
166 default:
167 usage (2);
168 }
169 }
170
171 if (optind != argc && !strcmp (argv[optind], "-"))
172 ignore_environment = 1;
Here are the macros, from src/sys2.h in the Coreutils distribution, that define
the declarations we saw earlier and the ‘case_GETOPT_xxx’ macros used above (lines
164–165):
/* Factor out some of the common --help and --version processing code. */
/* These enum values cannot possibly conflict with the option values
ordinarily used by commands, including CHAR_MAX + 1, etc. Avoid
CHAR_MIN - 1, as it may equal -1, the getopt end-of-options value. */
enum
{
GETOPT_HELP_CHAR = (CHAR_MIN - 2),
GETOPT_VERSION_CHAR = (CHAR_MIN - 3)
};
#define GETOPT_HELP_OPTION_DECL \
"help", no_argument, 0, GETOPT_HELP_CHAR
#define GETOPT_VERSION_OPTION_DECL \
"version", no_argument, 0, GETOPT_VERSION_CHAR
#define case_GETOPT_HELP_CHAR \
case GETOPT_HELP_CHAR: \
usage (EXIT_SUCCESS); \
break;
The upshot of this code is that --help prints the usage message and --version
prints version information. Both exit successfully. (“Success” and “failure” exit statuses
are described in Section 9.1.5.1, “Defining Process Exit Status,” page 300.) Given that
the Coreutils have dozens of utilities, it makes sense to factor out and standardize as
much repetitive code as possible.
Returning to env.c:
174 environ = dummy_environ;
175 environ[0] = NULL;
176
177 if (!ignore_environment)
178 for (; *envp; envp++)
179 putenv (*envp);
180
181 optind = 0; /* Force GNU getopt to re-initialize. */
182 while ((optc = getopt_long (argc, argv, "+iu:", longopts, NULL)) != -1)
183 if (optc == 'u')
184 putenv (optarg); /* Requires GNU putenv. */
185
186 if (optind != argc && !strcmp (argv[optind], "-")) Skip options
187 ++optind;
188
189 while (optind < argc && strchr (argv[optind], '=')) Set environment variables
190 putenv (argv[optind++]);
191
192 /* If no program is specified, print the environment and exit. */
193 if (optind == argc)
194 {
195 while (*environ)
196 puts (*environ++);
197 exit (EXIT_SUCCESS);
198 }
Lines 174–179 copy the existing environment into a fresh copy of the environment.
The global variable environ is set to point to an empty local array. The envp parameter
maintains access to the original environment.
Lines 181–184 remove any environment variables as requested by the -u option.
The program does this by rescanning the command line and removing names listed
there. Environment variable removal relies on the GNU putenv() behavior discussed
earlier: that when called with a plain variable name, putenv() removes the environment
variable.
After any options, new or replacement environment variables are supplied on the
command line. Lines 189–190 continue scanning the command line, looking for envi-
ronment variable settings of the form ‘name=value’.
2.5 Summary 49
Upon reaching line 192, if nothing is left on the command line, env is supposed to
print the new environment, and exit. It does so (lines 195–197).
If arguments are left, they represent a command name to run and arguments to pass
to that new command. This is done with the execvp() system call (line 200), which
replaces the current program with the new one. (This call is discussed in Section 9.1.4,
“Starting New Programs: The exec() Family,” page 293; don’t worry about the details
for now.) If this call returns to the current program, it failed. In such a case, env prints
an error message and exits.
200 execvp (argv[optind], &argv[optind]);
201
202 {
203 int exit_status = (errno == ENOENT ? 127 : 126);
204 error (0, errno, "%s", argv[optind]);
205 exit (exit_status);
206 }
207 }
The exit status values, 126 and 127 (determined on line 203), conform to POSIX.
127 means the program that execvp() attempted to run didn’t exist. (ENOENT means
the file doesn’t have an entry in the directory.) 126 means that the file exists, but
something else went wrong.
2.5 Summary
• C programs access their command-line arguments through the parameters argc
and argv. The getopt() function provides a standard way for consistent parsing
of options and their arguments. The GNU version of getopt() provides some
extensions, and getopt_long() and getopt_long_only() make it possible to
easily parse long-style options.
• The environment is a set of ‘name=value’ pairs that each program inherits from
its parent. Programs can, at their author’s whim, use environment variables to
change their behavior, in addition to any command-line arguments. Standard
routines (getenv(), setenv(), putenv(), and unsetenv()) exist for retrieving
environment variable values, changing them, or removing them. If necessary, the
entire environment is available through the external variable environ or
through the char **envp third argument to main(). The latter technique is
discouraged.
50 Chapter 2 • Arguments, Options, and the Environment
Exercises
1. Assume a program accepts options -a, -b, and -c, and that -b requires an ar-
gument. Write the manual argument parsing code for this program, without
using getopt() or getopt_long(). Accept -- to end option processing.
Make sure that -ac works, as do -bYANKEES, -b YANKEES, and -abYANKEES.
Test your program.
2. Implement getopt(). For the first version, don’t worry about the case in which
‘optstring[0] == ':'’. You may also ignore opterr.
3. Add code for ‘optstring[0] == ':'’ and opterr to your version of
getopt().
4. Print and read the GNU getopt.h, getopt.c and getopt1.c files.
5. Write a program that declares both environ and envp and compares their
values.
6. Parsing command line arguments and options is a wheel that many people
can’t refrain from reinventing. Besides getopt() and getopt_long(), you
may wish to examine different argument-parsing packages, such as:
•The Plan 9 From Bell Labs arg(2) argument-parsing library,4
•Argp,5
•Argv,6
•Autoopts,7
•GNU Gengetopt,8
•Opt,9
•Popt.10 See also the popt(3) manpage on a GNU/Linux system.
7. Extra credit: Why can’t a C compiler completely ignore the register keyword?
Hint: What operation cannot be applied to a register variable?
4 http://plan9.bell-labs.com/magic/man2html/2/arg
5 http://www.gnu.org/manual/glibc/html_node/Argp.html
6 http://256.com/sources/argv
7 http://autogen.sourceforge.net/autoopts.html
8 ftp://ftp.gnu.org/gnu/gengetopt/
9 http://nis-www.lanl.gov/~jt/Software/opt/opt-3.19.tar.gz
10 http://freshmeat.net/projects/popt/?topic_id=809
3
User-Level
Memory
Management
In this chapter
51
ithout memory for storing data, it’s impossible for a program to get any
W work done. (Or rather, it’s impossible to get any useful work done.) Real-
world programs can’t afford to rely on fixed-size buffers or arrays of data structures.
They have to be able to handle inputs of varying sizes, from small to large. This in
turn leads to the use of dynamically allocated memory—memory allocated at runtime
instead of at compile time. This is how the GNU “no arbitrary limits” principle is
put into action.
Because dynamically allocated memory is such a basic building block for real-world
programs, we cover it early, before looking at everything else there is to do. Our
discussion focuses exclusively on the user-level view of the process and its memory;
it has nothing to do with CPU architecture.
52
3.1 Linux/Unix Address Space 53
Zero-initialized data
Global and statically allocated data that are initialized to zero by default are kept
in what is colloquially called the BSS area of the process.1 Each process running
the same program has its own BSS area. When running, the BSS data are placed
in the data segment. In the executable file, they are stored in the BSS section.
The format of a Linux/Unix executable is such that only variables that are initialized
to a nonzero value occupy space in the executable’s disk file. Thus, a large array
declared ‘static char somebuf[2048];’, which is automatically zero-filled,
does not take up 2 KB worth of disk space. (Some compilers have options that let
you place zero-initialized data into the data segment.)
Heap
The heap is where dynamic memory (obtained by malloc() and friends) comes
from. As memory is allocated on the heap, the process’s address space grows, as
you can see by watching a running program with the ps command.
Although it is possible to give memory back to the system and shrink a process’s
address space, this is almost never done. (We distinguish between releasing no-
longer-needed dynamic memory and shrinking the address space; this is discussed
in more detail later in this chapter.)
It is typical for the heap to “grow upward.” This means that successive items that
are added to the heap are added at addresses that are numerically greater than
previous items. It is also typical for the heap to start immediately after the BSS
area of the data segment.
Stack
The stack segment is where local variables are allocated. Local variables are all
variables declared inside the opening left brace of a function body (or other left
brace) that aren’t defined as static.
On most architectures, function parameters are also placed on the stack, as well
as “invisible” bookkeeping information generated by the compiler, such as room
for a function return value and storage for the return address representing the return
from a function to its caller. (Some architectures do all this with registers.)
1 BSS is an acronym for “Block Started by Symbol,” a mnemonic from the IBM 7094 assembler.
54 Chapter 3 • User-Level Memory Management
It is the use of a stack for function parameters and return values that makes it
convenient to write recursive functions (functions that call themselves).
Variables stored on the stack “disappear” when the function containing them re-
turns; the space on the stack is reused for subsequent function calls.
On most modern architectures, the stack “grows downward,” meaning that items
deeper in the call chain are at numerically lower addresses.
When a program is running, the initialized data, BSS, and heap areas are usually
placed into a single contiguous area: the data segment. The stack segment and code
segment are separate from the data segment and from each other. This is illustrated in
Figure 3.1.
High Address
Program Stack
STACK SEGMENT
Possible ''hole"
in address space
Heap
Globals and
Static variables
(Data)
Low Address
Executable code
(shared)
TEXT SEGMENT
FIGURE 3.1
Linux/Unix process address space
3.1 Linux/Unix Address Space 55
Although it’s theoretically possible for the stack and heap to grow into each other,
the operating system prevents that event, and any program that tries to make it happen
is asking for trouble. This is particularly true on modern systems, on which process
address spaces are large and the gap between the top of the stack and the end of the
heap is a big one. The different memory areas can have different hardware memory
protection assigned to them. For example, the text segment might be marked “execute
only,” whereas the data and stack segments would have execute permission disabled.
This practice can prevent certain kinds of security attacks. The details, of course, are
hardware and operating-system specific and likely to change over time. Of note is that
both Standard C and C++ allow const items to be placed in read-only memory. The
relationship among the different segments is summarized in Table 3.1.
TABLE 3.1
Executable program segments and their locations
The size program prints out the size in bytes of each of the text, data, and BSS
sections, along with the total size in decimal and hexadecimal. (The ch03-memaddr.c
program is shown later in this chapter; see Section 3.2.5, “Address Space Examination,”
page 78.)
$ cc -O ch03-memaddr.c -o ch03-memaddr Compile the program
$ ls -l ch03-memaddr Show total size
-rwxr-xr-x 1 arnold devel 12320 Nov 24 16:45 ch03-memaddr
$ size ch03-memaddr Show component sizes
text data bss dec hex filename
1458 276 8 1742 6ce ch03-memaddr
$ strip ch03-memaddr Remove symbols
$ ls -l ch03-memaddr Show total size again
-rwxr-xr-x 1 arnold devel 3480 Nov 24 16:45 ch03-memaddr
$ size ch03-memaddr Component sizes haven’t changed
text data bss dec hex filename
1458 276 8 1742 6ce ch03-memaddr
56 Chapter 3 • User-Level Memory Management
The total size of what gets loaded into memory is only 1742 bytes, in a file that is
12,320 bytes long. Most of that space is occupied by the symbols, a list of the program’s
variables and function names. (The symbols are not loaded into memory when the
program runs.) The strip program removes the symbols from the object file. This can
save significant disk space for a large program, at the cost of making it impossible to
debug a core dump2 should one occur. (On modern systems this isn’t worth the trouble;
don’t use strip.) Even after removing the symbols, the file is still larger than what gets
loaded into memory since the object file format maintains additional data about the
program, such as what shared libraries it may use, if any.3
Finally, we’ll mention that threads represent multiple threads of execution within a
single address space. Typically, each thread has its own stack, and a way to get thread
local data, that is, dynamically allocated data for private use by the thread. We don’t
otherwise cover threads in this book, since they are an advanced topic.
2 A core dump is the memory image of a running process created when the process terminates unexpectedly. It may
be used later for debugging. Unix systems named the file core, and GNU/Linux systems use core.pid, where
pid is the process ID of the process that died.
3 The description here is a deliberate simplification. Running programs occupy much more space than the size
program indicates, since shared libraries are included in the address space. Also, the data segment will grow as a
program allocates memory.
3.2 Memory Allocation 57
of a certain initial size, you can change its size with the realloc() function. Dynamic
memory is released with the free() function.
Debugging the use of dynamic memory is an important topic in its own right. We
discuss tools for this purpose in Section 15.5.2, “Memory Allocation Debuggers,”
page 612.
p = buf;
while (some condition) {
...
p += something ;
...
where = p - buf; /* what index are we at? */
}
The <stdlib.h> header file declares many of the standard C library routines and
types (such as size_t), and it also defines the preprocessor constant NULL, which rep-
resents the “null” or invalid pointer. (This is a zero value, such as 0 or ‘((void *) 0)’.
58 Chapter 3 • User-Level Memory Management
The C++ idiom is to use 0 explicitly; in C, however, NULL is preferred, and we find it
to be much more readable for C code.)
The steps shown here are quite boilerplate. The order is as follows:
1. Declare a pointer of the proper type to point to the allocated memory.
2. Calculate the size in bytes of the memory to be allocated. This involves multi-
plying a count of objects needed by the size of the individual object. This size
in turn is retrieved from the C sizeof operator, which exists for this purpose
(among others). Thus, while the size of a particular struct may vary across
compilers and architectures, sizeof always returns the correct value and the
source code remains correct and portable.
When allocating arrays for character strings or other data of type char, it is
not necessary to multiply by sizeof(char), since by definition this is always
1. But it won’t hurt anything either.
3. Allocate the storage by calling malloc(), assigning the function’s return value
to the pointer variable. It is good practice to cast the return value of malloc()
3.2 Memory Allocation 59
to that of the variable being assigned to. In C it’s not required (although the
compiler may generate a warning). We strongly recommend always casting the
return value.
Note that in C++, assignment of a pointer value of one type to a pointer of
another type does requires a cast, whatever the context. For dynamic memory
management, C++ programs should use new and delete, to avoid type prob-
lems, and not malloc() and free().
4. Check the return value. Never assume that memory allocation will succeed. If
the allocation fails, malloc() returns NULL. If you use the value without
checking, it is likely that your program will immediately die from a segmentation
violation (or segfault), which is an attempt to use memory not in your address
space.
If you check the return value, you can at least print a diagnostic message and
terminate gracefully. Or you can attempt some other method of recovery.
Once we’ve allocated memory and set coordinates to point to it, we can then treat
coordinates as if it were an array, although it’s really a pointer:
int cur_x, cur_y, cur_z;
size_t an_index;
an_index = something;
cur_x = coordinates[an_index].x;
cur_y = coordinates[an_index].y;
cur_z = coordinates[an_index].z;
The compiler generates correct code for indexing through the pointer to retrieve the
members of the structure at coordinates[an_index].
NOTE The memory returned by malloc() is not initialized. It can contain any
random garbage. You should immediately initialize the memory with valid data
or at least with zeros. To do the latter, use memset() (discussed in Section 12.2,
“Low-Level Memory: The memXXX() Functions,” page 432):
memset(coordinates, '\0', amount);
Another option is to use calloc(), described shortly.
This approach guarantees that the malloc() will allocate the correct amount of
memory without your having to consult the declaration of pointer. If pointer’s type
later changes, the sizeof operator automatically ensures that the count of bytes to al-
locate stays correct. (Geoff’s technique omits the cast that we just discussed. Having
the cast there also ensures a diagnostic if pointer’s type changes and the call to
malloc() isn’t updated.)
This call won’t work, and it’s likely to lead to disastrous consequences, such as a
crash. (This is because many malloc() implementations keep “bookkeeping”
information in front of the returned data. When free() goes to use that informa-
tion, it will find invalid data there. Other implementations have the bookkeeping
information at the end of the allocated chunk; the same issues apply.)
Buffer overruns and underruns
Accessing memory outside an allocated chunk also leads to undefined behavior,
again because this is likely to be bookkeeping information or possibly memory
that’s not even in the address space. Writing into such memory is much worse,
since it’s likely to destroy the bookkeeping data.
Failure to free memory
Any dynamic memory that’s not needed should be released. In particular, memory
that is allocated inside loops or recursive or deeply nested function calls should
be carefully managed and released. Failure to take care leads to memory leaks,
whereby the process’s memory can grow without bounds; eventually, the process
dies from lack of memory.
This situation can be particularly pernicious if memory is allocated per input
record or as some other function of the input: The memory leak won’t be noticed
when run on small inputs but can suddenly become obvious (and embarrassing)
when run on large ones. This error is even worse for systems that must run contin-
uously, such as telephone switching systems. A memory leak that crashes such a
system can lead to significant monetary or other damage.
Even if the program never dies for lack of memory, constantly growing programs
suffer in performance, because the operating system has to manage keeping in-use
data in physical memory. In the worst case, this can lead to behavior known as
thrashing, whereby the operating system is so busy moving the contents of the
address space into and out of physical memory that no real work gets done.
62 Chapter 3 • User-Level Memory Management
While it’s possible for free() to hand released memory back to the system and shrink
the process address space, this is almost never done. Instead, the released memory is
kept available for allocation by the next call to malloc(), calloc(), or realloc().
Given that released memory continues to reside in the process’s address space, it may
pay to zero it out before releasing it. Security-sensitive programs may choose to do this,
for example.
See Section 15.5.2, “Memory Allocation Debuggers,” page 612, for discussion of a
number of useful dynamic-memory debugging tools.
coordinates = newcoords;
/* continue using coordinates ... */
As with malloc(), the steps are boilerplate in nature and are similar in concept:
1. Compute the new size to allocate, in bytes.
2. Call realloc() with the original pointer obtained from malloc() (or from
calloc() or an earlier call to realloc()) and the new size.
3.2 Memory Allocation 63
3. Cast and assign the return value of realloc(). More discussion of this shortly.
4. As for malloc(), check the return value to make sure it’s not NULL. Any
memory allocation routine can fail.
When growing a block of memory, realloc() often allocates a new block of the
right size, copies the data from the old block into the new one, and returns a pointer
to the new one.
When shrinking a block of data, realloc() can often just update the internal
bookkeeping information and return the same pointer. This saves having to copy the
original data. However, if this happens, don’t assume you can still use the memory beyond
the new size!
In either case, you can assume that if realloc() doesn’t return NULL, the old data
has been copied for you into the new memory. Furthermore, the old pointer is no
longer valid, as if you had called free() with it, and you should not use it. This is true
of all pointers into that block of data, not just the particular one used to call free().
You may have noticed that our example code used a separate variable to point to the
changed storage block. It would be possible (but a bad idea) to use the same initial
variable, like so:
coordinates = realloc(coordinates, new_amount);
This is a bad idea for the following reason. When realloc() returns NULL, the
original pointer is still valid; it’s safe to continue using that memory. However, if you
reuse the same variable and realloc() returns NULL, you’ve now lost the pointer to
the original memory. That memory can no longer be used. More important, that
memory can no longer be freed! This creates a memory leak, which is to be avoided.
There are some special cases for the Standard C version of realloc(): When the
ptr argument is NULL, realloc() acts like malloc() and allocates a fresh block of
storage. When the size argument is 0, realloc() acts like free() and releases the
memory that ptr points to. Because (a) this can be confusing and (b) older systems
don’t implement this feature, we recommend using malloc() when you mean
malloc() and free() when you mean free().
64 Chapter 3 • User-Level Memory Management
Here is another, fairly subtle, “gotcha.”4 Consider a routine that maintains a static
pointer to some dynamically allocated data, which the routine occasionally has to grow.
It may also maintain automatic (that is, local) pointers into this data. (For brevity, we
omit error checking code. In production code, don’t do that.) For example:
void manage_table(void)
{
static struct table *table;
struct table *cur, *p;
int i;
size_t count;
...
table = (struct table *) malloc(count * sizeof(struct table));
/* fill table */
cur = & table[i]; /* point at i'th item */
...
cur->i = j; /* use pointer */
...
if (some condition) { /* need to grow table */
count += count/2;
p = (struct table *) realloc(table, count * sizeof(struct table));
table = p;
}
This looks straightforward; manage_table() allocates the data, uses it, changes the
size, and so on. But there are some problems that don’t jump off the page (or the screen)
when you are looking at this code.
In the line marked ‘PROBLEM 1’, the cur pointer is used to update a table element.
However, cur was assigned on the basis of the initial value of table. If some
condition was true and realloc() returned a different block of memory, cur now
points into the original, freed memory! Whenever table changes, any pointers into
the memory need to be updated too. What’s missing here is the statement ‘cur = &
table[i];’ after table is reassigned following the call to realloc().
The two lines marked ‘PROBLEM 2’ are even more subtle. In particular, suppose
other_routine() makes a recursive call to manage_table(). The table variable
could be changed again, completely invisibly! Upon return from other_routine(),
the value of cur could once again be invalid.
One might think (as we did) that the only solution is to be aware of this and supply
a suitably commented reassignment to cur after the function call. However, Brian
Kernighan kindly set us straight. If we use indexing, the pointer maintenance issue
doesn’t even arise:
table = (struct table *) malloc(count * sizeof(struct table));
/* fill table */
...
table[i].i = j; /* Update a member of the i'th element */
...
if (some condition) { /* need to grow table */
count += count/2;
p = (struct table *) realloc(table, count * sizeof(struct table));
table = p;
}
Using indexing doesn’t solve the problem if you have a global copy of the original
pointer to the allocated data; in that case, you still have to worry about updating your
global structures after calling realloc().
NOTE As with malloc(), when you grow a piece of memory, the newly
allocated memory returned from realloc() is not zero-filled. You must clear
it yourself with memset() if that’s necessary, since realloc() only allocates
the fresh memory; it doesn’t do anything else.
Conceptually, at least, the calloc() code is fairly simple. Here is one possible
implementation:
66 Chapter 3 • User-Level Memory Management
if (p != NULL) If it worked …
memset(p, '\0', total); Fill it with zeros
Many experienced programmers prefer to use calloc() since then there’s never any
question about the contents of the newly allocated memory.
Also, if you know you’ll need zero-filled memory, you should use calloc(), because
it’s possible that the memory malloc() returns is already zero-filled. Although you,
the programmer, can’t know this, calloc() can know about it and avoid the call
to memset().
In three short paragraphs, Richard Stallman has distilled the important principles
for doing dynamic memory management with malloc(). It is the use of dynamic
3.2 Memory Allocation 67
memory and the “no arbitrary limits” principle that makes GNU programs so robust
and more capable than their Unix counterparts.
We do wish to point out that the C standard requires realloc() to not destroy the
original block if it returns NULL.
The nextfree variable points to a linked list of NODE structures. The getnode() macro
pulls the first structure off the list if one is there. Otherwise, it calls more_nodes() to
allocate a new list of free NODEs. The freenode() macro releases a NODE by putting it
at the head of the list.
NOTE When first writing your application, do it the simple way: use malloc()
and free() directly. If and only if profiling your program shows you that it’s
spending a significant amount of time in the memory-allocation functions
should you consider writing a private allocator.
The size field tracks the size of the entire buffer, and fp is the FILE pointer for the
input file. The floc structure isn’t of interest for studying the routine.
The function returns the number of lines in the buffer. (The line numbers here are
relative to the start of the function, not the source file.)
1 static long
2 readline (ebuf) static long readline(struct ebuffer *ebuf)
3 struct ebuffer *ebuf;
4 {
5 char *p;
6 char *end;
7 char *start;
8 long nlines = 0;
9
10 /* The behaviors between string and stream buffers are different enough to
11 warrant different functions. Do the Right Thing. */
12
13 if (!ebuf->fp)
14 return readstring (ebuf);
15
16 /* When reading from a file, we always start over at the beginning of the
17 buffer for each new line. */
18
19 p = start = ebuf->bufstart;
20 end = p + ebuf->size;
21 *p = '\0';
3.2 Memory Allocation 69
We start by noticing that GNU Make is written in K&R C for maximal portability.
The initial part declares variables, and if the input is coming from a string (such as
from the expansion of a macro), the code hands things off to a different function,
readstring() (lines 13 and 14). The test ‘!ebuf->fp’ (line 13) is a shorter (and less
clear, in our opinion) test for a null pointer; it’s the same as ‘ebuf->fp == NULL’.
Lines 19–21 initialize the pointers, and insert a NUL byte, which is the C string
terminator character, at the end of the buffer. The function then starts a loop (lines
23–95), which runs as long as there is more input.
23 while (fgets (p, end - p, ebuf->fp) != 0)
24 {
25 char *p2;
26 unsigned long len;
27 int backslash;
28
29 len = strlen (p);
30 if (len == 0)
31 {
32 /* This only happens when the first thing on the line is a '\0'.
33 It is a pretty hopeless case, but (wonder of wonders) Athena
34 lossage strikes again! (xmkmf puts NULs in its makefiles.)
35 There is nothing really to be done; we synthesize a newline so
36 the following line doesn't appear to be part of this line. */
37 error (&ebuf->floc,
38 _("warning: NUL character seen; rest of line ignored"));
39 p[0] = '\n';
40 len = 1;
41 }
The fgets() function (line 23) takes a pointer to a buffer, a count of bytes to read,
and a FILE * variable for the file to read from. It reads one less than the count so that
it can terminate the buffer with '\0'. This function is good since it allows you to avoid
buffer overflows. It stops upon encountering a newline or end-of-file, and if the newline
is there, it’s placed in the buffer. It returns NULL on failure or the (pointer) value of the
first argument on success.
In this case, the arguments are a pointer to the free area of the buffer, the amount
of room left in the buffer, and the FILE pointer to read from.
The comment on lines 32–36 is self-explanatory; if a zero byte is encountered, the
program prints an error message and pretends it was an empty line. After compensating
for the NUL byte (lines 30–41), the code continues.
70 Chapter 3 • User-Level Memory Management
Lines 43–52 increment the pointer into the buffer past the data just read. The code
then checks whether the last character read was a newline. The construct p[-1] (line 48)
looks at the character in front of p, just as p[0] is the current character and p[1] is the
next. This looks strange at first, but if you translate it into terms of pointer math,
*(p-1), it makes more sense, and the indexing form is possibly easier to read.
If the last character was not a newline, this means that we’ve run out of space, and
the code goes off (with goto) to get more (line 49). Otherwise, the line count is
incremented.
54 #if !defined(WINDOWS32) && !defined(_ _MSDOS_ _)
55 /* Check to see if the line was really ended with CRLF; if so ignore
56 the CR. */
57 if ((p - start) > 1 && p[-2] == '\r')
58 {
59 --p;
60 p[-1] = '\n';
61 }
62 #endif
Lines 54–62 deal with input lines that follow the Microsoft convention of ending
with a Carriage Return-Line Feed (CR-LF) combination, and not just a Line Feed (or
newline), which is the Linux/Unix convention. Note that the #ifdef excludes the code
on Microsoft systems; apparently the <stdio.h> library on those systems handles this
conversion automatically. This is also true of other non-Unix systems that support
Standard C.
64 backslash = 0;
65 for (p2 = p - 2; p2 >= start; --p2)
66 {
67 if (*p2 != '\\')
68 break;
69 backslash = !backslash;
70 }
71
3.2 Memory Allocation 71
72 if (!backslash)
73 {
74 p[-1] = '\0';
75 break;
76 }
77
78 /* It was a backslash/newline combo. If we have more space, read
79 another line. */
80 if (end - p >= 80)
81 continue;
82
83 /* We need more space at the end of our buffer, so realloc it.
84 Make sure to preserve the current offset of p. */
85 more_buffer:
86 {
87 unsigned long off = p - start;
88 ebuf->size *= 2;
89 start = ebuf->buffer = ebuf->bufstart = (char *) xrealloc (start,
90 ebuf->size);
91 p = start + off;
92 end = start + ebuf->size;
93 *p = '\0';
94 }
95 }
So far we’ve dealt with the mechanics of getting at least one complete line into the
buffer. The next chunk handles the case of a continuation line. It has to make sure,
though, that the final backslash isn’t part of multiple backslashes at the end of the line.
It tracks whether the total number of such backslashes is odd or even by toggling the
backslash variable from 0 to 1 and back. (Lines 64–70.)
If the number is even, the test ‘! backslash’ (line 72) will be true. In this case, the
final newline is replaced with a NUL byte, and the code leaves the loop.
On the other hand, if the number is odd, then the line contained an even number
of backslash pairs (representing escaped backslashes, \\ as in C), and a final backslash-
newline combination.5 In this case, if at least 80 free bytes are left in the buffer, the
program continues around the loop to read another line (lines 78–81). (The use of
the magic number 80 isn’t great; it would have been better to define and use a symbolic
constant.)
5 This code has the scent of practical experience about it: It wouldn’t be surprising to learn that earlier versions
simply checked for a final backslash before the newline, until someone complained that it didn’t work when there
were multiple backslashes at the end of the line.
72 Chapter 3 • User-Level Memory Management
Upon reaching line 83, the program needs more space in the buffer. Here’s where
the dynamic memory management comes into play. Note the comment about preserving
p (lines 83–84); we discussed this earlier in terms of reinitializing pointers into dynamic
memory. end is also reset. Line 89 resizes the memory.
Note that here the function being called is xrealloc(). Many GNU programs use
“wrapper” functions around malloc() and realloc() that automatically print an
error message and exit if the standard routines return NULL. Such a wrapper might look
like this:
extern const char *myname; /* set in main() */
if (p == NULL) {
fprintf(stderr, "%s: out of memory!\n", myname);
exit(1);
}
}
Thus, if xrealloc() returns, it’s guaranteed to return a valid pointer. (This strategy
complies with the “check every call for errors” principle while avoiding the code clutter
that comes with doing so using the standard routines directly.) In addition, this allows
valid use of the construct ‘ptr = xrealloc(ptr, new_size)’, which we otherwise
warned against earlier.
Note that it is not always appropriate to use such a wrapper. If you wish to handle
errors yourself, you shouldn’t use it. On the other hand, if running out of memory is
always a fatal error, then such a wrapper is quite handy.
97 if (ferror (ebuf->fp))
98 pfatal_with_name (ebuf->floc.filenm);
99
100 /* If we found some lines, return how many.
101 If we didn't, but we did find _something_, that indicates we read the last
102 line of a file with no final newline; return 1.
103 If we read nothing, we're at EOF; return -1. */
104
105 return nlines ? nlines : p == ebuf->bufstart ? -1 : 1;
106 }
3.2 Memory Allocation 73
Finally, the readline() routine checks for I/O errors, and then returns a descriptive
return value. The function pfatal_with_name() (line 98) doesn’t return.
The functions return -1 upon end-of-file or error. The strings hold the terminating
newline or delimiter (if there was one), as well as a terminating zero byte. Using
getline() is easy, as shown in ch03-getline.c:
/* ch03-getline.c --- demonstrate getline(). */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <sys/types.h>
/* main --- read a line and echo it back out until EOF. */
int main(void)
{
char *line = NULL;
size_t size = 0;
ssize_t ret;
return 0;
}
Here it is in action, showing the size of the buffer. The third input and output lines
are purposely long, to force getline() to grow the buffer; thus, they wrap around:
$ ch03-getline Run the program
this is a line
(120) this is a line
And another line.
(120) And another line.
A llllllllllllllllloooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnnnnngggg
gggggggg llliiiiiiiiiiiiiiiiiiinnnnnnnnnnnnnnnnnnnneeeeeeeeee
(240) A llllllllllllllllloooooooooooooooooooooooooooooooonnnnnnnnnnnnnnnng
nnnggggggggggg llliiiiiiiiiiiiiiiiiiinnnnnnnnnnnnnnnnnnnneeeeeeeeee
#include <string.h>
if (copy != NULL)
strcpy(copy, str);
With the 2001 POSIX standard, programmers the world over can breathe a little
easier: This function is now part of POSIX as an XSI extension:
#include <string.h> XSI
The brk() system call actually changes the process’s address space. The address is a
pointer representing the end of the data segment (really the heap area, as shown earlier
in Figure 3.1). Its argument is an absolute logical address representing the new end of
the address space. It returns 0 on success or -1 on failure.
The sbrk() function is easier to use; its argument is the increment in bytes by which
to change the address space. By calling it with an increment of 0, you can determine
where the address space currently ends. Thus, to increase your address space by 32
bytes, use code like this:
char *p = (char *) sbrk(0); /* get current end of address space */
if (brk(p + 32) < 0) {
/* handle error */
}
/* else, change worked */
Practically speaking, you would not use brk() directly. Instead, you would use
sbrk() exclusively to grow (or even shrink) the address space. (We show how to do
this shortly, in Section 3.2.5, “Address Space Examination,” page 78.)
Even more practically, you should never use these routines. A program using them
can’t then use malloc() also, and this is a big problem, since many parts of the standard
library rely on being able to use malloc(). Using brk() or sbrk() is thus likely to
lead to hard-to-find program crashes.
But it’s worth knowing about the low-level mechanics, and indeed, the malloc()
suite of routines is implemented with sbrk() and brk().
The alloca() function allocates size bytes from the stack. What’s nice about this
is that the allocated storage disappears when the function returns. There’s no need to
explicitly free it because it goes away automatically, just as local variables do.
At first glance, alloca() seems like a programming panacea; memory can be allo-
cated that doesn’t have to be managed at all. Like the Dark Side of the Force, this is
indeed seductive. And it is similarly to be avoided, for the following reasons:
• The function is nonstandard; it is not included in any formal standard, either ISO
C or POSIX.
• The function is not portable. Although it exists on many Unix systems and
GNU/Linux, it doesn’t exist on non-Unix systems. This is a problem, since it’s
often important for code to be multiplatform, above and beyond just Linux
and Unix.
• On some systems, alloca() can’t even be implemented. All the world is not an
Intel x86 processor, nor is all the world GCC.
• Quoting the manpage (emphasis added): “The alloca function is machine
and compiler dependent. On many systems its implementation is buggy. Its use is
discouraged.”
• Quoting the manpage again: “On many systems alloca cannot be used inside
the list of arguments of a function call, because the stack space reserved by alloca
would appear on the stack in the middle of the space for the function arguments.”
• It encourages sloppy coding. Careful and correct memory management isn’t hard;
you just to have to think about what you’re doing and plan ahead.
GCC generally uses a built-in version of the function that operates by using inline
code. As a result, there are other consequences of alloca(). Quoting again from
the manpage:
The fact that the code is inlined means that it is impossible to take the address
of this function, or to change its behavior by linking with a different library.
The inlined code often consists of a single instruction adjusting the stack
pointer, and does not check for stack overflow. Thus, there is no NULL error
return.
78 Chapter 3 • User-Level Memory Management
The manual page doesn’t go quite far enough in describing the problem with GCC’s
built-in alloca(). If there’s a stack overflow, the return value is garbage. And you have
no way to tell! This flaw makes GCC’s alloca() impossible to use in robust code.
All of this should convince you to stay away from alloca() for any new code that
you may write. If you’re going to have to write portable code using malloc() and
free() anyway, there’s no reason to also write code using alloca().
34 printf("Data Locations:\n");
35 printf("\tAddress of data_var: %p\n", & data_var);
36
37 printf("BSS Locations:\n");
38 printf("\tAddress of bss_var: %p\n", & bss_var);
39
40 b = sbrk((ptrdiff_t) 32); /* grow address space */
41 nb = sbrk((ptrdiff_t) 0);
42 printf("Heap Locations:\n");
43 printf("\tInitial end of heap: %p\n", b);
44 printf("\tNew end of heap: %p\n", nb);
45
46 b = sbrk((ptrdiff_t) -16); /* shrink it */
47 nb = sbrk((ptrdiff_t) 0);
48 printf("\tFinal end of heap: %p\n", nb);
49 }
50
51 void
52 afunc(void)
53 {
54 static int level = 0; /* recursion level */
55 auto int stack_var; /* automatic variable, on stack */
56
57 if (++level == 3) /* avoid infinite recursion */
58 return;
59
60 printf("\tStack level %d: address of stack_var: %p\n",
61 level, & stack_var);
62 afunc(); /* recursive call */
63 }
This program prints the locations of the two functions main() and afunc() (lines
22–23). It then shows how the stack grows downward, letting afunc() (lines 51–63)
print the address of successive instantiations of its local variable stack_var. (stack_var
is purposely declared auto, to emphasize that it’s on the stack.) It then shows the loca-
tion of memory allocated by alloca() (lines 28–32). Finally it prints the locations of
data and BSS variables (lines 34–38), and then of memory allocated directly through
sbrk() (lines 40–48). Here are the results when the program is run on an Intel
GNU/Linux system:
$ ch03-memaddr
Text Locations:
Address of main: 0x804838c
Address of afunc: 0x80484a8
Stack Locations:
Stack level 1: address of stack_var: 0xbffff864
Stack level 2: address of stack_var: 0xbffff844 Stack grows downward
Start of alloca()'ed array: 0xbffff860
End of alloca()'ed array: 0xbffff87f Addresses are on the stack
80 Chapter 3 • User-Level Memory Management
Data Locations:
Address of data_var: 0x80496b8
BSS Locations:
Address of bss_var: 0x80497c4 BSS is above data variables
Heap Locations:
Initial end of heap: 0x80497c8 Heap is immediately above BSS
New end of heap: 0x80497e8 And grows upward
Final end of heap: 0x80497d8 Address spaces can shrink
3.3 Summary
• Every Linux (and Unix) program has different memory areas. They are stored in
separate parts of the executable program’s disk file. Some of the sections are loaded
into the same part of memory when the program is run. All running copies of the
same program share the executable code (the text segment). The size program
shows the sizes of the different areas for relocatable object files and fully linked
executable files.
• The address space of a running program may have holes in it, and the size of the
address space can change as memory is allocated and released. On modern systems,
address 0 is not part of the address space, so don’t attempt to dereference
NULL pointers.
• At the C level, memory is allocated or reallocated with one of malloc(),
calloc(), or realloc(). Memory is freed with free(). (Although realloc()
can do everything, using it that way isn’t recommended). It is unusual for freed
memory to be removed from the address space; instead, it is reused for
later allocations.
• Extreme care must be taken to
• Free only memory received from the allocation routines,
• Free such memory once and only once,
• Free unused memory, and
• Not “leak” any dynamically allocated memory.
• POSIX provides the strdup() function as a convenience, and GLIBC provides
getline() and getdelim() for reading arbitrary-length lines.
3.4 Exercises 81
• The low-level system call interface functions, brk() and sbrk(), provide direct
but primitive access to memory allocation and deallocation. Unless you are writing
your own storage allocator, you should not use them.
• The alloca() function for allocating memory on the stack exists, but is not rec-
ommended. Like being able to recognize poison ivy, you should know it only so
that you’ll know to avoid it.
Exercises
1. Starting with the structure—
struct line {
size_t buflen;
char *buf;
FILE *fp;
};
—write your own readline() function that will read an any-length line.
Don’t worry about backslash continuation lines. Instead of using fgets() to
read lines, use getc() to read characters one at a time.
2. Does your function preserve the terminating newline? Explain why or why not.
3. How does your function handle lines that end in CR-LF?
4. How do you initialize the structure? With a separate routine? With a document-
ed requirement for specific values in the structure?
5. How do you indicate end-of-file? How do you indicate that an I/O error has
occurred? For errors, should your function print an error message? Explain why
or why not.
6. Write a program that uses your function to test it, and another program to
generate input data to the first program. Test your function.
7. Rewrite your function to use fgets() and test it. Is the new code more complex
or less complex? How does its performance compare to the getc() version?
8. Study the V7 end(3) manpage (/usr/man/man3/end.3 in the V7 distribution).
Does it shed any light on how ‘sbrk(0)’ might work?
9. Enhance ch03-memaddr.c to print out the location of the arguments and the
environment. In which part of the address space do they reside?
This page intentionally left blank
4
Files
and
File I/O
In this chapter
83
his chapter describes basic file operations: opening and creating files, reading
T and writing them, moving around in them, and closing them. Along the way
it presents the standard mechanisms for detecting and reporting errors. The chapter
ends off by describing how to set a file’s length and force file data and metadata
to disk.
In the next and subsequent sections, we illustrate the model by writing a very simple
version of cat. It’s so simple that it doesn’t even have options; all it does is concatenate
the contents of the named files to standard output. It does do minimal error reporting.
Once it’s written, we compare it to the V7 cat.
We present the program top-down, starting with the command line. In succeeding
sections, we present error reporting and then get down to brass tacks, showing how to
do actual file I/O.
84
4.2 Presenting a Basic Program Structure 85
Knowing that an error occurred isn’t enough. It’s necessary to know what error oc-
curred. For that, each process h