6    Checking C Programs with lint

You can use the lint program to check your C programs for potential coding problems. The lint program checks a program more carefully than some C compilers, and displays messages that point out possible problems. Some of the messages require corrections to the source code; others are only informational messages and do not require corrections.

This chapter addresses the following topics:

See lint(1) for a complete list of lint options.

6.1    Syntax of the lint Command

The lint command has the following syntax:

lint [ options ] [ file ... ]

options

Options to control lint checking operations.

The cc driver options, -std, -std0, and -std1, are available as options to lint. These options affect the parsing of the source as well as the selection of the lint library to use. Selecting either the -std or -std1 options turns on ANSI parsing rules in lint.

When you use the -MA lint option, -std1 is used for the C preprocessing phase and _ANSI_C_SOURCES is defined using the -D preprocessor option. The following table describes the action lint takes for each option:

lint Option Preprocessor Switch lint Parsing lint Library
-MA -std1 and -D_ANSI_C_SOURCE ANSI llib-lansi.ln
-std -std ANSI llib-lcstd.ln
-std1 -std1 ANSI llib-lcstd.ln
-std0 -std0

EXTD [Footnote 5]

llib-lc.ln

file

The name of the C language source file for lint to check. The name must have one of the following suffixes:

Suffix Description
.c C source file
.i File produced by the C preprocessor (cpp)
.ln lint library file

Note that lint library files are the result of a previous invocation of the lint program with either the -c or -o option. They are analogous to the .o files produced by the cc command when it is given a .c file as input. The ability to specify lint libraries as input to the lint program facilitates intermodule interface checking in large applications. Adding rules that specify the construction of lint libraries to their makefiles can make building such applications more efficient. See Section 6.10 for a discussion on how to create a lint library.

You can also specify as input a lint library that resides in one of the system's default library search directories by using the -lx option. The library name must have the following form:

llib-llibname.ln

By default, the lint program appends the extended C (K&R C) lint library (llib-lc.ln) to the list of files specified on the command line. If the -std or -std1 option is used, it appends the standard C lint library (llib-lcstd.ln) instead.

The following additional libraries are included with the system:

Library Description Specify As
crses Checks curses library call syntax -lcrses
m Checks math library call syntax -lm
port Checks for portability with other systems -p (not -lport)
ansi Enforces ANSI C standard rules -MA (not -lansi)

If you specify no options on the command line, the lint program checks the specified C source files and writes messages about any of the following coding problems that it finds:

The lint program also checks for syntax errors in statements in the source programs. Syntax checking is always done and is not influenced by any options that you specify on the lint command.

If lint does not report any errors, the program has correct syntax and will compile without errors. Passing that test, however, does not mean that the program will operate correctly or that the logic design of the program is accurate.

See Section 6.10 for information on how to create your own lint library.

6.2    Program Flow Checking

The lint program checks for dead code, that is, parts of a program that are never executed because they cannot be reached. It writes messages about statements that do not have a label but immediately follow statements that change the program flow, such as goto, break, continue, and return.

The lint program also detects and writes messages for loops that cannot be entered at the top. Some programs that include this type of loop may produce correct results; however, this type of loop can cause problems.

The lint program does not recognize functions that are called but can never return to the calling program. For example, a call to exit may result in code that cannot be reached, but lint does not detect it.

Programs generated by yacc and lex may have hundreds of break statements that cannot be reached. The lint program normally writes an error message for each of these break statements. To eliminate the extraneous code associated with these break statements, use the -O option to the cc command when compiling the program. Use the -b option with the lint program to prevent it from writing these messages when checking yacc and lex output code. (For information on yacc and lex, see the Programming Support Tools manual.)

6.3    Data Type Checking

The lint program enforces the type-checking rules of the C language more strictly than the compiler does. In addition to the checks that the compiler makes, lint checks for potential data type errors in the following areas:

Details on each of these potential problem areas are provided in the sections that follow.

6.3.1    Binary Operators and Implied Assignments

The C language allows the following data types to be mixed in statements, and the compiler does not indicate an error when they are mixed:

char
short
int
long
unsigned
float
double

The C language automatically converts data types within this group to provide the programmer with more flexibility in programming. This flexibility, however, means that the programmer, not the language, must ensure that the data type mixing produces the desired result.

