Thinkage Ltd.
85 McIntyre Drive
Kitchener, Ontario
Canada N2R 1H6
Copyright © 1995, by Thinkage
LINT is a program that examines C source code and makes note of "irregularities" in the code. When LINT was first implemented, its primary function was locating bugs and inefficiencies. However, as the C programming language spread to a variety of machines and systems, LINT was enhanced to locate deviations from the strict definition of C.
The ANSI standard for C now provides the "official" definition of C. However, the ANSI standard does not entirely describe the behavior of C implementations, because the standard allows certain operations to be performed in a system-dependent way. If a program is written in conformance with the ANSI standard, avoiding such system-dependent features, we say that the program is written in Strict C.
Ideally, all programs should be written in Strict C, since these programs are maximally portable. One of the major functions of LINT is to detect code constructs which do not conform with Strict C. Such constructs are often valid and can be used in working programs, but they are not truly portable and may be indications of loose programming style. LINT shows where you have written non-portable code; for portability, you should try to remove such code.
Before we begin, we should note that it is inevitable that LINT will miss some problems and will also complain about code that turns out to be valid. The designers have tried to chart a course between too much laxness (which may miss significant irregularities) and too much nit-picking (which produces quantities of irrelevant output that you will likely ignore). Thus we have designed this version of LINT to report situations that are usually signs of errors. While odd constructions and unusual programming style can confuse LINT from time to time, the majority of material that LINT locates should deserve the programmer's attention.
LINT understands all the usual C preprocessing directives (e.g. #include, #define, etc.). In addition, LINT recognizes a number of other directives that have the same sort of format:
#pragma aligned #pragma argsused #pragma notreached #pragma optresult #pragma used #pragma varargs
These directives tell LINT about special features of your source code and allow LINT to provide more meaningful diagnostic messages. The usage of each LINT directive is explained later in the manual.
ANSI C compilers should not reject these special LINT directives; the ANSI standard says that C compilers are supposed to ignore any #pragma directives they do not understand. However, non-ANSI C compilers may issue diagnostic messages for these #pragma directives, and even some ANSI C compilers may issue warnings about unrecognized #pragmas.
LINT generally produces quite a lot of output. By default the output is written to the terminal, but it can be redirected to a file using the standard output redirection constructions on the LINT command line.
The first lines of output from LINT are diagnostic messages comparable to those that might be produced by a C compiler. These diagnostics describe easily detected things like syntax errors.
Following this comes output that is unique to LINT. Most lines in the output have the form
filename,lineno: class: text
where filename is the name of one of the source files that LINT is scanning and lineno is a line number within that file. The class field tells the type of problem found; see "Message Classes" below for more information. The text part of the message provides specific information about the problem.
Some lines may have the form
libname: class: text
where libname is the name of a LINT Library. For more information on LINT libraries, see the last section of this manual.
Each message produced by LINT is labelled with a string indicating what kind of problem the message describes. The following message classes are recognized:
i=i;
is valid but suspicious, possibly indicating some problem in your code.
printf("%ld",1);
is invalid since %ld requires a long argument but 1 is just int. However, this always works properly on machines where long and int are the same size.
LINT output is grouped according to the functions of the source code.
The first line of a group of messages gives the name of a function or an external variable. After that come all the messages pertaining to that function or variable. The messages end with a blank line, followed by the group of messages for the next function or variable.
Groups are sorted in alphabetical order according to the name of the function or variable.
The first line of every function group gives the name of the function and the type of value that the function returns. If LINT has found a definition for the function, the first line of the function group is
Function "NAME" returns "TYPE".
where NAME is the name of the function and TYPE is the type of value that the function returns.
If LINT can't find a definition for the function, LINT looks at the first reference to the function that appears in the source code and prints
Function "NAME" is assumed to return "TYPE".
where TYPE is the type suggested by the way in which the function is used. In keeping with the rules of C, LINT assumes that a function returns int if the function is not explicitly declared to have a different type. If the first reference to the function does not use a return value from the function, the message is
Function "NAME" assumed to return no value.
If LINT scans a function definition and finds that there are no return statements that return a value, LINT outputs the message
Function "NAME" returns no value.
You should distinguish between this message and
Function "NAME" returns void.
The void message appears when a function is explicitly declared with the void keyword. The "no value" message appears when a function definition has no return statements.
The first appearance of an external variable is treated in much the same way as the first appearance of a function. You will see a message like
External "NAME" is "TYPE".
or
External "NAME" is assumed to be "TYPE".
With one exception, the messages we have described so far are always issued, whether or not there are errors. The exception is when a symbol appears in a LINT library (described later on) and is not referenced in normal source code. Messages about library functions and variables only appear if the symbol is used in source code.
Other messages in LINT output describe situations that may be errors. These messages refer to difficulties within the function with which they are grouped.
At the end of its output, LINT displays a block of information describing the types of the program. A typical line of output is
filename,lineno: type
where filename is the name of the file where the type was defined and lineno is the line number where the definition began.
LINT provides information about any typedefs, enumerated types, structures, and/or unions that were involved in the messages produced. This lets you locate the definitions of these types.
If a type appears twice in this list, it usually means that there is more than one definition for the same type and the types are not assignment-compatible.
LINT also checks for structures and unions which are used in functions and which have been incompletely defined. Incomplete definitions are acceptable as long as there is a complete definition somewhere. All complete definitions have to be identical. If complete definitions conflict with each other, LINT issues a diagnostic beginning with
filename : type
where filename is the name of a file where the type was used and found to conflict with another definition.
A function is defined by the function header that actually starts the function. This tells the type of value that the function returns and describes the arguments of the function. An external variable is defined by a declaration for the variable that is outside the scope of all functions and that does not include the keyword extern.
A function is declared by a declaration or function call inside another function. An external variable is declared by a declaration that includes the keyword extern. Such a variable declaration does not allocate space for the variable; it merely describes the type of the variable and presumes that the variable is defined elsewhere.
A variable is set if it is assigned a value or if a pointer to the variable is taken. Similarly, a variable is used if its value is used or if a pointer is taken. (You might wonder why taking a pointer marks a variable as both "set" and "used". The answer is that once a pointer to a variable is taken, LINT can't keep track of assignments or uses of the variable through the pointer. Once the pointer is taken, LINT can only assume that the data object may have been both set and used.)
One of the easiest problems for LINT to find is a variable or function that is defined but not used or called in the rest of the program. Such items can usually be deleted, since they are not performing any function in the program. The diagnostic messages for such situations are listed and explained below.
Messages are not generated if a symbol in a LINT library is unused.
Sometimes, you may intentionally declare a variable without using it. For example, suppose a variable is only used inside a #if block and that block of code is not compiled because the #if condition is not met. LINT would normally issue an error message for this condition, but you might not consider this situation an error.
To avoid this sort of message, you can add the LINT directive
#pragma used NAME NAME NAME ...
to your source code. This tells LINT that the names listed in the directive are used by your program, even if it doesn't look like they are.
LINT uses a simple-minded way to find places where undefined variables are used: a variable is assumed to be undefined if a statement using the variable appears in the code before the variable is set. Of course, it is possible to construct programs where this approach fails. With the use of spaghetti-like gotos, the top-down technique can be outwitted.
Because static and external variables are automatically initialized to zero by the compiler if they are not initialized explicitly, LINT does not pick up problems with these variables.
The following diagnostics pertain to data objects that are used before they are set, defined, or declared.
LINT attempts to detect parts of the source code that cannot be reached (for example, unlabelled statements following a goto). It also attempts to find loops that can never be exited from the bottom, such as while(1)... At the same time, LINT finds loops that cannot be entered from the top.
The message that is issued for detected unreachable code is
Non reachable code at "TEXT".
where TEXT is the source code that cannot be reached.
LINT cannot detect functions which are called and never return (for example, ones that terminate execution via exit). This means that it is possible for LINT to miss some unreachable code, as in
g() { exit(-1); } f() { ... g(); /* everything that follows * is unreachable */ ... }
To mark this kind of situation, you can use the LINT directive
#pragma notreached
When this directive appears in your source code, LINT regards any source code following the directive as unreachable, up to the end of the block or the next statement label. Appropriate diagnostic messages will be issued.
LINT reports functions that contain both
return( expression );
and
return;
This kind of situation usually results in errors (since the calling function may assume a value is being returned but one of the return statements does not return a value).
Correspondingly, LINT makes note of whether or not a call to a function makes use of a return value from the function. If the caller expects a value but the function does not return one, it is clearly a bug. If the function returns a value but the the caller does not use it, it could be an error, an inefficiency, or sloppy programming style.
If a function definition contains no return statements that return a value, LINT regards the function type as int unless it is explicitly declared differently. If LINT finds a reference to the function before it finds the function definition, it assumes that the function returns a value unless the function is explicitly declared as void. Programmers who are used to looser controls than this will find themselves with a large number of diagnostic messages.
The messages related to function return values are listed below.
return(expression)
statement.
return(expression);
statement, but the caller never uses the return result.
There is always the possibility that you want to write a function whose return value can be ignored. The C library has several functions whose return value is superfluous. For example,
strcpy(A,B)
copies string B into string A and returns a pointer to A. This is unnecessary, since the caller already has a pointer to A.
LINT lets you mark functions with optional return values by using a directive similar to the #directives of the C preprocessor.
#pragma optresult
can be placed immediately before the definition of a function that returns an optional result. LINT then recognizes that this function returns a result that can be ignored; LINT does not give error messages if the result is ignored.
LINT enforces stronger type-checking than most compilers do. In expressions where different types of data are intermixed, LINT observes the strict conventions for conversions of data and requires that everything else be properly cast.
LINT checks for type incompatibilities in three places:
For fullest type-checking, the code should use prototypes wherever possible. LINT also checks for situations in which some function calls take place with a prototype in scope while others do not.
When arguments are passed to functions, LINT issues a diagnostic if the argument type passed by the caller differs from the argument type expected by the called function. Similarly, LINT complains if the argument type returned by the called function differs from the argument type expected by the caller. The messages associated with this situation are given below.
Argument 2 is "int", declared "unsigned int".
This indicates that the caller passed an integer value as the second argument of the function, but the called function expected an unsigned integer.
LINT also issues a message whenever a data object is implicitly shortened. For example, suppose we have
long l; int i; /* stuff */ i = l;
The C compiler automatically shortens the long "l" and assigns the result to "i". However, LINT flags this as a potential problem (since different integer sizes on different machines may affect how this works). No error message is issued if the conversion is done explicitly, as in
i = (int) l;
As a final note about type-checking, LINT occasionally gets some complicated types wrong. For example, warnings about structures that contain arrays of structures do not give the right type (namely struct). The same problem occurs when LINT is not given the internal structure of a struct. For example, it is valid for a source module to declare
struct ABC *ptr;
without describing the internal structure of the ABC struct. In such cases, LINT does not have enough information to determine if code is valid, so the warning messages may not be entirely accurate.
LINT compares all the available prototype declarations for a function to make sure that the prototypes match exactly. For example, LINT notes situations where one prototype gives void * as the type of an argument while another types the same argument as char *. In practice, there is no difference between these two pointer types; however, LINT makes note of the situation because it may be indicative of an error.
In addition to checking that the argument types expected by a called function match the argument types passed by the caller, LINT also checks that the number of arguments expected by the called function matches the number of arguments passed by the caller. If there is a mismatch, it prints
Called with N arguments, requires M
where N is the number of arguments that the caller passes and M is the number of arguments that the function expects.
Whenever possible, LINT checks to see that argument declarations in function prototypes agree exactly with the declarations in the function definition. If the declarations do not agree, LINT outputs
Prototype argument N is "TYPE" but declared as "TYPE"
LINT expects argument declarations to match exactly. For example, the void * type is considered assignment-compatible with all pointer types and therefore there is not a conflict between void * and another pointer type. Nevertheless, LINT points out the difference, simply to warn you that there seems to be a discrepancy in your code.
Finally, LINT makes sure that the number of arguments in a prototype declaration matches the number in the function definition. If not, LINT prints
Prototyped with N arguments, but requires M
where M and N are both integers.
In earlier versions of C it was valid to pass more arguments than the function definition specifies or to pass fewer arguments. This is not valid in ANSI C unless you use the "..." construct in the prototype; however, older code may still make use of such calling sequences and most compilers still handle the situation properly.
In order to handle all the possibilities of this situation, LINT introduces the #pragma varargs directive. The directive has two forms.
#pragma varargs N
indicates that the next function to be defined can take a variable number of arguments but must have a minimum of N arguments. For example, we might write
#pragma varargs 3 int Mini(N,a,b,c,d); int N,a,b,c,d; { ....
to declare a function Mini that must take at least three arguments and can have more. In this case, LINT does not issue an error message if you have at least three integer arguments present.
With this form of the #pragma varargs directive, LINT typechecks all the arguments that are present. In the above example, LINT ensures that all the arguments being passed are integers.
The second form of the directive is
#pragma varargs N M
where both N and M are integers. This indicates that the next function to be defined must have a minimum of N arguments, and LINT should typecheck up to M arguments if they appear. For example, you can imagine a printf-like function that takes a format string plus one or more values to print. This could be preceded with
#pragma varargs 2 1
showing that the function must always have two arguments (a format string and at least one output value) but that only the first argument should be typechecked (since the output value(s) need not have a fixed type).
The directive can also take the form
#pragma varargs printf #pragma varargs scanf
These forms may be used to indicate functions that take format strings comparable to the strings of printf and scanf (respectively). LINT compares the placeholders in the format string to the types of the arguments that follow the format string, and reports any conflicts between types. For example, it tells you if the argument corresponding to a %s placeholder is an int value (incompatible with %s).
Some functions are able to use all the arguments they are passed without actually referring to every argument by name. For example, consider a function Max10 which can accept up to 10 int arguments and return the value of the largest argument. The first argument passed to Max10 tells how many values have actually been passed. This could be defined with
int Max10(N,a,b,c,d,e,f,g,h,i) int N,a,b,c,d,e,f,g,h,i; { int j,m,*p; m = a; p = &&a for (j=1; j < N; ++j) if (p[j] > m) m = p[j]; return(m); }
As you can see, the function does not refer to the parameters b, c, etc. by name. However, it does look at their values, since it walks through the stack using offsets from a. Therefore the values are used.
This sort of coding practice is a violation of the ANSI standard. The standard doesn't let you use subscripts to access memory locations outside the bounds of a single object, so p[j] is invalid if j is not zero. On the other hand, many compilers still accept code like this and older programs may use it.
For such programs, you can avoid some diagnostic messages from LINT by putting the LINT directive
#pragma argsused
on a line preceding the function, as in
#pragma argsused int Max10(N,a,b,c,d,e,f,g,h,i) ...
This tells LINT that the function uses all its arguments, even if it doesn't appear to.
For compatibility with the Bell Labs' version of LINT, this version of LINT may accept some directives in the form of comments. These are only recognized if the +ControlComments option is specified on the LINT command line.
/*VARARGSN*/
(where N is an integer) is automatically converted to
#pragma varargs 0 N
For example,
/*VARARGS3*/
is equivalent to
#pragma varargs 0 3
In addition,
/*VARARGS*/
(with no value N) is equivalent to
#pragma varargs 0
In addition to VARARGS, the comment
/*ARGSUSED*/
is automatically converted to
#pragma argsused
and the comment
/*NOTREACHED*/
is equivalent to
#pragma notreached
In all cases, the comment form may not contain white space (blanks, tabs, or new-lines). All alphabetic characters must be in upper case and there can be nothing else in the comment except the keyword and any number that should follow.
LINT always uses the full name of functions and variables, and always distinguishes between upper and lower case letters. This is the way that C compilers are supposed to work as well. However, some compilers or loaders truncate long names to a certain number of characters, and some compilers or loaders do not distinguish between the case of letters in names. For this reason, LINT issues the following warnings.
LINT detects redeclaration of functions and variables defined outside the scope of any function. The set of messages dedicated to this kind of problem are given below.
LINT assumes that short integers have a length of 16 bits and that long integers have a length of 32 bits, regardless of what length these data types have on your machine. The reason is that short and long integers are guaranteed to have at least these lengths on all machines. Your hardware may allow longer integers (for example, 36 bits) but code that uses the increased size is not portable to machines with smaller words. For the same reason, LINT assumes that all characters are 8-bit signed quantities, even though they may be longer or unsigned on your machine.
In addition to the diagnostics produced especially for LINT, the LINT output also contains all the error and warning messages produced by the parsing phase of the C compiler. Most of these are self-explanatory, but there are a few that deserve further comment.
while (1) { ... }
In addition to the error messages listed above, you may receive the warning
Possible alignment problems with cast
When casting data of one type to data of another, alignment problems may occur. For example, there may be difficulties in casting a character pointer to an integer pointer if the character pointer is not aimed at a word boundary. At times, however, you may have taken pains to make sure that this kind of alignment problem will not occur. In particular, you may have created a function that returns a value or pointer that is suitably aligned for any use. This is true of functions like malloc; the value that malloc returns is officially a void * pointer, but it is suitably aligned to store any sort of data.
If you have a function that returns a suitably aligned pointer, you can tell LINT by placing
#pragma aligned
in front of the start of the function declaration. This tells LINT that the function itself deals with any alignment problems.
Syntax:
lint [file] [option]*
(+|-)ControlComments (-) (+|-)Declarations (-) (+|-)Keep (-) (+|-)StandardLibrary (+) (+|-)Verbose (-) (+|-)Wide (-) Configuration=file CrossReference=keyword Define=name=value Include=directory Installation=file Library=lib libraryName=string Output=file Output+=file StandardInclude=directory StandardLibrary=name Summary=file Target=keyword indeX=file
Examples:
lint myfile lint x=files o=incls ln=proj def=PROJECT=1
Options:
myfile define=VERSION=1
appeared in an index file, LINT would use the given option when examining myfile but not for other files named in the index file. When processing an index line, LINT normally uses all the options appearing on the main command line, followed by the options specified on the index line. As a special case, however, Include= options on a line in an index file are used before any Include= options on the LINT command line. Only one indeX= option may appear on the command line.
/*VARARGS*/
should be processed. The default is -ControlComments, in which case such comments are ignored. A ControlComments option on a line in an index file overrides any ControlComments option on the main LINT command line.
#define name value
in the C source code. The option indicates that "name" should be replaced with "value" (as text) wherever it appears in the source code being examined. If "value" contains blanks or tab characters, it should be enclosed in double or single quotes.
#include "file"
it begins by searching the given directory for the file. If the file is not found there, LINT searches any directory named in StandardInclude= options, and finally searches the directory that contains the source file being examined. Any number of Include= options may be specified. Directories are searched in the order given on the command line.
#include <file>
it begins by searching the given directory for the file. If the file is not found there, LINT searches directories named in Include= options, and finally searches the directory that contains the source file being examined. Any number of StandardInclude= options may be specified. Directories are searched in the order given on the command line.
The option keywords given above can be abbreviated by omitting any or all of the letters shown in lower case. For example, CrossReference=All may be abbreviated to
crossref=all cref=all cr=all cr=a
and so on. When entering option keywords, you may type letters in upper, lower, or mixed case; the use of upper and lower case in this documentation is simply to show what letters are and aren't required.
In addition to checking C code for irregularities, LINT can:
Summary files and LINT libraries are discussed below.
A summary file contains a "summary" of your source code: the names and types of all the external variables and functions defined or referenced in the code, plus any other information LINT may need when checking the code (such as the declared types of function parameters). Summary files also record any error messages that might be issued by the C compiler when parsing the source code. All of this information is stored in a special format that is not directly readable by humans.
To create a summary file from a normal source code file, use the Output=file option, as in
lint src1.c output=summ
You can add more material to the same summary file using Output+=file, as in
lint src2.c output+=summ
This appends new material to the existing contents of the summary file.
When LINT creates a Summary file, LINT only summarizes your source code and checks for syntax errors. It does not check for such problems as non-portable constructs or type mismatches. However, you can run a summary file through LINT again to do standard type-checking. For example,
lint srcfile output=lf lint summ=lf
creates a summary file, then uses the information in the summary file to see if types match in symbol references and definitions.
The reason for creating summary files is the same as the reason for breaking up the source code of your program into several source files: it is easier to deal with source code in small pieces than in one big hunk. Some users may keep a summary file for every source file. If the code in one source file is changed, you can create a summary file from that source file, then run all the summary files of the program through LINT to see if the change has caused any problems. This is much faster than using LINT on all the raw source code.
A LINT library is similar to a summary file, in that it contains a summary of C source code. However, LINT libraries are intended to parallel the way that object libraries work.
To create a LINT library, specify both the Output= option and the libraryName= option on the LINT command line. For example,
lint file output=mylib libname="abc"
creates a LINT library named abc in the file mylib. This name is used in LINT diagnostic messages related to symbols found in the LINT library. Using LINT in this way only generates minimal messages; full messages are printed when you actually use the LINT library.
To use a LINT library, specify the Library= option on the LINT command line, as in
lint myfile library=mylib
When LINT finds that myfile contains a reference to an undefined symbol, LINT checks the information in the given LINT library to see if the symbol is defined there. This works just like compiling a module while referring to an object library. No error occurs if a symbol in the LINT library has the same name as a symbol in the source file; the source file symbol is the one that is used.
Functions should only be placed in a LINT library when you are sure they contain no errors. LINT does not look for errors when it is creating a LINT library.
To specify a file as an input, simply give the file's name. To specify a summary file as input, use Summary=file. To specify a LINT library as input, use Library=file. LINT uses all of the contents of a source file or a summary file; it only uses library entries if they are referenced by source code or a summary file.
An index file contains partial command lines to be used by LINT in its operations. These command lines can name source files as input, but not summary files or LINT libraries.
When both an Output= and libraryName= option are specified, LINT creates a LINT library. If only Output= is specified, LINT creates a summary file. If Output= is not specified, LINT examines a source file or summary file for problem spots and writes its diagnostics to the standard output.
If LINT finds two definitions for the same function or external variable, it always outputs a diagnostic message. If the two definitions are both in normal source code, LINT arbitrarily chooses the first definition it finds as the "correct" definition, and issues diagnostics for any later deviations from this definition. If one of the two definitions is in a LINT library and the other is in normal source code, LINT chooses the non-library version as the "correct" definition. In this way, a definition in normal source code overrides a library definition.
A configuration file consists of a series of directives that control the behavior of LINT. The possible directives are explained below.
inline sim.h extern int junk1; extern int junk2; #
LINT collects the two declarations and associates them with the name ""sim.h"". If a program contains the directive,
#include <sim.h>
LINT takes the gathered text and includes it at that point in the program, as if it had come from an included file.
#include includename1
should be converted to
#include includename2
An installation file specifies the pathnames for software and data files used by LINT. Installation files are text files made up of comment lines and option lines.
Keyword=pathname
In this documentation, keywords are written with some letters in upper case and some in lower case. You may abbreviate keywords by omitting any or all of the letters shown in lower case. The remaining letters may be entered in either upper or lower case; the documentation simply uses upper case to show which characters may not be omitted.
In this version of LINT, possible option lines are: