------------------------------- Page    i -------------------------------

                       Lint - a C Program Checker




                                                 S. C. Johnson



                                                 Edited for UTS

------------------------------- Page   ii -------------------------------

                            TABLE OF CONTENTS


Abstract  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .   1

1.    Introduction and Usage  . . . . . . . . . . . . . . . . . . . .   1

2.    A Word About Philosophy . . . . . . . . . . . . . . . . . . . .   2

3.    Unused Variables and Functions  . . . . . . . . . . . . . . . .   2

4.    Set/Used Information  . . . . . . . . . . . . . . . . . . . . .   3

5.    Flow of Control . . . . . . . . . . . . . . . . . . . . . . . .   4

6.    Function Values . . . . . . . . . . . . . . . . . . . . . . . .   4

7.    Type Checking . . . . . . . . . . . . . . . . . . . . . . . . .   6

8.    Type Casts  . . . . . . . . . . . . . . . . . . . . . . . . . .   6

9.    Nonportable Character Use . . . . . . . . . . . . . . . . . . .   7

10.   Assignments of longs to ints  . . . . . . . . . . . . . . . . .   7

11.   Strange Constructions . . . . . . . . . . . . . . . . . . . . .   8

12.   Ancient History . . . . . . . . . . . . . . . . . . . . . . . .   9

13.   Pointer Alignment . . . . . . . . . . . . . . . . . . . . . . .  10

14.   Multiple Uses and Side Effects  . . . . . . . . . . . . . . . .  10

15.   Implementation  . . . . . . . . . . . . . . . . . . . . . . . .  11

16.   Portability . . . . . . . . . . . . . . . . . . . . . . . . . .  12

17.   Shutting Lint Up  . . . . . . . . . . . . . . . . . . . . . . .  13

18.   Library Declaration Files . . . . . . . . . . . . . . . . . . .  15

19.   Bugs, etc.  . . . . . . . . . . . . . . . . . . . . . . . . . .  16

Appendix A.    Current Lint Options . . . . . . . . . . . . . . . . .  17


                                                            Last Page  17

-------------------------------- Page  1 --------------------------------

ABSTRACT

Lint is a command which examines C source programs, detecting a number of
bugs and obscurities.  It enforces the type rules of C more strictly than
the C compilers.  It may also be used to enforce a number of  portability
restrictions involved  in  moving  programs  between  different  machines
and/or operating systems.  Another option  detects a number of  wasteful,
or error prone, constructions which nevertheless are, strictly  speaking,
legal.

Lint accepts multiple input files and library specifications, and  checks
them for consistency.

The separation of function between lint and the C compilers has both his-
torical and practical rationale.  The compilers turn C programs into exe-
cutable files rapidly and efficiently.  This is possible in part  because
the compilers do not  do sophisticated type checking, especially  between
separately compiled programs.  Lint takes  a more global, leisurely  view
of the program, looking much more carefully at the compatibilities.

This document discusses the use of lint, gives an overview of the  imple-
mentation, and gives some hints  on the writing of machine independent  C
code.




1.    INTRODUCTION AND USAGE

Suppose there are two C source files, file1.c and file2.c which are ordi-
narily compiled and loaded together.  Then the command

     lint  file1.c  file2.c

produces messages describing  inconsistencies and  inefficiencies in  the
programs.  The program enforces the typing rules of C more  strictly than
the C compilers (for both historical and practical reasons) enforce them.
The command

     lint  -p  file1.c  file2.c

will produce,  in addition  to the  above messages,  additional  messages
which relate to the portability  of the programs to other operating  sys-
tems and machines.  Replacing the  -p by -h  will produce messages  about
various error-prone or wasteful  constructions which, strictly  speaking,
are not bugs.  Saying -hp gets the whole works.

-------------------------------- Page  2 --------------------------------

The next  several sections  describe  the major  messages;  the  document
closes with sections discussing the implementation and giving suggestions
for writing portable C.  An appendix gives a summary of the lint options.




2.    A WORD ABOUT PHILOSOPHY

Many of the facts which  lint needs may be  impossible to discover.   For
example, whether  a given  function  in a  program ever  gets called  may
depend on  the input  data.   Deciding whether  exit is  ever  called  is
equivalent to solving the  famous 'halting problem,'  known to be  recur-
sively undecidable.

