lint-specific diagnostics are issued for three broad categories of conditions: inconsistent use, nonportable code, and questionable constructs. In this section, we review examples of lint’s behavior in each of these areas, and suggest possible responses to the issues they raise.
Inconsistent use of variables, arguments, and functions is checked within files as well as across them. Generally speaking, the same checks are performed for prototype uses, declarations, and parameters as lint checks for old-style functions. If your program does not use function prototypes, lint checks the number and types of parameters in each call to a function more strictly than the compiler. lint also identifies mismatches of conversion specifications and arguments in [fs]printf() and [fs]scanf() control strings.
Examples:
Within files, lint flags non-void functions that “fall off the bottom” without returning a value to the invoking function. In the past, programmers often indicated that a function was not meant to return a value by omitting the return type: fun() {}. That convention means nothing to the compiler, which regards fun() as having the return type int. Declare the function with the return type void to eliminate the problem.
Across files, lint detects cases where a non-void function does not return a value, yet is used for its value in an expression—and the opposite problem, a function returning a value that is sometimes or always ignored in subsequent calls. When the value is always ignored, it may indicate an inefficiency in the function definition. When it is sometimes ignored, it’s probably bad style (typically, not testing for error conditions). If you need not check the return values of string functions like strcat(), strcpy(), and sprintf(), or output functions like printf() and putchar(), cast the offending calls to void.
lint identifies variables or functions that are declared but not used or defined; used, but not defined; or defined, but not used. When lint is applied to some, but not all files of a collection to be loaded together, it produces error messages about functions and variables that are:
Declared in those files, but defined or used elsewhere
Used in those files, but defined elsewhere
Defined in those files, but used elsewhere
Invoke the-x option to suppress the first complaint, -u to suppress the latter two.
Some nonportable code is flagged by lint in its default behavior, and a few more cases are diagnosed when lint is invoked with -p or -Xc. The latter causes lint to check for constructs that do not conform to the ISO C standard. For the messages issued under -p and -Xc, see 4.6.2 lint Libraries.
Examples:
In some C language implementations, character variables that are not explicitly declared signed or unsigned are treated as signed quantities with a range typically from -128 to 127. In other implementations, they are treated as nonnegative quantities with a range typically from 0 to 255. So the test:
char c; c = getchar(); if (c == EOF) ... |
where EOF has the value -1, always fails on machines where character variables take on nonnegative values. lint invoked with -p checks all comparisons that imply a plain char may have a negative value. However, declaring c as a signed char in the above example eliminates the diagnostic, not the problem. That’s because getchar() must return all possible characters and a distinct EOF value, so a char cannot store its value. We cite this example, perhaps the most common one arising from implementation-defined sign-extension, to show how a thoughtful application of lint’s portability option can help you discover bugs not related to portability. In any case, declare c as an int.
A similar issue arises with bit-fields. When constant values are assigned to bit-fields, the field may be too small to hold the value. On a machine that treats bit-fields of type int as unsigned quantities, the values allowed for int x:3 range from 0 to 7, whereas on machines that treat them as signed quantities, they range from -4 to 3. However, a three-bit field declared type int cannot hold the value 4 on the latter machines. lint invoked with -p flags all bit-field types other than unsigned int or signed int. These are the only portable bit-field types. The compiler supports int, char, short, and long bit-field types that may be unsigned, signed, or plain. It also supports the enum bit-field type.
Bugs can arise when a larger-sized type is assigned to a smaller-sized type. If significant bits are truncated, accuracy is lost:
short s; long l; s = l; |
lint flags all such assignments by default; the diagnostic can be suppressed by invoking the -a option. Bear in mind that you may be suppressing other diagnostics when you invoke lint with this or any other option. Check the list in 4.6.2 lint Libraries for the options that suppress more than one diagnostic.
A cast of a pointer to one object type to a pointer to an object type with stricter alignment requirements may not be portable. lint flags:
int *fun(y) char *y; { return(int *)y; } |
because, on most machines, an int cannot start on an arbitrary byte boundary, whereas a char can. You can suppress the diagnostic by invoking lint with -h, although, again, you may be disabling other messages. Better still, eliminate the problem by using the generic pointer void *.
ISO C leaves the order of evaluation of complicated expressions undefined. That is, when function calls, nested assignment statements, or the increment and decrement operators cause side effects when a variable is changed as a by-product of the evaluation of an expression, the order in which the side effects take place is highly machine-dependent. By default, lint flags any variable changed by a side effect and used elsewhere in the same expression:
int a[10]; main() { int i = 1; a[i++] = i; } |
In this example, the value of a[1] may be 1 if one compiler is used, 2 if another. The bitwise logical operator & can give rise to this diagnostic when it is mistakenly used in place of the logical operator &&:
if ((c = getchar()) != EOF & c != ’0’) |
lint flags a miscellany of legal constructs that may not represent what the programmer intended. Examples:
An unsigned variable always has a nonnegative value. So the test:
unsigned x; if (x < 0) ... |
always fails. The test:
unsigned x; if (x > 0) ... |
is equivalent to:
if (x != 0) ... |
This may not be the intended action. lint flags questionable comparisons of unsigned variables with negative constants or 0. To compare an unsigned variable to the bit pattern of a negative number, cast it to unsigned:
if (u == (unsigned) -1) ... |
Or use the U suffix:
if (u == -1U) ... |
lint flags expressions without side effects that are used in a context where side effects are expected—that is, where the expression may not represent what the programmer intends. It issues an additional warning whenever the equality operator is found where the assignment operator is expected—that is, where a side effect is expected:
int fun() { int a, b, x, y; (a = x) && (b == y); } |
lint cautions you to parenthesize expressions that mix both the logical and bitwise operators (specifically, &, |, ^, <<, >>), where misunderstanding of operator precedence may lead to incorrect results. Because the precedence of bitwise &, for example, falls below logical ==, the expression:
if (x & a == 0) ... |
is evaluated as:
if (x & (a == 0)) ... |
which is most likely not what you intended. Invoking lint with -h disables the diagnostic.