"Usted correrá lint frecuentemente y estudiará su pronunciamiento con cuidado, dado que verdaderamente su comprensión y buen juicio algunas veces van más allá de los tuyos."
-Henry Spencer, "The Ten Commandments for C Programmers"-Henry Spencer, "Los Diez Mandamientos para los Programadores de C"
Los programadores de C tienen el orgullo de pensar (y a menudo proclamarle al mundo) que ellos saben lo que están haciendo. Esta suprema seguridad en sí mismos (¿o debería decir arrogancia?) no es una cosa mala - pero una pequeña precaución siempre es bien tenida en cuenta ya que C es un lenguaje con muchos esquinas negras (¿por qué la gente escribe libros como " C, Trampas y Peligros"?). Tomando a Lint como compañero suyo en su viaje dentro de los bosques oscuros de C siempre será algo que valdrá la pena - ¡aunque esta compañía es a veces un poco alborotadora y cansadora!
En aquellos buenos días (dicen), una decisión fue hecha para sacar el chequeo de semántica completa del compilador de C y ponerlo en un programa aparte llamado lint (Las razones usuales - hacer al compilador más pequeño, más simple y más rápido - rindiendo culto en el altar del pequeño Dios de la eficiencia). El programador de C, tan seguro de sí mismo, jamás se tomó la molestia de correr lint en su código - ¡con el resultado sumamente gratificante de que él poseía código con errores que compilaba rápido! Lint es una herramienta que le muestra a usted cómo su perspicaz compilador de C puede darle sorpresas a usted - ignórelo a su propio riesgo.
Usted puede darle a LCLint un probadita. LCLint es una potente herramienta la cual está disponible de forma gratuita en forma de código fuente en http://lclint.cs.virginia.edu/ftp/lclint/lclint-2.4b.src.tar.gz LCLint, como veremos más tarde, es mucho más que lint.
LCLint hace los chequeos tradicionales de lint como detectar:
Acá está un pequeño programa en C:
main()
{
int a[10];
if (sizeof(a)/sizeof(a[0]) > -1)
printf("hola\n");
}
Esperábamos que esto imprimiera hola, pero no lo hizo. gcc no nos dio ninguna
pista. Veamos que tiene que decir Lint acerca de esta preciosura. Aquí está la
salida a partir de la ejecución de 'lclint a.c':
LCLint 2.4b --- 18 Apr 98
a.c: (in function main)
a.c:4:15: Operands of > have incompatible types (arbitrary unsigned integral
type, int): sizeof((a)) / sizeof((a[0])) > -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
Oh, oh, sizeof le da a usted el tamaño como un valor sin designar.
Estamos comparando esto con -1, lo cual cuando es interpretado como una
operación de división sin designar le da un gran número.
La salida de LCLint es poco concisa, pero está en una forma que es legible por ordinarios mortales, y no en ANSI (o ISO o cualquier) legalismo. La salida además muestra por pantalla suficiente contexto para ayudarnos a localizar inmediatamente el sitio del problema. Note que también se nos dijo cómo quitar tales errores, esto es, utilice +ignoresigns como una opción cuando se invoca a LCLint. Usted puede llamar a LCLint un programa con una buena "capacidad de ayuda".
Veamos otro ejemplo, una metida de pata que cualquier programador de C meritorio de su nombre alguna vez ha cometido cuando él era un niño que recién comienza a andar:
main()
{
int a=0;
while (a=1)
printf("hola\n");
return 0;
}
LCLint está justificadamente enojado ante tan amateuridad en el uso de C, pero
él es gentil en sus advertencias:
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
LCLint es capaz de detectar un gran número de errores en el manejo de la memoria. Aquí hay uno:
#include <stdlib.h>
int main()
{
int *p = malloc(5*sizeof(int));
*p = 1;
free(p);
return 0;
}
Si usted pensó que LCLint sería engañado, estaba en un error:
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 foundCuando el programa es rescrito de la siguiente forma:
#include <stdlib.h>
#include <stdio.h>
int main()
{
int *p = malloc(5*sizeof(int));
if (p == NULL) {
fprintf(stderr, "error en malloc");
exit(EXIT_FAILURE);
} else *p = 1;
free(p);
return 0;
}
LCLint está perfectamente feliz.
Acá hay un ejemplo de código el cual trata de liberar un bloque de memoria dos veces:
#include <stdlib.h>
main()
{
int *p = malloc(5*sizeof(int));
int *q;
q = p;
free(q); free(p);
return 0;
}
Esto es lo que responde LCLint:
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
Uno puede escribir programas en C perfectamente horribles sin ninguna asistencia del preprocesador de macros y sin embargo algunas personas no quedan satisfechas. Ellos olvidan que el preprocesador de macros de C es un simple programa diseñado para hacer cosas simples y proceder a construir grandiosos diseños con danzantes #defines, #ifdef , #endif y así sucesivamente. El resultado se pronuncia en un caos. Los diseñadores de LCLint están mucho más consientes de la pasión de los programadores de C por las macros y ellos han incorporado dentro de su programa la habilidad de detectar muchas clases de errores de programación en las macros.
Aquí hay una instancia típica de cómo una macro definida para trabajar como una función no trabaja como una.
#define sqr(p) p * p
main()
{
int i=2, j;
j = sqr(i+1);
printf("%d", j); /* prints 5 */
return 0;
}
LCLint es rápido en apuntar el error. Por favor note que cuando usted ejecuta
lclint, debe especificar que usted espera que sus macros (con parámetros) se
comporten como funciones utilizando la bandera +fcn-macros. De este modo,
invocaríamos al programa anterior de la siguiente forma 'lclint i.c +fcn-macros'.
Acá está la salida de LCLint:
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 foundEl tercer mensaje de error claramente le dice a usted que necesita utilizar paréntesis.
¿Qué hace un prototipo de función? Bueno, el prototipo le dice a usted cuales son todos los argumentos que acepta la función - el tipo y número de argumentos y el tipo que retorna la función. Actúa como una clase de interfaz entre la función y su llamador (o sea quien llama a la función). El llamador es requerido para cumplir con la interfase, si él desea paz para él mismo, su programa y el mundo generalmente- El prototipo además puede pensarse como la colocación de algún tipo de restricción en el uso legal de la función.
El suministro de restricciones en las funciones viene en su ayuda cuando usted comienza a construir grandes sistemas. Usted estará seguro que su función foo_bar() es siempre llamada con el número correcto y el tipo de argumentos si usted se asegura que todas las llamadas a sus funciones se llevan a cabo en la presencia de prototipos. Hay otras varias restricciones que a usted le gustaría colocar en su función, como definir la lista de variables globales que la función puede modificar. El lenguaje C no permite ninguna de esas restricciones, por lo tanto la única opción que usted posee es utilizar herramientas como LCLint.
Acá hay un ejemplo del uso de una anotación.
static void foo(int *a, int *b) /*@modifies *a@*/
{
*a=1, *b=2;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
Observe el comentario (un comentario estilizado) /*@modifies *a@/. Esta es una
señal para LCLint de que la función foo está obligada a modificar a *a
solamente. Veamos que salida produce LCLint:
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 foundAquí hay otro ejemplo:
static void foo(int *a, int *b) /*@modifies nothing@*/
{
*a=1, *b=2;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
LCLint le dice a usted:
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 foundAcá hay otro que transa con variables globales:
/*@checkedstrict@*/ static int abc, def;
static void foo() /*@globals abc@*/
{
def = 1;
}
main()
{
int p=10, q=20;
foo(&p, &q);
return 0;
}
La anotación /*@checkedstrict@*/ le dice a LCLint que provea mensajes de error
en todos los accesos a variables globales que estén sin documentar, tanto
sea para lectura como para escritura:
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
No hemos ni arañado la superficie de la capacidad de LCLint. Si usted siente que desea explorar más, inspeccione http://www.sds.lcs.mit.edu/lclint/.
Acá hay un consejo, no de parte de nosotros, sino de la gente que lo ha aprendido de la forma difícil - si usted desea utilizar lint en su proyecto, comience desde la palabra go (ir), o se expone a la locura (Peter van der Linden, en su libro 'Expert C programming - Deep C secrets (Programación experta en C - Secretos profundos de C)', habla de una 'fiesta lint' que él tuvo en Sun Microsystems. ¡Él debe haber tenido que sacar provecho de eso!).
Lint, especialmente una muy poderosa versión como LCLint, puede ser utilizada para aprender más sobre la programación en C. Sólo piense en los mensajes de error y trate de hacer que se vayan, le darán a usted un montón de entendimiento.