Thus, most of the  lint algorithms are  a compromise.  If  a function  is
never mentioned, it  can never  be called.  If  a function is  mentioned,
lint assumes it can be called; this is  not necessarily so, but in  prac-
tice is quite reasonable.

Lint tries to give information with a high degree of relevance.  Messages
of the form 'xxx might be a bug' are easy to generate, but are acceptable
only in proportion to the  fraction of real bugs  they uncover.  If  this
fraction of real bugs is  too small, the messages lose their  credibility
and serve merely to clutter up  the output, obscuring the more  important
messages.

Keeping these issues in mind, we now consider in more detail the  classes
of messages which lint produces.




3.    UNUSED VARIABLES AND FUNCTIONS

As sets of  programs evolve  and develop, previously  used variables  and
arguments to functions may become unused; it is not uncommon for external
variables, or even entire functions,  to become unnecessary, and yet  not
be removed from the  source.  These 'errors  of commission' rarely  cause
working programs to fail, but they are a source of inefficiency, and make
programs harder to  understand and  change.  Moreover, information  about
such unused variables and  functions can occasionally  serve to  discover
bugs; if a function does a necessary job, and is  never called, something
is wrong!

-------------------------------- Page  3 --------------------------------

Lint complains about variables  and functions which  are defined but  not
otherwise mentioned.   An  exception  is  variables  which  are  declared
through explicit extern  statements but  are never  referenced; thus  the
statement

     extern  float  sin();

will evoke no comment if sin is never  used.  Note that this agrees  with
the semantics of the  C compiler.  In  some cases, these unused  external
declarations might be of some interest; they can be discovered by  adding
the -x flag to the lint invocation.

Certain styles of programming require  many functions to be written  with
similar interfaces; frequently, some  of the arguments  may be unused  in
many of the calls.  The -v option  is available to suppress the  printing
of complaints about unused arguments.  When -v is in effect,  no messages
are produced about unused arguments except for those arguments which  are
unused and also declared as register arguments; this can be considered an
active (and preventable) waste of the register resources of the machine.

There is one case where information about unused, or undefined, variables
is more distracting than helpful.  This is when lint is  applied to some,
but not all, files out of a collection  which are to be loaded  together.
In this  case, many of  the functions  and variables defined  may not  be
used, and, conversely, many functions and variables defined elsewhere may
be used.  The -u flag may be used to suppress the spurious messages which
might otherwise appear.




4.    SET/USED INFORMATION

Lint attempts to detect cases where a variable is used before it is  set.
This is very difficult  to do well; many  algorithms take a good deal  of
time and space,  and still  produce messages about  perfectly valid  pro-
grams.  Lint  detects  local variables  (automatic and  register  storage
classes) whose first  use appears  physically earlier in  the input  file
than the first assignment  to the variable.   It assumes that taking  the
address of a variable constitutes a 'use,' since the actual use may occur
at any later time, in a data dependent fashion.

The restriction to the physical appearance of variables in the file makes
the algorithm very simple and quick to implement, since the  true flow of
control need not  be discovered.   It does  mean that  lint can  complain
about some programs which are legal, but these programs would probably be
considered bad  on stylistic  grounds (e.g.  might contain  at least  two
gotos).  Because static and external  variables are initialized to 0,  no

-------------------------------- Page  4 --------------------------------

meaningful information can be discovered about their uses.  The algorithm
deals correctly, however, with initialized automatic variables, and vari-
ables which are used in the expression which first sets them.

The set/used information also  permits recognition of  those local  vari-
ables which are set and never used; these form a frequent source of inef-
ficiencies, and may also be symptomatic of bugs.




5.    FLOW OF CONTROL

Lint attempts to  detect unreachable  portions of the  programs which  it
processes.  It will complain about unlabeled statements immediately  fol-
lowing goto, break, continue, or  return statements.  An attempt is  made
to detect loops which can never be left at the bottom, detecting the spe-
cial cases while( 1 ) and for(;;) as infinite loops.  Lint also complains
about loops which cannot be  entered at the top; some valid programs  may
have such loops, but at best they are bad style, at worst bugs.

Lint has an important area of blindness in the flow of control algorithm:
it has no way of  detecting functions which are called and never  return.
Thus, a  call to  exit may  cause unreachable  code which  lint does  not
detect; the  most serious  effects of  this are in  the determination  of
returned function values (see the next section).