You can mix these data types when using them in the following ways (in the examples, alpha is type char and num is type int):

The data types of pointers must agree exactly, except that you can mix arrays of any type with pointers to the same type.

6.3.2    Structures and Unions

The lint program checks structure operations for the following requirements:

The lint program makes similar checks for references to unions.

6.3.3    Function Definition and Uses

The lint program applies strict rules to function argument and return value matching. Arguments and return values must agree in type, with the following exceptions:

6.3.4    Enumerators

The lint program checks enumerated data type variables to ensure that they meet the following requirements:

6.3.5    Type Casts

Type casts in the C language allow the program to treat data of one type as if it were data of another type. The lint program can check for type casts and write a message if it finds one.

The -wp and -h options for the lint command line control the writing of warning messages about casts. If neither of these options are used, lint produces warning messages about casts that may cause portability problems.

In migration checking mode, -Qc suppresses cast warning messages (see Section 6.6).

6.4    Variable and Function Checking

The lint program checks for variables and functions that are declared in a program but not used. The lint program checks for the following errors in the use of variables and functions:

Details on each of these potential problem areas are provided in the sections that follow.

6.4.1    Inconsistent Function Return

If a function returns a value under one set of conditions but not under another, you cannot predict the results of the program. The lint program checks functions for this type of behavior. For example, if both of the following statements are in a function definition, a program calling the function may or may not receive a return value:

return(expr);

.
.
.
return;

These statements cause the lint program to write the following message to point out the potential problem:

function name has return(e); and return

The lint program also checks functions for returns that are caused by reaching the end of the function code (an implied return). For example, in the following part of a function, if a tests false, checkout calls fix_it and then returns with no defined return value:

checkout (a)
{
        if (a) return (3);
        fix_it ();
}

These statements cause the lint program to write the following message:

function checkout has return(e); and return
 

If fix_it, like exit, never returns, lint still writes the message even though nothing is wrong.

6.4.2    Function Values That Are Not Used

The lint program checks for cases in which a function returns a value and the calling program may not use the value. If the value is never used, the function definition may be inefficient and should be examined to determine whether it should be modified or eliminated. If the value is sometimes used, the function may be returning an error code that the calling program does not check.

6.4.3    Disabling Function-Related Checking

To prevent lint from checking for problems with functions, specify one or more of the following options to the lint command:

-x Do not check for variables that are declared in an extern statement but never used.
-v Do not check for arguments to functions that are not used, except for those that are also declared as register arguments.
-u Do not check for functions and external variables that are either used and not defined or defined and not used. Use this option to eliminate useless messages when you are running lint on a subset of files of a larger program. (When using lint with some, but not all, files that operate together, many of the functions and variables defined in those files may not be used. Also, many functions and variables defined elsewhere may be used.)

You can also place directives in the program to control checking:

6.5    Checking on the Use of Variables Before They Are Initialized

The lint program checks for the use of a local variable (auto and register storage classes) before a value has been assigned to it. Using a variable with an auto (automatic) or register storage class also includes taking the address of the variable. This is necessary because the program can use the variable (through its address) any time after it knows the address of the variable. Therefore, if the program does not assign a value to the variable before it finds the address of the variable, lint reports an error.

Because lint only checks the physical order of the variables and their usage in the file, it may write messages about variables that are initialized properly (in execution sequence).

The lint program recognizes and writes messages about:

Note

The Tru64 UNIX operating system initializes static and extern variables to zero. Therefore, lint assumes that these variables are set to zero at the start of the program and does not check to see if they have been assigned a value when they are used. When developing a program for a system that does not do this initialization, ensure that the program sets static and extern variables to an initial value.

6.6    Migration Checking

Use lint to check for all common programming techniques that might cause problems when migrating programs from 32-bit operating systems to the Tru64 UNIX operating system. The -Q option provides support for checking ULTRIX and DEC OSF/1 Version 1.0 programs that you are migrating to 64-bit systems.

Because the -Q option disables checking for most other programming problems, use this option only for migration checking. Suboptions are available to suppress specific categories of checking. For example, entering -Qa suppresses the checking of pointer alignment problems. You can enter more than one suboption with the -Q option, for example, -QacP to suppress checking for pointer alignment problems, problematic type casts, and function prototype checks, respectively. For more information about migration checking, see lint(1).

