old-www/LDP/LG/issue51/pramode.html

443 lines
16 KiB
HTML

<!--startcut ==============================================-->
<!-- *** BEGIN HTML header *** -->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
<HTML><HEAD>
<title>Static checking of C programs with LCLint LG #51</title>
</HEAD>
<BODY BGCOLOR="#FFFFFF" TEXT="#000000" LINK="#0000FF" VLINK="#0000AF"
ALINK="#FF0000">
<!-- *** END HTML header *** -->
<!-- *** BEGIN navbar *** -->
<A HREF="index.html"><IMG ALT="[ Table of Contents ]"
SRC="../gx/indexnew.gif" WIDTH=163 HEIGHT=60 ALIGN=bottom ></A>
<A HREF="../index.html"><IMG ALT="[ Front Page ]"
SRC="../gx/homenew.gif" WIDTH=163 HEIGHT=60 ALIGN=bottom></A>
<A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/back2.gif" WIDTH=41 HEIGHT=60 ALIGN=bottom></A>
<A HREF="../faq/index.html"><IMG ALT="[ Linux Gazette FAQ ]"
SRC="./../gx/dennis/faq.gif"WIDTH=163 HEIGHT=60 ALIGN=bottom></A>
<A HREF="steffler.html"><IMG ALT="[ Next ]" SRC="../gx/fwd.gif" WIDTH=41 HEIGHT=60 ALIGN=bottom ></A>
<!-- *** END navbar *** -->
<!--endcut ============================================================-->
<H4>
"Linux Gazette...<I>making Linux just a little more fun!</I>"
</H4>
<P> <HR> <P>
<!--===================================================================-->
<center>
<H1><font color="maroon">Static checking of C programs with LCLint</font></H1>
<H4>By <a href="mailto:iclabs@vsnl.com">Pramode C E and Gopakumar C E</a></H4>
</center>
<P> <HR> <P>
<!-- END header -->
<BLOCKQUOTE>
<p> "Thou shalt run lint frequently and study its pronouncements with
care, for verily its perception and judgment oft exceed thine."
<DIV ALIGN="right"><cite>-Henry Spencer, "The Ten Commandments for C Programmers"</cite></DIV>
</BLOCKQUOTE>
<p> C programmers take pride in thinking(and often proclaiming to the
world) that they know what they are doing. This supreme self confidence
(or shall we say arrogance?) is not a bad thing - but a little caution
is always judicious as C is a language with many dark corners(why
should people write books like "C Traps and Pitfalls"?). Taking Lint as
a companion with you in your journey into the dark woods of C will
always be worthwhile - though this companion is at times a bit noisy
and tiring!
<H2><b>What is Lint?</b></H2>
<p>In the good old days(it is said), a decision was made to take out
full semantic checking from the C compiler and put it in a stand alone
program called <b>lint</b>(the usual reasons - making the compiler
smaller, simpler and faster - worshiping at the altar of the little
Tin God of efficiency). The C programmer, so sure of himself,
never took the trouble to run lint on his code - with the extremely
gratifying result that he got buggy code which compiled very fast! Lint
is a tool which shows you how your smart C compiler may spring
surprises on you - ignore him at your own peril.
<H2><b>Which Lint should I use?</b></H2>
<p>You can give LCLint a try. LCLint is a powerful tool which is available for free in source form from <a href="http://lclint.cs.virginia.edu/ftp/lclint/lclint-2.4b.src.tar.gz">http://lclint.cs.virginia.edu/ftp/lclint/lclint-2.4b.src.tar.gz</a>
LCLint, as we will see later, is much
more than a lint.</p>
<H2><b>What can LCLint do?</b></H2>
<p>LCLint does the traditional lint checks like detecting:
<ol>
<li>Unused declarations
<li>Type inconsistencies
<li>Unreachable code
<li>Use before definition
<li>Likely infinite loops and fall through cases
<li>Ignored return values and execution paths with no return
</ol>
But the specialty of LCLint is that it can do much more powerful and extensive checks by making use of
annotations (in the form of special comments) in your source program.
<H2><b>Show me LCLint in action</b></H2>
<p>Here is a small C program:
<PRE>
main()
{
int a[10];
if (sizeof(a)/sizeof(a[0]) &gt; -1)
printf("hello\n");
}
</PRE>
We expected this to print hello, but it did not. gcc did not give us any hint. Let us see what lint has to say
about this beauty. Here is the output from running 'lclint a.c':
<PRE>
LCLint 2.4b --- 18 Apr 98
a.c: (in function main)
a.c:4:15: Operands of &gt; have incompatible types (arbitrary unsigned integral
type, int): sizeof((a)) / sizeof((a[0])) &gt; -1
To ignore signs in type comparisons use +ignoresigns
a.c:6:2: Path with no return in function declared to return int
There is a path through a function declared to return a value on which there
is no return statement. This means the execution may fall through without
returning a meaningful result to the caller. (-noret will suppress message)
Finished LCLint checking --- 2 code errors found
</PRE>
Oh, oh, sizeof gives you the size as an unsigned value. We are comparing this to -1, which when interpreted
as an unsigned yields a big number.</p>
<p> The output of LCLint is verbose, but it is in a form readable by ordinary mortals, and not ANSI (or ISO or
whatever) legalese. The output also displays enough of context to help us immediately locate the trouble spot.
Note that we are also told how to turn off such errors, ie, use +ignoresigns as an option when invoking LCLint.
You may call LCLint a program with a very 'helping mentality'.
<p> Let us see another example, a goof-up which any C programmer worth his name should have made when he was a
toddler:
<PRE>
main()
{
int a=0;
while (a=1)
printf("hello\n");
return 0;
}
</PRE>
LCLint is justifiably angry at such amateurish use of C, but he is gentle in his admonishments:
<PRE>
LCLint 2.4b --- 18 Apr 98
c.c: (in function main)
c.c:4:14: Test expression for while is assignment expression: a = 1
The condition test is an assignment expression. Probably, you mean to use ==
instead of =. If an assignment is intended, add an extra parentheses nesting
(e.g., if ((a = b)) ...) to suppress this message. (-predassign will suppress
message)
c.c:4:14: Test expression for while not boolean, type int: a = 1
Test expression type is not boolean or int. (-predboolint will suppress
message)
Finished LCLint checking --- 2 code errors found
</PRE>
<H2><b>Memory management pitfalls</b></H2>
<p> LCLint is capable of detecting many memory management gotchas. Here is one:
<PRE>
#include &lt;stdlib.h&gt;
int main()
{
int *p = malloc(5*sizeof(int));
*p = 1;
free(p);
return 0;
}
</PRE>
If you thought LCLint would be fooled, you are mistaken:
<PRE>
LCLint 2.4b --- 18 Apr 98
d.c: (in function main)
d.c:5:7: Dereference of possibly null pointer p: *p
A possibly null pointer is dereferenced. Value is either the result of a
function which may return null (in which case, code should check it is not
null), or a global, parameter or structure field declared with the null
qualifier. (-nullderef will suppress message)
d.c:4:14: Storage p may become null
Finished LCLint checking --- 1 code error found
</PRE>
When the program is rewritten as follows:
<PRE>
#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
int main()
{
int *p = malloc(5*sizeof(int));
if (p == NULL) {
fprintf(stderr, "error in malloc");
exit(EXIT_FAILURE);
} else *p = 1;
free(p);
return 0;
}
</PRE>
LCLint is perfectly happy.
<p> Here is an example of code which tries to free a block twice:
<PRE>
#include &lt;stdlib.h&gt;
main()
{
int *p = malloc(5*sizeof(int));
int *q;
q = p;
free(q); free(p);
return 0;
}
</PRE>
This is how LCLint responds:
<PRE>
LCLint 2.4b --- 18 Apr 98
f.c: (in function main)
f.c:7:19: Dead storage p passed as out parameter: p
Memory is used after it has been released (either by passing as an only param
or assigning to and only global. (-usereleased will suppress message)
f.c:7:10: Storage p is released
Finished LCLint checking --- 1 code error found
</PRE>
<H2><b>Checking macros</b></H2>
<p>One can write perfectly horrible C programs without any assistance from the macro preprocessor and yet
some people are not satisfied. They forget that the C macro preprocessor is a simple program designed to
do simple things and proceed to build grandiose designs with dancing #defines, #ifdef's , #endif's and so
on. The result is utter chaos. The designers of LCLint are very much aware of the C programmer's passion
for macros and they have built into their program the ability to detect many kinds of macro programming
errors.
<p>Here is a typical instance of how a macro defined to work like a function does not work like one.
<PRE>
#define sqr(p) p * p
main()
{
int i=2, j;
j = sqr(i+1);
printf("%d", j); /* prints 5 */
return 0;
}
</PRE>
LCLint is quick to point out the error. Please note that when you run lclint, you must specify that you expect
your macros (with parameters) to behave like functions by using the flag +fcn-macros. Thus, we would invoke the
above program as 'lclint i.c +fcn-macros'. Here is the output from LCLint:
<PRE>
LCLint 2.4b --- 18 Apr 98
i.c:1: Parameterized macro has no prototype or specification: sqr
Function macro has no declaration. (-macrofcndecl will suppress message)
i.c: (in macro sqr)
i.c:1:13: Macro parameter p used more than once
A macro parameter is not used exactly once in all possible invocations of the
macro. To behave like a function, each macro parameter must be used exactly
once on all invocations of the macro so that parameters with side-effects are
evaluated exactly once. Use /*@sef@*/ to denote parameters that must be
side-effect free. (-macroparams will suppress message)
i.c:1:16: Macro parameter used without parentheses: p
A macro parameter is used without parentheses. This could be dangerous if the
macro is invoked with a complex expression and precedence rules will change
the evaluation inside the macro. (-macroparens will suppress message)
i.c:1:20: Macro parameter used without parentheses: p
Finished LCLint checking --- 4 code errors found
</PRE>
The third error message clearly tells you that you need to use parenthesis.
<H2><b>Annotations - the key to LCLint's power</b></H2>
<p>What does a function prototype do? Well, the prototype tells you what all arguments the function accepts - the type
and number of the arguments and the return type of the function. It acts as a sort of interface between the
function and its caller. The caller is required to abide by the interface if he wishes peace for himself, his
program and the world at large. The prototype might also be thought of as placing some sort of constraint on
the legal use of the function.
<p>The provision of constraints on functions comes to your aid when you start building large systems. You
are sure that your function foo_bar() is always called with the right number and type arguments if you
ensure that all your function calls take place in the presence of prototypes. There are several other
constraints which you would like to place on your function, like defining the list of globals which the function
is allowed to modify. The C language does not permit any such constraints, so the only option you have
is to use tools like LCLint.
<p>Here is an example of the use of an annotation.
<PRE>
static void foo(int *a, int *b) /*@modifies *a@*/
{
*a=1, *b=2;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
</PRE>
Note the comment(a stylized comment) /*@modifies *a@/. This is a hint to LCLint that
function foo is constrained to modify the value of *a only. Let us see what output LCLint
produces:
<PRE>
LCLint 2.4b --- 18 Apr 98
j.c: (in function foo)
j.c:3:11: Undocumented modification of *b: *b = 2
An externally-visible object is modified by a function, but not listed in its
modifies clause. (-mods will suppress message)
Finished LCLint checking --- 1 code error found
</PRE>
Here is another example:
<PRE>
static void foo(int *a, int *b) /*@modifies nothing@*/
{
*a=1, *b=2;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
</PRE>
LCLint tells you:
<PRE>
LCLint 2.4b --- 18 Apr 98
k.c: (in function foo)
k.c:3:5: Undocumented modification of *a: *a = 1
An externally-visible object is modified by a function, but not listed in its
modifies clause. (-mods will suppress message)
k.c:3:11: Undocumented modification of *b: *b = 2
k.c: (in function main)
k.c:8:5: Statement has no effect: foo(&p, &q)
Statement has no visible effect --- no values are modified. (-noeffect will
suppress message)
Finished LCLint checking --- 3 code errors found
</PRE>
Here is another one dealing with global variables:
<PRE>
/*@checkedstrict@*/ static int abc, def;
static void foo() /*@globals abc@*/
{
def = 1;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
</PRE>
The annotation /*@checkedstrict@*/ tells LCLint to provide error messages on all undocumented accesses of
global variables, whether it be for reading or writing:
<PRE>
LCLint 2.4b --- 18 Apr 98
l.c: (in function foo)
l.c:5:5: Undocumented use of file static def
A checked global variable is used in the function, but not listed in its
globals clause. By default, only globals specified in .lcl files are checked.
To check all globals, use +allglobals. To check globals selectively use
/*@checked@*/ in the global declaration. (-globs will suppress message)
l.c:2:13: Global abc listed but not used
A global variable listed in the function's globals list is not used in the
body of the function. (-globuse will suppress message)
l.c: (in function main)
l.c:10:5: Called procedure foo may access file static abc
l.c:1:32: File static variable abc declared but not used
A variable is declared but never used. Use /*@unused@*/ in front of
declaration to suppress message. (-varuse will suppress message)
Finished LCLint checking --- 4 code errors found
</PRE>
<p>We have not even scratched the surface of LCLint's capabilities. If you feel that you wish to explore
more, go over to <a href="http://www.sds.lcs.mit.edu/lclint/">http://www.sds.lcs.mit.edu/lclint/</a>.
<H2><b>Concluding remarks</b></H2>
<p>Here is an advice, not from us, but from people who have learned it the hard way - if you wish to use lint
in your project, start from the word go, or risk insanity (Peter van der Linden, in his book 'Expert C programming - Deep C secrets',
talks of a 'lint party' he had at Sun Microsystems. He must have got a kick out of it!).
<p>Lint, especially a very powerful version like LCLint, can be used to learn more about C programming. Just
thinking about the error messages and trying to make them go away will give you a lot of insight.</p>
<!-- *** BEGIN copyright *** -->
<P> <hr> <!-- P -->
<H5 ALIGN=center>
Copyright &copy; 2000, Pramode C E and Gopakumar C E<BR>
Published in Issue 51 of <i>Linux Gazette</i>, March 2000</H5>
<!-- *** END copyright *** -->
<!--startcut ==========================================================-->
<!-- P --> <HR> <!-- P -->
<A HREF="http://www.linuxgazette.com/cgi-bin/talkback/all.py?site=LG&article=http://www.linuxgazette.com/issue51/pramode.html">
<FONT SIZE="+2">Talkbacks</FONT></A>
<P>
<!-- *** BEGIN navbar *** -->
<A HREF="index.html"><IMG ALT="[ Table of Contents ]"
SRC="../gx/indexnew.gif" WIDTH=163 HEIGHT=60 ALIGN=bottom ></A>
<A HREF="../index.html"><IMG ALT="[ Front Page ]"
SRC="../gx/homenew.gif" WIDTH=163 HEIGHT=60 ALIGN=bottom></A>
<A HREF="nielsen.html"><IMG ALT="[ Prev ]" SRC="../gx/back2.gif" WIDTH=41 HEIGHT=60 ALIGN=bottom></A>
<A HREF="../faq/index.html"><IMG ALT="[ Linux Gazette FAQ ]"
SRC="./../gx/dennis/faq.gif"WIDTH=163 HEIGHT=60 ALIGN=bottom></A>
<A HREF="steffler.html"><IMG ALT="[ Next ]" SRC="../gx/fwd.gif" WIDTH=41 HEIGHT=60 ALIGN=bottom ></A>
<!-- *** END navbar *** -->
</BODY></HTML>
<!--endcut ============================================================-->