Unix Systems Programming
C Style Sheet
Grading on Function and Design
Your programming assignments will be graded on function and
program must meet the functional specifications given (in part) by the
assignment; this establishes the correct functioning of your program.
But your program must not only be read by the computer, it
must be read by humans as well, so your program must also be well-designed
using a clear and consistent style. When you write your program keep in
mind the following readers:
The evaluation of your program's function counts 70% of the assignment
grade, the evaluation of your programming style counts 30%.
This note explains each of these components.
- The computer which executes your program. Your program must be
efficient and free of bugs and potential difficulties from a malicious
- You will have to read your code long after you wrote it. You may
need to debug your code, add new functionality, modify some feature, or
re-use the code in a different program.
- Other people will read your code. I will read to evaluate your
design or try to track down difficulties. You may later have a
partner in a project who needs to modify the code you have.
A common mistake that people make when trying to design something
completely foolproof is to underestimate the ingenuity of fools.
Each program has a functional specification which specifies
The specifications largely govern how the program is expected to behave
under normal conditions.
But the program may also be used under extreme conditions when things are
not normal. This may be due to mistakes on the part of the user of the
program or due to system failures. The functional specification given to
you may leave you the task of handling the myriad of possible mistakes in
an appropriate manner.
Your program should be designed with this in mind: try to minimize the
mistakes a user could make and make sure that your program handles errors
gracefully. You are responsible for your program's behavior under any
condition the user may put it.
- Arguments and Options
- Return value
- Side effects (such as changes to files or print out)
- Error Handling
Your program will be judged on correct functionality as given by
the functional specification for the program. You will
be expected to submit a
typescript file which shows that your program
runs according to the functional specifications for the program. This is
meant as an authentication by you that your program works as expected. We
will verify that your program works as expected; but we will also run your
program in ways that are unexpected, to see how your program responds.
Here are some general guidelines that will be used in evaluating your
- You are responsible for your program's behavior under all
conditions, not just those given by the functional specification.
Try to expect the unexpected. This requires that you think
beyond the specification given to you, to what a user might do with
your program. This to be an ongoing process. It is hard to predict
every use to which your program will be put. Grading is one of the
mechanisms to expose incorrect behavior by your program.
Every program you submit you will have a chance to correct or redesign.
- Your program must meet the expectations established by the
specification. The expectations establish what is to be expected under normal
conditions, and may include what is to be expected under some of the
unusual conditions that a program may be subjected. You will provide a
typescript file documenting that you have tested your program under the
normal and abnormal conditions provided by the specification and your
program meets these expectations. If you are unable to satisfy the
expectations then you should state this in the
DOC file under
the heading of
- Your program must compile on a machine in the CSPP cluster. You
will be informed on which machine your program will be tested. It must
compile and run on this machine. Most of the programs for this class
will compile and run correctly on any Unix/Linux architecture.
- Your program must compile without warnings when compiled
gcc -WALL. Compiler warnings will be taken to be a
sign of poorly designed code which is vulnerable to bugs:
disfunctional behavior by your program as determined by the functional
- Your code will be evaluated on the efficient use of system
resources in its design. There are several components here:
Develope the good habit of respecting limited system resources.
- Time: Design your code for efficient use of time. You are
not expected to optimize your code for performance, but you should think
carefully about the data structures you use and your program design.
- Memory: Do not waste memory. Free up memory that your
program allocated but no longer requires. Do not place absurd bounds on
buffers to prevent buffer overflows. Use sensible limits, or design your
code without placing limits.
- Program Resources: There are limits to the number of child
processes a program can create and files a program can have open at one
time. These values may greatly exceed what you expect under normal
conditions, but why should you expect every condition to be normal?
Always close files after you will no longer need access to them.
- Document any deviations or design decisions
you include over and above the functional specifications given to you.
This will ensure you recieve proper credit.
- Test every function and system call in your program for error. Do
not assume the system is working normally. For example, do not assume
that every file you try to open with
open actually opens
a file. Check for errors on every system call. All errors
must be sent to
stderr (for file streams
STDERR_FILENO (file descriptor 3, for low-level I/O.)
- Your program should be robust against human variation.
Here are some examples of things where your program should attempt to
- Spacing in the input
- The length of input, especially your program places
restrictions on length which is
not something a user would be expected to know.
- Be consistent with similar usage already established.
This will minimize human error. My favorite example comes from the C
gets replaces a terminating
a null character,
fgets leaves the newline alone.
- Place sensible bounds on buffers or design your code without
bounds. For example, an operating system places a bound on the
length of filenames. This is given by the constant
limits.h, and is at least 255 bytes. Use this
constant to size a buffer to hold a file name, do not make-up a number
on your own. This will help prevent errors when your buffer is too
small and waste when it is too large.
- Your program should handle errors gracefully. This
involves two components
- Handle the error appropriately. Some errors will require the
program to exit, because there is nothing else for it to do. Many
system errors fall in this class. For example, your program may
have exceeded the memory limits of the machine. Another error that
may require program exit is an incorrect value passed as an
argument. For example, if your program recieves too few
arguments. Some errors can be handled without exiting the program.
This is common when a program is interacting with a person and
recieves input that it did not expect--it would be harsh to
terminate the program when it could display a message on correct use
and allow the user to re-enter the input.
- Give informative error responses. If you decide that
the correct response to an error is to terminate the program, then
you should print an error message to standard error. This is given
by the file stream (
stderr or to
the file descriptor
STDERR_FILENO (file descriptor 3.)
Usually, this is the terminal, but the user may have re-directed
error messages to a file.
Thirty percent of your grade rests on you coding style. Coding style
has three components
Keep in mind that certain files are intended to be publicly accessible:
DOC file giving the functional specification, and
your header file with publicly accessible functions, constants and
variables. Rules of style will be more strictly enforced in these files,
especially documentation. You have more flexibility in the style you
choose for source files since these files contain many objects which are
only locally accessible. You may find it appropriate to relax standards
Rule of Modularity:
Write simple parts connected by clean interfaces.
Modularity is a way to organize programs. A module is a
collection of functions (services) available to other parts of
the program (the clients.) Each module consists of two parts
Dividing a program into modules consisting of related functions has the
- Interface: the services available to the client, how they
are to be used and what their effects are.
- Implementation: the source code implementing the
When designing your program pay careful attention to the modularity of your
- Abstraction: A client need only know what services are
available, their correct use and their effects in order to use them,
without having to know implementation details. This allows you to
change the implementation without having to change all the uses of the
services in the rest of the code.
- Reusability: Any module that provides services is
potentially reusable in other programs.
- Maintainability: Bugs can be localized to single modules
making them easier to find and fix.
- Consider what modules to implement. A module consists
of a collection of related services (functions, variables, constants,
- Consider what services each module should provide, how each service
is to be used and what effects each service will have.
- Consider how modules should be interrelated. Try to make the
module as independent as possible from other modules, especially
modules which are likely to change in later versions of the program.
In C modules are implemented by creating header files and
source files and enforced by the use of storage
classes. The use of distinct header and source files are mainly for the
human reader. The header file ends in
.h (although this is
not necessary) and contains the publicly accessible objects (functions,
variables, constants, types) and documentation on the proper use and
effects to be expected. So, the header file is an essential part of a
module's interface with the human user. The source file ends in
.c (this is necessary) and contains the code implementing the
public services in the header file as well as services which may only be
known and used within the source file.
Dividing a module into header and source file does not enforce the
distinction between public services usable by other parts of the
program and private services which are to be restricted to the
source file in which they are defined. If you define a variable at the top
level of a file, that variable can be accessed by any other file; function
definitions are, by default, accessible by any other file. For this
reason, C uses storage class specifiers for functions and variables to enforce the extent to which
these objects can be used. A
storage class has three compenents
C has several storage class
specifiers, but for modularity the two key ones are
- Duration: the period of time storage is allocated
- Scope: the region of C program text over which a
declaration is visible. (Scope is limited to some part of the C source file in
which the variable occurs.)
- Linkage : where the variable is defined. The definition
determines the initial value and where this value is stored. (A
variable can be linked to a definition in another C file.)
extern. Suppose you define a variable at the top level of
int size=10;. Did you intend the value of
size to be accessible outside the file? If so it is a public
variable, otherwise it is a private variable. Similarly, when you define
static int inc_size() in a file do you intend the
function to be used by other files, in which case it is a public function;
or is the function to be used only within the file, in which case it is a
If you intend for the variable
size defined at the top level
of the file to be accessed only within the file then use
static modifier to ensure it is kept private to the file.
For example, the definition
static int size=10; at the top
level of your file will prevent any other file from referencing the
value defined by
size. The scope of this variable will still
be the entire file, but it cannot be referenced by another file. Similarly,
if you intend the function
inc_size() to be used only
within the file in which it is defined then it should be declared with
static modifier. For example, the prototye
static int inc_size(); specifies that the function
inc_size() can be used anywhere within the file, but may not
be used outside the file. (This function may increment the value
size and we may not wish for clients to have this privilege.)
If you intend the variable
size to be referenced outside the
file or the function
inc_size() to be used outside the file, then you do not need
to use any modifier. Just define these at the top level of the file.
(All functions are top level definitions by default, in C.) To reference
size in a different file, you must declare it with
size;. To use the function
inc_size() in a different
file you must include its prototype in the file using the
extern int get_inc();. There are two ways of
including these declarations: either explicitly writing them into the file
before you use them, or by writing them in a header file which is among
the list of
#includes in the file.
Each service has a contract with its client: it promises to do exactly what
it says it will and nothing more. Modular code enforces this contract.
Here are some rules of thumb to maintain modular code:
- Organize your program into modules of related services (functions,
variables, constants and types.)
- Make sure your modules are as independent of other modules as
- Use well-documented header files specifying the public interface
of the module for your human user. This specifies the contract the
function is making to a user of the function.
- Use the
static storage class specifier to enforce
privacy in your source file.
- Your module should be responsible for releasing all resources it
creates after these are no longer needed. For example,
close all files which were
free all memory obtained by
malloc. If you have a function which creates an object
consider have a dual function which releases the system resources for
that object when it is no longer required.
- Design each function to do one thing well. It is better to build
a new function to do a new job, rather than complicate an old function
with new features.
- If you have a global variable which may change over the course of
the program, designate one function whose purpose is to modify the
variable and keep the variable private to the file. This restricts
access to the variable and aids in understanding the program flow. If
an outside user must change a global variable provide a public
function which does this.
- Functions should not make unexpected changes to the process state
that persist beyond the return from the function. If a function
does change the process state then it should save and restore the
previous state. Here are some examples of ways a function can have
non-local effects on the process state:
If a public function leaves a lasting effect on the program state, then
document this in your description of the function in the header file.
- Modifying global variables.
- Using statically-declare variables (whose value persists between
separate calls to the function.)
- Printing to the terminal or a file.
- The best approach for handling errors encountered in a
function is to return a non-standard value (typically, a negative
NULL for a pointer.) Let the
section of the code determine how errors should be handled. Think
carefully how you want functions to respond to errors. If you print
error messages, exit the program or set global variables (like
errno) you are leaving a lingering effect on the
program. Are you certain that this will be appropriate every
time the function is to be use?
- Functions should release hidden resources used during its
execution if these resources are no longer needed. For example,
close all files which were
opened during the execution of the
free all memory obtained by
Programs are to be read by computers and human's alike. Good documentation
should permeate your code. Keep in mind that I will be reading your code
for the following purposes
When documenting your code keep in mind that both you and I will be
reading your code. I will need to read your
- Evaluate how modular your code is.
- Determine how I should expect your program to perform. (This is
especially true if you have extra credit editions to your code, or
modifications that go beyond the functional specifications given by the
- Detect sources of errors or problems in the performance of your
program. I need to be able to find my way around your program to
detect sources of error in your code.
DOC file and your
header files with the publicly accessible objects, so follow the
guidelines put down below for documenting these files and the objects
found within them (especially, the public functions in the header file.)
Use your own judgement in documenting your source files, or in your
README file (if you choose to include one.)
It is very important that you provide good documentation in the
DOC file: You are required to submit a file called
DOC which gives the functional specification of your
program. This file is styled after the organization of the unix
manpages. You must follow the guide set-out in the
The specification will largely be given in the assignment, but be aware
that any additions to this specification you make should be documented
DOC to recieve credit.
README file: This is an optional file where you can
place any additional documentation on your program. Typically, this
file will contain a brief description of the organization and purpose
of each file used to build your directory.
- Header files: The header files have publically accessible
objects (functions, variables, constants, definitions) and should be
carefully documented. Each function must have an introduction
explaining how it is to be used, what it does and any side-effects to
its use. You must follow the guide set-out in the
header template. Remember,
I will be reading this file to evaluate your program.
- Source files: Documentation in the source file should enhance the
readability and understanding of your code. Use your judgement where
you need to comment code. I have placed some suggestions below and a
source template. You can
relax the standards for the header file. Over documenting can make the
code difficult to read. You are the main reader of the source file.
file and in the header files, since these are the places your reader will
look to first. Rules for documenting will be strictly enforced in these
places. Documentation of your source files is up to your judgement. Here
are some pointers to keep in mind
||Each file should have an introduction that gives the name of the file and
describes the purpose of the set of functions which make-up the file. In
addition it should provide any hints or warnings about the use of the
functions in the file.
||Each public function should have an introduction
that describes the purpose of the function,
the meaning of each of its arguments,
the meaning of the return value,
the possible error conditions, and any side-effects the
function produces. Keep your description clear and simple.
A local function may have a very specific and narrow
purpose, so a one line description may be plenty sufficient.
If your public functions are well-documented in the header file, a
one line description can be sufficient in the source file.
The local variables should have names that
describe their purpose. Consider commenting
variables when their name does not quite capture their
purpose. This is more important with global variables,
especially those which are publicly accessible.
|| Each chunk of code in the function should have a
brief description to explain where it fits
in the flow of the function.
Furthermore, explaining a piece of
code helps clarify your logic.
||C invites you to write clever, efficient
code. As you get used to the language,
you will find yourself writing code that
looks a little weird. When you feel that
twinge of cleverness, put a comment like:
/* yes, I mean =, it should not be == */
Readability consists of two components
The key to readability is consistency. By consistently following
a set of rules to guide the readability of your code, your reader will anticipate
key properties of your code by the layout on the screen or the names chosen.
This will aid in reasoning about the program.
Poorly written code stands out by the effort of a reader to follow it.
Here are some rules of thumb to keep in mind:
- Arrangement of the code on the screen
- Naming of objects
- Arrange your code to reflect the logical structure of your
program. Use separation between the major logical parts of a function
definition (initialization code, main code, and termination code).
This separation may take the form of extra spacing or short comments
introducing the section of code.
- Use a consistent indentation to separate code blocks. In C, code
blocks are usually grouped by braces.
- Choose names that allow your reader to anticipate the use of the
object with the name. Functions and global variables are usually given
more descriptive names. Local variables (especially those which
fulfill a commonly used role) can be given shorter names. Often it is
better to be terse, if you are consistent. For example, variables
used for counting the iterations of a loop can be given a very short
name, such as
i; often this makes the code more readable
than a more verbose name.
- Functions should be simple. You should be able to reason
about the function without strain. It is easier to enforce modularity
with simple functions and judge correctness of your code.
There are several measures of simplicity:
- You should be able to describe the purpose of the function in a
- Limit the number of local variables used to about five. Any more and
your function is probably doing too much.
- Functions should require little space on the screen: they should be
under 50 lines (this is the space of about two screens of text.) Code
which requires that we page up and down to follow it is probably too
complicated to reason about.
Choose a coding style and follow it consistently. Remember readability
aids your reader by allowing the reader to anticipate the purpose of a
section of code or of an object named in your program. This makes the code
easier to reason about, and minimizes errors in use or bugs in