One form  of unreachable  statement is  not usually  complained about  by
lint; a break statement that  cannot be reached causes no message.   Pro-
grams generated by yacc, and especially lex, may have literally  hundreds
of unreachable  break statements.   The -O  flag in the  C compiler  will
often eliminate  the resulting  object  code inefficiency.   Thus,  these
unreached statements are of little importance, there is typically nothing
the user can do about them, and  the resulting messages would clutter  up
the lint output.  If these messages are desired, lint can be invoked with
the -b option.




6.    FUNCTION VALUES

Sometimes functions return values  which are never  used; sometimes  pro-
grams incorrectly use function  'values' which have never been  returned.
Lint addresses this problem in a number of ways.

-------------------------------- Page  5 --------------------------------

Locally, within a function definition, the appearance of both

     return(  expr  );

and

     return ;

statements is cause for alarm; lint will give the message

     function name contains return(e) and return

The most serious difficulty with this is detecting when a function return
is implied by flow of control reaching the end of the function.  This can
be seen with a simple example:

     f ( a ) {
        if ( a ) return ( 3 );
        g ();
        }

Notice that, if a  tests false, f  will call  g and then  return with  no
defined return value;  this will  trigger a complaint  from lint.  If  g,
like exit, never returns, the message will still be produced when in fact
nothing is wrong.

In practice, some potentially serious  bugs have been discovered by  this
feature; it also accounts for a substantial fraction of the  'noise' mes-
sages produced by lint.

On a global scale, lint detects cases  where a function returns a  value,
but this value is sometimes, or always, unused.  When the value is always
unused, it may  constitute an  inefficiency in  the function  definition.
When the value is sometimes unused, it may represent bad style (e.g., not
testing for error conditions).

The dual  problem, using  a function  value when  the function  does  not
return one, is  also detected.   This is a  serious problem.   Amazingly,
this bug has been  observed on a  couple of occasions  in 'working'  pro-
grams; the desired function value just happened to have been  computed in
the function return register!

-------------------------------- Page  6 --------------------------------

7.    TYPE CHECKING

Lint enforces the type checking  rules of C more  strictly than the  com-
pilers do.  The additional checking  is in four major areas: across  cer-
tain binary operators and implied assignments, at the structure selection
operators, between the definition and  uses of functions, and in the  use
of enumerations.

There are a number of operators  which have an implied balancing  between
types of the  operands.  The  assignment, conditional (  ?: ), and  rela-
tional operators have this property; the argument of a return  statement,
and expressions used in  initialization also suffer similar  conversions.
In these operations, char, short, int, long, unsigned, float, and  double
types may  be  freely  intermixed.   The types  of  pointers  must  agree
exactly, except that arrays  of x's  can, of course,  be intermixed  with
pointers to x's.

The type checking rules also  require that, in structure references,  the
left operand of the  --> be a pointer  to structure, the left operand  of
the .  be  a structure, and  the right  operand of these  operators be  a
member of the structure implied by the left operand.  Similar checking is
done for references to unions.

Strict rules apply to function  argument and return value matching.   The
types float  and double may  be freely  matched, as may  the types  char,
short, int, and unsigned.  Also, pointers can be matched with the associ-
ated arrays.  Aside from  this, all actual  arguments must agree in  type
with their declared counterparts.

With enumerations, checks are made that enumeration variables or  members
are not mixed with other types, or other enumerations, and  that the only
operations applied are =, initialization, ==, !=, and function  arguments
and return values.




8.    TYPE CASTS

The type cast feature in C was introduced largely as an aid to  producing
more portable programs.  Consider the assignment

     p = 1 ;

where p is a character pointer.  Lint will quite rightly complain.   Now,
consider the assignment

     p = (char *)1 ;

-------------------------------- Page  7 --------------------------------

in which a  cast has  been used  to convert  the integer  to a  character
pointer.  The  programmer obviously  had  a strong  motivation for  doing
this, and has clearly signaled his  intentions.  It seems harsh for  lint
to continue to complain about  this.  On the other hand, if this code  is
moved to another machine, such code  should be looked at carefully.   The
-c flag controls  the printing of  comments about casts.   When -c is  in
effect, casts are treated as though they were assignments subject to com-
plaint; otherwise, all legal casts are passed without comment, no  matter
how strange the type mixing seems to be.