6.7    Portability Checking

Use lint to help ensure that you can compile and run C programs using different C language compilers and other systems.

The following sections indicate areas to check before compiling the program on another system. Checking only these areas, however, does not guarantee that the program will run on any system.

Note

The llib-port.ln library is brought in by using the -p option, not by using the -lport option.

6.7.1    Character Uses

Some systems define characters in a C language program as signed quantities with a range from -128 to 127; other systems define characters as positive values. The lint program checks for character comparisons or assignments that may not be portable to other systems. For example, the following fragment may work on one system but fail on systems where characters always take on positive values:

char c;

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

This statement causes the lint program to write the following message:

nonportable character comparison

To make the program work on systems that use positive values for characters, declare c as an integer because getchar returns integer values.

6.7.2    Bit Field Uses

Bit fields may produce problems when a program is transferred to another system. Bit fields may be signed quantities on the new system. Therefore, when constant values are assigned to a bit field, the field may be too small to hold the value. To make this assignment work on all systems, declare the bit field to be of type unsigned before assigning values to it.

6.7.3    External Name Size

When changing from one type of system to another, be aware of differences in the information retained about external names during the loading process:

When transferring from one system to another, you should always take the following steps to avoid problems with loading a program:

  1. Review the requirements of each system.

  2. Run lint with the -p option.

The -p option tells lint to change all external symbols to lowercase and limit them to six characters while checking the input files. The messages produced identify the terms that may need to be changed.

6.7.4    Multiple Uses and Side Effects

Be careful when using complicated expressions because of the following considerations:

The following situations demonstrate the types of problems that can result from these differences:

6.8    Checking for Coding Errors and Coding Style Differences

Use lint to detect possible coding errors, and to detect differences from the coding style that lint expects. Although coding style is mainly a matter of individual taste, examine each difference to ensure that the difference is both needed and accurate. The following sections indicate the types of coding and style problems that lint can find.

6.8.1    Assignments of Long Variables to Integer Variables

If you assign variables of type long to variables of type int, the program may not work properly. The long variable is truncated to fit in the integer space and data may be lost.

An error of this type frequently occurs when a program that uses more than one typedef is converted to run on a different system.

To prevent lint from writing messages when it detects assignments of long variables to int variables, use the -a option.

6.8.2    Operator Precedence

The lint program detects possible or potential errors in operator precedence. Without parentheses to show order in complex sequences, these errors can be hard to find. For example, the following statements are not clear:

 if(x&077==0). . .   /* evaluated as: if(x & (077 == 0) ) */
                     /* should be: if( (x & 077) == 0) */
 
 x<<2+40             /* evaluated as: x <<(2+40) */
                     /* should be: (x<<2) + 40 */
                     /* shift x left 42 positions */

Use parentheses to make the operation more clearly understood. If you do not, lint issues a message.

6.8.3    Conflicting Declarations

The lint program writes messages about variables that are declared in inner blocks in ways that conflict with their use in outer blocks. This practice is allowed, but may cause problems in the program.

Use the -h option with the lint program to prevent lint from checking for conflicting declarations.

6.9    Increasing Table Size

The lint command provides the -N option and related suboptions to allow you to increase the size of various internal tables at run time if the default values are not enough for your program. These tables include:

These tables are dynamically allocated by the lint program. Using the -N option on large source files can improve performance.

6.10    Creating a lint Library

For programming projects that define additional library routines, you can create an additional lint library to check the syntax of the programs. Using this library, the lint program can check the new functions in addition to the standard C language functions. To create a new lint library, follow these steps:

  1. Create an input file that defines the new functions.

  2. Process the input file to create the lint library file.

  3. Run lint using the new library.

The following sections describe these steps.

6.10.1    Creating the Input File

The following example shows an input file that defines three additional functions for lint to check:

/*LINTLIBRARY*/
 
#include <dms.h>
 
int  dmsadd( rmsdes, recbuf, reclen )
               int rmsdes;
               char *recbuf;
               unsigned reclen;
             { return 0; }
int  dmsclos( rmsdes)
               int rmsdes;
             { return 0; }
int  dmscrea( path, mode, recfm, reclen )
               char *path;
               int mode;
               int recfm;
               unsigned reclen;
             { return 0; }