9.    NONPORTABLE CHARACTER USE

On the PDP-11, characters are signed  quantities, with a range from  -128
to 127.  On most of the other C implementations, characters  take on only
positive values.  Thus, lint  will flag certain  comparisons and  assign-
ments as being illegal or nonportable.  For example, the fragment

     char c;
        ...
     if( (c = getchar()) < 0 ) ....

works on the PDP-11, but  will fail on  machines where characters  always
take on positive values.  The  real solution is to declare c an  integer,
since getchar is actually  returning integer values.   In any case,  lint
will say 'nonportable character comparison'.

A similar  issue arises  with  bitfields; when  assignments  of  constant
values are made  to bitfields,  the field may  be too  small to hold  the
value.  This is especially  true because on  some machines bitfields  are
considered as signed quantities.  While  it may seem unintuitive to  con-
sider that a two bit field declared of type int cannot hold the value  3,
the problem disappears if the bitfield is declared to have type unsigned.




10.   ASSIGNMENTS OF LONGS TO INTS

Bugs may arise from the assignment of long  to an int, which loses  accu-
racy.  This may happen in programs which have been incompletely converted
to use typedefs.  When a  typedef variable is changed  from int to  long,
the program can  stop working  because some intermediate  results may  be
assigned  to  ints,  losing  accuracy.   Since  there  are  a  number  of

-------------------------------- Page  8 --------------------------------

legitimate reasons for assigning  longs to ints,  the detection of  these
assignments is enabled by the -a flag.




11.   STRANGE CONSTRUCTIONS

Several perfectly legal, but somewhat strange, constructions are  flagged
by lint; the  messages hopefully encourage  better code quality,  clearer
style, and may even point out bugs.  The -h flag is used to enable  these
checks.  For example, in the statement

     *p++ ;

the * does nothing; this  provokes the message  'null effect' from  lint.
The program fragment

     unsigned x ;
     if( x < 0 ) ...

is clearly somewhat strange; the test will never succeed.  Similarly, the
test

     if( x > 0 ) ...

is equivalent to

     if( x != 0 )

which may not be the intended action.  Lint will say 'degenerate unsigned
comparison' in these cases.  If one says

     if( 1 != 0 ) ....

lint will report 'constant in conditional context', since the  comparison
of 1 with 0 gives a constant result.

Another construction detected by lint involves operator precedence.  Bugs
which arise from misunderstandings about the precedence of operators  can
be accentuated by spacing and formatting, making such bugs extremely hard
to find.  For example, the statements

     if( x&077 == 0 ) ...

or

     x<2 + 40

-------------------------------- Page  9 --------------------------------

probably  do  not  do  what  was  intended.   The  best  solution  is  to
parenthesize such expressions, and lint encourages this by an appropriate
message.

Finally, when the  -h flag  is in  force lint  complains about  variables
which are redeclared in inner  blocks in a way that conflicts with  their
use in outer blocks.  This is legal, but is considered by many (including
the author) to be bad style, usually unnecessary, and frequently a bug.




12.   ANCIENT HISTORY

There are  several  forms of  older  syntax which  are  being  officially
discouraged.  These fall into two classes, assignment operators and  ini-
tialization.

The older forms  of assignment operators  (e.g., =+,  =-, . .  . )  could
cause ambiguous expressions, such as

     a  =-1 ;

which could be taken as either

     a =-  1 ;

or

     a  =  -1 ;

The situation is especially perplexing  if this kind of ambiguity  arises
as the result of a  macro substitution.  The newer, and preferred  opera-
tors (+=, -=, etc. ) have no  such ambiguities.  To spur the  abandonment
of the older forms, lint complains about these old fashioned operators.

A similar issue arises with initialization.  The older language allowed

     int  x  1 ;

to initialize x to 1.  This also caused syntactic difficulties: for exam-
ple,

     int  x  ( -1 ) ;