The input file is a text file that you create with an editor. It consists of:

Alternatively, you can create a lint library file from function prototypes. For example, assume that the dms.h file includes the following prototypes:

int dmsadd(int,
           char*,
           unsigned);
int dmsclose(int);
int dmscrea(char*,
            int,
            int,
            unsigned);

In this case, the input file contains the following:

/*LINTSTDLIB*/
#include <dms.h>

In the case where a header file may include other headers, the LINTSTDLIB command can be restricted to specific files:

/*LINTSTDLIB_dms.h*/

In this case, only prototypes declared in dms.h will be expanded. Multiple LINTSTDLIB commands can be included.

In all cases, the name of the input file must have the prefix llib-l. For example, the name of the sample input file created in this section could be llib-ldms. When choosing the name of the file, ensure that it is not the same as any of the existing files in the /usr/ccs/lib directory.

6.10.2    Creating the lint Library File

The following command creates a lint library file from the input file described in the previous section:

% lint [options] -c llib_ldms.c

This command tells lint to create a lint library file, llib-ldms.ln, using the file llib-ldms.c as input. To use llib-ldms.ln as a system lint library (that is, a library specified in the -lx option of the lint command), move it to /usr/ccs/lib. Use the -std or -std1 option to use ANSI preprocessing rules to build the library.

6.10.3    Checking a Program with a New Library

To check a program using a new library, use the lint command with the following format:

lint -lpgm filename.c
        

The variable pgm represents the identifier for the library, and the variable filename.c represents the name of the file containing the C language source code that is to be checked. If no other options are specified, the lint program checks the C language source code against the standard lint library in addition to checking it against the indicated special lint library.

6.11    Understanding lint Error Messages

Although most error messages produced by lint are self-explanatory, certain messages may be misleading without additional explanation. Usually, once you understand what a message means, correcting the error is straightforward. The following is a list of the more ambiguous lint messages:

constant argument to NOT

A constant is used with the NOT operator (!). This is a common coding practice and the message does not usually indicate a problem. The following program demonstrates the type of code that can generate this message:

% cat x.c
#include <stdio.h>
#define SUCCESS    0
 
main()
{
	  int value = !SUCCESS;
 
	  printf("value = %d\n", value);
	  return 0;
}
% lint -u x.c
"x.c", line 7: warning: constant argument to NOT
% ./x
value = 1
% 

The program runs as expected, even though lint complains.

Recommended Action: Suppress these lint warning messages by using the -wC option.

constant in conditional context

A constant is used where a conditional is expected. This problem occurs often in source code due to the way in which macros are encoded. For example:

typedef struct _dummy_q {
  int lock;
  struct _dummy_q *head, *tail;
} DUMMY_Q;
 
#define QWAIT   1
#define QNOWAIT 0
#define DEQUEUE(q, elt, wait)    [1]         \
        for (;;) {                           
            simple_lock(&(q)->lock);         
        if (queue_empty(&(q)->head))         
            if (wait) {          [1]         \
                assert(q);                   
                simple_unlock(&(q)->lock);   
                continue;                    
            } else                           
                *(elt) = 0;                  
        else                                 
                  dequeue_head(&(q)->head);  
                  simple_unlock(&(q)->lock); 
        break;                               
    }
 
int doit(DUMMY_Q *q, int *elt)
{
  DEQUEUE(q, elt, QNOWAIT);
}

  1. The QWAIT or QNOWAIT option is passed as the third argument (wait) and is later used in the if statement. The code is correct, but lint issues the warning because constants used in this way are normally unnecessary and often generate wasteful or unnecessary instructions. [Return to example]

Recommended Action: Suppress these lint warning messages by using the -wC option.

conversion from long may lose accuracy

  • A signed long is copied to a smaller entity (for example, an int). This message is not necessarily misleading; however, if it occurs frequently, it may or may not indicate a coding problem, as shown in the following example:

    long BuffLim = 512;         [1]
     
    void foo (buffer, size)
    char *buffer;
    int size;
    {
    register int count;
    register int limit = size < (int)BufLimit ? size : (int)BufLim;  [1]
     
     
    

    1. The lint program reports the conversion error, even though the appropriate (int) cast exists. [Return to example]

    Recommended Action: Review code sections for which lint reports this message, or suppress the message by using the -wl option.
  • declaration is missing declarator

  • A line in the declaration section of the program contains just a semicolon (;). Although you would not deliberately write code like this, it is easy to inadvertently generate such code by using a macro followed by a semicolon. If, due to conditionalization, the macro is defined as empty, this message can result.

    Recommended Action: Remove the trailing semicolon.

  • degenerate unsigned comparison

  • An unsigned comparison is being performed against a signed value when the result is expected to be less than zero. The following program demonstrates this situation:

    % cat x.c
    #include <stdio.h>
    unsigned long offset = -1;
     
    main()
    {
        if (offset < 0) {               [1]
            puts ("code is Ok...");
            return 0;
        } else {
            puts ("unsigned comparison failed...");
            return 1;
        }
    }
    % cc -g -o x x.c
    % lint x.c
    "x.c" line 7: warning: degenerate unsigned comparison
    % ./x
    unsigned comparison failed...
    % 
    

    1. Unsigned comparisons such as this will fail if the unsigned variable contains a negative value. The resulting code may be correct, depending upon whether the programmer intended a signed comparison. [Return to example]

    Recommended Action: You can fix the previous example in two ways:

    • Add a (long) cast before offset in the if comparison.

    • Change the declaration of offset from unsigned long to long.

    In certain cases, it might be necessary to cast the signed value to unsigned.

  • function prototype not in scope

  • This error is not strictly related to function prototypes, as the message implies. Actually, this error occurs from invoking any function that has not been previously declared or defined.

    Recommended Action: Add the function prototype declaration.

  • null effect

  • The lint program detected a cast or statement that does nothing. The following code segments demonstrate various coding practices that cause lint to generate this message:

        scsi_slot = device->ctlr_hd->slot,unit_str;     [1]
     
        #define MCLUNREF(p)             \
                (MCLMAPPED(p) && --mclrefcnt[mtocl(p)] == 0)
     
        (void) MCLUNREF(m);                             [2]
     
     
    

    1. Reason: unit_str does nothing. [Return to example]

    2. Reason: (void) is unnecessary; MCLUNREF is a macro. [Return to example]

    Recommended Action: Remove unnecessary casts or statements, or update macros.
  • possible pointer alignment problem

  • A pointer is used in a way that may cause an alignment problem. The following code segment demonstrates the type of code that causes lint to generate this message:

    read(p, args, retval)
            struct proc *p;
            void *args;
            long *retval;
    {
            register struct args {
                    long    fdes;
                    char    *cbuf;
                    unsigned long  count;
            } *uap = (struct args *) args;          [1]
            struct uio auio;
            struct iovec aiov;
    

    1. The line *uap = (struct args *) args causes the error to be reported. Because this construct is valid and occurs throughout the kernel source, this message is filtered out. [Return to example]

  • precision lost in field assignment

  • An attempt was made to assign a constant value to a bit field when the field is too small to hold the value. The following code segment demonstrates this problem:

    % cat x.c
    struct bitfield {
        unsigned int block_len : 4;
    } bt;
     
    void
    test()
    {
        bt.block_len = 0xff;
    }
    % lint -u x.c
    "x.c", line 8: warning: precision lost in field assignment
    % cc -c -o x x.c
    %
    

    This code compiles without error. However, because the bit field may be too small to hold the constant, the results may not be what the programmer intended and a run-time error may occur.

    Recommended Action: Change the bit field size or assign a different constant value.

  • unsigned comparison with 0

  • An unsigned comparison is being performed against zero when the result is expected to be equal to or greater than zero.

    The following program demonstrates this problem:

    % cat z.c
    #include <stdio.h>
    unsigned offset = -1;
     
    main()
    {
        if (offset > 0) {               [1]
            puts("unsigned comparison with 0 Failed");
            return 1;
        } else {
            puts("unsigned comparison with 0 is Ok");
            return 0;
        }
    }
    % cc -o z z.c
    % lint z.c
    "z.c", line 7: warning: unsigned comparison with 0?
    % ./z
    unsigned comparison with 0 Failed
    % 
    

    1. Unsigned comparisons such as this will fail if the unsigned variable contains a negative value. The resulting code may not be correct, depending on whether the programmer intended a signed comparison. [Return to example]

    Recommended Action: You can fix the previous example in two ways:

    • Add an (int) cast before offset in the if comparison.

    • Change the declaration of offset from unsigned to int.

  • 6.12    Using Warning Class Options to Suppress lint Messages

    Several lint warning classes have been added to the lint program to allow the suppression of messages associated with constants used in conditionals, portability, and prototype checks. By using the warning class option to the lint command, you can suppress messages in any of the warning classes.

    The warning class option has the following format:

    -wclass [ class... ]

    All warning classes are active by default, but may be individually deactivated by including the appropriate option as part of the class argument. Table 6-1 lists the individual options.

    Note

    Several lint messages depend on more than one warning class. Therefore, you may need to specify several warning classes for the message to be suppressed. Notes in Table 6-1 indicate which messages can be suppressed only by specifying multiple warning classes.

    For example, because lint messages related to constants in conditional expressions do not necessarily indicate a coding problem (as described in Section 6.11), you may decide to use the -wC option to suppress them.

    The -wC option suppresses the following messages:

    Because many of the messages associated with portability checks are related to non-ANSI compilers and limit restrictions that do not exist in the C compiler for Tru64 UNIX, you can use the -wp option to suppress them. The -wp option suppresses the following messages:

    Although the use of function prototypes is a recommended coding practice (as described in Section 6.13), many programs do not include them. You can use the -wP option to suppress prototype checks. The -wP option suppresses the following messages:

    Table 6-1:  lint Warning Classes

    Warning Class Description of Class
    a

    Non-ANSI features. Suppresses:

    c

    Comparisons with unsigned values. Suppresses:

    • Comparison of unsigned with negative constant

    • Degenerate unsigned comparison

    • Possible unsigned comparison with 0

    d

    Declaration consistency. Suppresses:

    h

    Heuristic complaints. Suppresses:

    k

    K&R type code expected. Suppresses:

    l

    Assign long values to non-long variables. Suppresses:

    • Conversion from long may lose accuracy

    • Conversion to long may sign-extend incorrectly

    n

    Null-effect code. Suppresses:

    o

    Unknown order of evaluation. Suppresses:

    • Precedence confusion possible: parenthesize! [Footnote 7]

    • %s evaluation order undefined

    p

    Various portability concerns. Suppresses:

    • Ambiguous assignment for non-ANSI compilers

    • Illegal cast in a constant expression

    • Long in case or switch statement may be truncated in non-ANSI compilers

    • Nonportable character comparison

    • Possible pointer alignment problem, op %s [Footnote 7]

    • Precision lost in assignment to (possibly) sign-extended field

    • Precision lost in field assignment

    • Too many characters in character constant

    r

    Return statement consistency. Suppresses:

    • Function %s has return(e); and return;

    • Function %s must return a value

    • main() returns random value to invocation environment

    S

    Storage capacity checks. Suppresses:

    • Array not large enough to store terminating null

    • Constant value (0x%x) exceeds (0x%x)

    u

    Proper usage of variables and functions. Suppresses:

    A

    Activate all warnings. Default option in lint script. Specifying another A class toggles the setting of all classes.

    C

    Constants occurring in conditionals. Suppresses:

    D

    External declarations are never used. Suppresses:

    • Static %s %s unused

    O

    Obsolete features. Suppresses:

    • Storage class not the first type specifier

    P

    Prototype checks. Suppresses:

    • Function prototype not in scope [Footnote 6]

    • Mismatched type in function argument

    • Mix of old- and new-style function declaration

    • Old-style argument declaration [Footnote 6]

    • Use of old-style function definition in presence of prototype

    R

    Detection of unreachable code. Suppresses:

    • Statement not reached

    6.13    Generating Function Prototypes for Compile-Time Detection of Syntax Errors

    In addition to correcting the various errors reported by the lint program, we recommend adding function prototypes to your program for both external and static functions. These declarations provide the compiler with information it needs to check arguments and return values.

    The cc compiler provides an option that automatically generates prototype declarations. By specifying the -proto[is] option for a compilation, you create an output file (with the same name as the input file but with a .H extension) that contains the function prototypes. The i option includes identifiers in the prototype, and the s option generates prototypes for static functions as well.

    You can copy the function prototypes from a .H file and place them in the appropriate locations in the source and include files.