looks somewhat like the beginning of a function declaration:

     int  x  ( y ) {  . . .

-------------------------------- Page 10 --------------------------------

and the compiler must read a fair ways past x  in order to sure what  the
declaration really is..  Again, the problem is even more perplexing  when
the initializer involves a  macro.  The current  syntax places an  equals
sign between the variable and the initializer:

     int  x  =  -1 ;

This is free of any possible syntactic ambiguity.




13.   POINTER ALIGNMENT

Certain pointer assignments may be reasonable on some machines, and ille-
gal on others, due entirely  to alignment restrictions.  For example,  on
the PDP-11,  it  is  reasonable  to assign  integer  pointers  to  double
pointers, since double precision  values may begin  on any integer  boun-
dary.  On the Honeywell 6000, double precision values must begin on  even
word boundaries; thus, not all  such assignments make sense.  Lint  tries
to detect cases where pointers are  assigned to other pointers, and  such
alignment problems might arise.  The message 'possible pointer  alignment
problem' results from this situation whenever  either the -p or -h  flags
are in effect.




14.   MULTIPLE USES AND SIDE EFFECTS

In complicated expressions, the  best order in  which to evaluate  subex-
pressions may  be highly  machine  dependent.  For  example, on  machines
(like the PDP-11) in which  the stack runs backwards, function  arguments
will probably be best  evaluated from right-to-left;  on machines with  a
stack running  forward, left-to-right  seems most  attractive.   Function
calls embedded as arguments of other functions may or may  not be treated
similarly to ordinary arguments.  Similar issues arise with other  opera-
tors which have side  effects, such as  the assignment operators and  the
increment and decrement operators.

In order that the efficiency of C on  a particular machine not be  unduly
compromised, the C language leaves the order of evaluation of complicated
expressions up to the local  compiler, and, in fact,  the various C  com-
pilers have  considerable differences  in the  order in  which they  will
evaluate complicated  expressions.  In  particular,  if any  variable  is
changed by a side effect, and also used elsewhere in the same expression,

-------------------------------- Page 11 --------------------------------

the result is explicitly undefined.

Lint checks for the important special case where a simple scalar variable
is affected.  For example, the statement

     a[i] = b[i++] ;

will draw the complaint:

     warning: i evaluation order undefined




15.   IMPLEMENTATION

Lint consists of two programs and a driver.  The first program is a  ver-
sion of  the Portable  C  Compiler which  is the  basis of  the IBM  370,
Honeywell 6000, and Interdata 8/32 C compilers.  This compiler does lexi-
cal and syntax analysis on the input text, constructs and  maintains sym-
bol tables,  and builds  trees for  expressions.  Instead  of writing  an
intermediate file which is passed to a code generator, as  the other com-
pilers do, lint produces an intermediate file which consists of lines  of
ascii text.  Each line contains an external variable name, an encoding of
the context in which it was seen (use, definition, declaration, etc.),  a
type specifier, and a source file name and line number.   The information
about variables local to a function or file is collected by accessing the
symbol table, and examining the expression trees.

Comments about local problems are produced as detected.  The  information
about external names is collected  onto an intermediate file.  After  all
the source files and library descriptions have been collected, the inter-
mediate file is sorted to  bring all information collected about a  given
external name together.  The second, rather small, program then reads the
lines from the  intermediate file  and compares all  of the  definitions,
declarations, and uses for consistency.

The driver controls this process, and is also responsible for making  the
options available to both passes of lint.

-------------------------------- Page 12 --------------------------------

16.   PORTABILITY

In this section, the term  "IBM systems" refers to  IBM systems that  are
not based upon UNIX-.  In particular, they do not refer to UTS.  C on the
Honeywell and IBM systems is used, in part, to write  system code for the
host operating system.  This means that the implementation of C tends  to
follow local conventions rather than adhere strictly to UNIX system  con-
ventions.  Despite these differences, many C programs have been  success-
fully moved to GCOS and the various IBM installations with little effort.
This section describes some  of the differences  between the  implementa-
tions, and discusses the lint features which encourage portability.

Uninitialized external  variables are  treated differently  in  different
implementations of  C.   Suppose two  files  both contain  a  declaration
without initialization, such as

     int a ;

outside of any  function.  The  UNIX loader will  resolve these  declara-
tions, and cause only  a single word  of storage to  be set aside for  a.
Under the GCOS and IBM implementations, this is not feasible (for various
stupid reasons!) so each such declaration causes a word of  storage to be
set aside and called  a.  When  loading or library  editing takes  place,
this causes fatal  conflicts which  prevent the proper  operation of  the
program.  If lint is invoked with the -p flag, it will detect such multi-
ple definitions.

A related difficulty comes from the amount of information retained  about
external names during the  loading process.  On  the UNIX system,  exter-
nally known names have seven significant characters, with the upper/lower
case distinction kept.  On the  IBM systems, there are eight  significant
characters, but the case distinction  is lost.  On  GCOS, there are  only
six characters, of a  single case.  This  leads to situations where  pro-
grams run on the UNIX system, but encounter loader problems on the IBM or
GCOS systems.  Lint -p  causes all external  symbols to be mapped to  one
case and truncated to six characters, providing a worst-case analysis.

A number of differences arise in the area of character handling:  charac-
ters in the  UNIX system are eight  bit ascii, while  they are eight  bit
ebcdic on  the IBM,  and nine  bit ascii  on GCOS.   Moreover,  character
strings go from high to  low bit positions ('left to right') on GCOS  and
IBM, and low to high  ('right to left') on the  PDP-11.  This means  that
code attempting  to  construct strings  out  of character  constants,  or
attempting to use characters as  indices into arrays,  must be looked  at
with great  suspicion.   Lint is  of  little help  here, except  to  flag
multi-character character constants.


_______________
  -UNIX is a trademark of Bell Laboratories.

-------------------------------- Page 13 --------------------------------

Of course, the word sizes are  different!  This causes less trouble  than
might be  expected, at least  when moving  from the UNIX  system (16  bit
words) to the IBM  (32 bits) or  GCOS (36 bits).   The main problems  are
likely to  arise in  shifting  or masking.   C now  supports a  bit-field
facility, which can be used  to write much of this  code in a  reasonably
portable way.  Frequently, portability  of such code  can be enhanced  by
slight rearrangements  in coding  style.  Many  of the  incompatibilities
seem to have the flavor of writing

     x &= 0177700 ;

to clear the low order six bits of x.   This suffices on the PDP-11,  but
fails badly on GCOS  and IBM.  If the  bit field feature cannot be  used,
the same effect can be obtained by writing

     x &= ~ 077 ;

which will work on all these machines.

The right shift operator is arithmetic  shift on the PDP-11, and  logical
shift on most other machines.  To obtain a logical shift on all machines,
the left operand can be typed unsigned.  Characters are considered signed
integers on the PDP-11,  and unsigned on  the other machines.  This  per-
sistence of the sign bit may be reasonably considered a bug in the PDP-11
hardware which has infiltrated itself into the C language.  If there were
a good way to discover the programs which  would be affected, C could  be
changed; in any case, lint is no help here.

The above discussion may have made the problem of portability seem bigger
than it in fact is.   The issues involved here are rarely subtle or  mys-
terious, at least to the  implementor of the  program, although they  can
involve some work to straighten out.  The most serious bar  to the porta-
bility of UNIX system utilities has been the inability to mimic essential
UNIX system functions on the  other systems.  The inability to seek to  a
random character position in a text file, or to establish a pipe  between
processes, has involved far more rewriting and debugging than any  of the
differences in C compilers.  On the other hand, lint has been very  help-
ful in moving the UNIX  operating system and associated utility  programs
to other machines.




17.   SHUTTING LINT UP

There are occasions when the programmer is smarter than lint.  There  may
be valid  reasons for  'illegal' type  casts, functions  with a  variable
number of  arguments, etc.   Moreover, as  specified above,  the flow  of

-------------------------------- Page 14 --------------------------------

control information produced by lint often has blind spots, causing occa-
sional spurious messages about perfectly reasonable programs.  Thus, some
way of communicating with lint, typically to shut it up, is desirable.

The form which this mechanism should take is not at all clear.  New  key-
words would require  current and  old compilers to  recognize these  key-
words, if only to ignore them.  This has both philosophical and practical
problems.  New preprocessor syntax suffers from similar problems.

What was finally done was to cause a number of words to be recognized  by
lint when they were embedded in comments.  This required minimal  prepro-
cessor changes;  the preprocessor  just  had to  agree to  pass  comments
through to its output,  instead of deleting  them as had been  previously
done.  Thus,  lint directives  are invisible  to the  compilers, and  the
effect on systems with  the older preprocessors  is merely that the  lint
directives don't work.

The first directive is concerned with  flow of control information; if  a
particular place  in  the program  cannot  be reached,  but this  is  not
apparent to lint, this can be asserted by the directive

     /* NOTREACHED */

at the appropriate spot in the program.   Similarly, if it is desired  to
turn off strict type checking for the next expression, the directive

     /* NOSTRICT */

can be used; the situation reverts to the previous default after the next
expression.  The -v flag can be turned on for one  function by the direc-
tive

     /* ARGSUSED */

Complaints about variable number of arguments in calls to a function  can
be turned off by the directive

     /* VARARGS */

preceding the function  definition.  In  some cases, it  is desirable  to
check  the  first  several  arguments,  and  leave  the  later  arguments
unchecked.  This can be done by following the VARARGS keyword immediately
with a  digit giving  the number  of arguments which  should be  checked;
thus,

     /* VARARGS2 */

will cause the first two arguments  to be checked, the others  unchecked.
Finally, the directive

-------------------------------- Page 15 --------------------------------

     /* LINTLIBRARY */

at the head of a file identifies this file as a library declaration file;
this topic is worth a section by itself.




18.   LIBRARY DECLARATION FILES

Lint accepts certain library directives, such as

     -ly

and tests the source files for compatibility with these libraries.   This
is done  by accessing  library  description files  whose names  are  con-
structed from the  library directives.   These files all  begin with  the
directive

     /* LINTLIBRARY */

which is followed by a series of dummy function definitions.  The  criti-
cal parts of these definitions are the declaration of the function return
type, whether the  dummy function  returns a  value, and  the number  and
types of arguments to the function.  The VARARGS and ARGSUSED  directives
can be used to specify features of the library functions.

Lint library  files are  processed almost  exactly like  ordinary  source
files.  The  only difference  is that  functions which are  defined on  a
library file, but  are not  used on a  source file,  draw no  complaints.
Lint does not simulate a full library search algorithm, and  complains if
the source files contain a redefinition of  a library routine (this is  a
feature!).

By default,  lint checks  the programs  it is  given against  a  standard
library file, which contains descriptions of the programs which are  nor-
mally loaded when a  C program is run.   When the -p  flag is in  effect,
another file  is  checked containing  descriptions  of the  standard  I/O
library routines  which  are  expected  to  be  portable  across  various
machines.  The -n flag can be used to suppress all library checking.

-------------------------------- Page 16 --------------------------------

19.   BUGS, ETC.

Lint was a difficult program  to write, partially  because it is  closely
connected with matters of programming style, and partially because  users
usually don't notice bugs which cause lint to miss errors which it should
have caught.  (By contrast, if lint incorrectly complains about something
that is correct, the programmer reports that immediately!)

A number of areas remain to be further developed.  The checking of struc-
tures  and  arrays  is  rather  inadequate;  size  incompatibilities   go
unchecked, and  no  attempt is  made  to match  up  structure  and  union
declarations across  files.  Some  stricter checking  of the  use of  the
typedef is clearly desirable, but  what checking is appropriate, and  how
to carry it out, is still to be determined.

Lint shares the preprocessor with the C  compiler.  At some point it  may
be appropriate  for a  special  version of  the preprocessor  to be  con-
structed which checks for things such as unused macro definitions,  macro
arguments which have side effects  which are not expanded at all, or  are
expanded more than once, etc.

The central problem with lint is  the packaging of the information  which
it collects.  There  are many options  which serve only  to turn off,  or
slightly modify, certain features.  There are pressures to add even  more
of these options.

In conclusion, it appears that the general notion of having two  programs
is a good one.  The compiler concentrates on quickly and accurately turn-
ing the program text  into bits which  can be run;  lint concentrates  on
issues of  portability, style,  and efficiency.   Lint can  afford to  be
wrong, since incorrectness and over-conservatism are merely annoying, not
fatal.  The compiler can be fast since it knows that  lint will cover its
flanks.  Finally, the programmer can concentrate at one stage of the pro-
gramming process solely on the algorithms, data structures, and  correct-
ness of the program, and then later retrofit,  with the aid of lint,  the
desirable properties of universality and portability.

-------------------------------- Page 17 --------------------------------

APPENDIX A.    CURRENT LINT OPTIONS

The command currently has the form

     lint [-options ] files... library-descriptors...

The options are

h    Perform heuristic checks

p    Perform portability checks

v    Don't report unused arguments

u    Don't report unused or undefined externals

b    Report unreachable break statements.

x    Report unused external declarations

a    Report assignments of long to int or shorter.

c    Complain about questionable casts

n    No library checking is done

s    Same as h (for historical reasons)
