本节提供有关 lint 的参考信息,包括由 lint 执行的检查、lint 库以及 lint 过滤器。
对以下三大类情况执行特定于 lint 的诊断:不一致的使用、不可移植的代码以及可疑的构造。在本节中,我们将研究在每种条件下 lint 的行为示例,并针对它们引起的问题提供可能的解决方法建议。
在文件内部以及各文件之间检查使用变量、参数和函数的不一致性。一般说来,对原型的使用、声明和参数执行的检查与 lint 对旧样式函数执行的检查相同。如果程序未使用函数原型,lint 将比编译器更严格地检查每个函数调用中参数的数量和类型。lint 还标识 [fs]printf() 和 [fs]scanf() 控制字符串中转换定义和参数之间的不匹配。
示例:
在文件内部,lint 会标记执行到底部但未向调用函数返回值的非 void 函数。过去,程序员通常通过省略返回类型指明某个函数不应返回值: fun() {}。该约定对于编译器没有任何意义,它会将 fun() 视为具有返回类型 int。可使用返回类型 void 来声明函数以消除该问题。
在文件之间,lint 检测非 void 函数不返回值但由于它在某个表达式中有值而仍被使用的情况以及相反的情况(即,函数返回值,但在随后调用中有时或总是被忽略)。当值总是被忽略时,可能表示函数定义无效率。当值有时被忽略时,可能是样式错误(通常不测试是否存在错误条件)。如果无需检查 strcat()、strcpy() 和 sprintf() 等字符串函数或 printf() 和 putchar() 等输出函数的返回值,可将违例调用强制转换为 void 类型。
lint 标识已声明但未使用或定义、已使用但未定义或已定义但未使用的变量或函数。将 lint 应用于某个集合中要一起装入的某些文件而非全部文件时,它会产生关于出现以下情况的函数和变量的错误消息:
在那些文件中声明,但在其他地方定义或使用
在那些文件中使用,但在其他地方定义
在那些文件中定义,但在其他地方使用
可调用 -x 选项以禁止第一种错误消息,调用 -u 以禁止后两种错误消息。
一些不可移植的代码在其缺省行为中带有 lint 标志,当用 -p 或 -Xc 调用 lint 时,会诊断额外的几种情况。后者导致 lint 检查不符合 ISO C 标准的构造。有关使用 -p 和 -Xc 时发出的消息,请参见4.6.2 lint 库。
示例:
在某些 C 语言实现中,未显式声明为 signed 或 unsigned 的字符变量会被视为范围通常在 -128 到 127 之间的带符号值。在其他实现中,它们会被视为范围通常在 0 到 255 之间的非负值。因此测试:
char c; c = getchar(); if (c == EOF) ... |
其中 EOF 的值为 -1,在字符变量取非负值的计算机上总是失败。使用 -p 调用的 lint 会检查暗示无格式 char 可取负值的所有比较。然而,在以上示例中,将 c 声明为 signed char 可避免执行诊断,却无法避免出现该问题。这是因为 getchar() 必须返回所有可能的字符和一个不同的 EOF 值,因此 char 无法存储其值。此示例可能是出自于实现定义的符号扩展的最常见示例,我们之所以引用该示例,是为了说明如何通过巧妙地应用 lint 的可移植性选项来帮助您发现与可移植性无关的错误。在任何情况下,将 c 声明为 int。
位字段也会出现类似的问题。将常量值赋给位字段时,该字段可能太小,无法容纳该值。在将 int 类型的位字段视为无符号值的计算机上,int x:3 所允许的值在 0 到 7 的范围内;而在将其视为带符号值的计算机上,相应值的范围在 -4 到 3 之间。但是,声明为类型 int 的三位字段无法在后一种计算机上存储值 4。使用 -p 调用的 lint 会标记除 unsigned int 和 signed int 之外的所有位字段类型。它们仅是可移植的位字段类型。编译器支持 int、char、short 和 long 位字段类型,它们可以为 unsigned、 signed 或无格式。编译器还支持 enum 位字段类型。
在将较长的类型赋值给较短的类型时,会出现错误。如果有效位被截断,则失去准确性:
short s; long l; s = l; |
lint 在缺省情况下会标记所有此类赋值;可通过调用 -a 选项来禁止该诊断。请记住,使用此选项或任何其他选项调用 lint 时,可能会禁止其他诊断。请查看4.6.2 lint 库中的列表,以了解禁止多项诊断的选项。
一个对象类型指针向具有更严格对齐要求的对象类型指针的强制类型转换可能不可移植。lint 标记以下代码:
int *fun(y) char *y; { return(int *)y; } |
这是因为在大多数计算机上,int 不能在一个任意字节边界上开始,而 char 则可以。可通过使用 -h 调用 lint 来禁止诊断,尽管这可能会再次禁用其他消息。但是最好通过使用通用指针 void * 来消除该问题。
ISO C 未定义复杂表达式的求值顺序。也就是说,如果由于某个表达式的求值而更改某个变量时,函数调用、嵌套赋值语句或加减运算符会引起副作用,那么副作用发生的顺序在极大程度上取决于计算机。在缺省情况下,lint 会标记由于副作用而更改并且在同一表达式的其他地方使用的任何变量:
int a[10]; main() { int i = 1; a[i++] = i; } |
在此示例中,a[1] 的值在使用一个编译器时可能为 1,在使用另一个编译器时可能为 2。当按位逻辑运算符 & 被错误地代替逻辑运算符 && 使用时,会导致此诊断:
if ((c = getchar()) != EOF & c != ’0’) |
lint 会标记那些不能表达程序员意图的各种合法构造。示例:
unsigned 变量总是具有非负值。因此测试:
unsigned x; if (x < 0) ... |
总是失败。测试:
unsigned x; if (x > 0) ... |
等效于:
if (x != 0) ... |
这可能不是预期的操作。lint 会标记 unsigned 变量与负常量或 0 的可疑比较。要将 unsigned 变量与负数的位模式进行比较,请将该变量的类型强制转换为 unsigned:
if (u == (unsigned) -1) ... |
或者使用 U 后缀:
if (u == -1U) ... |
lint 会标记用于预期出现副作用的上下文但没有副作用的表达式,即可能未表达程序员意图的表达式。它在应该出现赋值运算符的位置发现等号运算符(即预期出现副作用)时发出一个额外的警告:
int fun() { int a, b, x, y; (a = x) && (b == y); } |
lint 会提醒您注意给混合有逻辑运算符和按位运算符(特别是 &、|、^、<< 和 >>)的表达式加上括号,否则运算符优先级的误解会引起不正确的结果。例如,按位运算符 & 的优先级低于逻辑运算符 ==,所以表达式:
if (x & a == 0) ... |
求值如下:
if (x & (a == 0)) ... |
这很有可能不是您的意图。使用 -h 调用 lint 会禁用 诊断。
可以使用 lint 库检查程序是否与已在其中进行调用的库函数兼容,其中包括函数返回类型的声明以及函数所预期参数的数量和类型等。标准 lint 库与 C 编译系统提供的库对应,并且通常存储在系统上的标准位置。按照约定,lint 库的名称采用 llib-lx.ln 形式。
lint 标准 C 库 lliblc.ln 在缺省情况下附加至 lint 命令行;可通过调用 -n 选项来禁止其兼容性检查。其他 lint 库作为 -l 的参数进行访问。即:
% lint -lx file1.c file2.c |
指示 lint 检查 file1.c 和 file2.c 中函数和变量的用法是否与 lint 库 llib-lx.ln 兼容。仅由定义组成的库文件完全作为普通源文件和普通 .ln 文件处理,区别在于不会针对以下情况发出错误消息:函数和变量在库文件中的用法不一致,或者函数和变量在库文件中已定义但未在源文件中使用。
要创建您自己的 lint 库,请在 C 源文件的开头插入指令 NOTE(LINTLIBRARY),然后使用 -o 选项以及指定给 -l 的库名称针对该文件调用 lint:
% lint -ox file1.c file2.c |
导致仅将源文件中以 NOTE(LINTLIBRARY) 开头的定义写入文件 llib-lx.ln。(请注意,lint -o 与 cc -o 类似。)可以相同的方式从函数原型声明的文件创建库,不同的是必须在声明文件的最前面插入 NOTE(LINTLIBRARY) 和 NOTE(PROTOLIB(n))。如果 n 为 1,则原型声明写入库 .ln 文件,这与旧样式的定义相同。如果 n 为 0(缺省 值),则取消该进程。使用 -y 调用 lint 是创建 lint 库的另一种方法。命令行:
% lint -y -ox file1.c file2.c |
导致该行中命名的每个源文件被视为以 NOTE(LINTLIBRARY) 开头,并且只有其定义被写入 llib-lx.ln。
缺省情况下,lint 在标准位置搜索 lint 库。要指示 lint 在非标准位置的目录中搜索 lint 库,请使用 -L 选项指定目录路径:
% lint -Ldir -lx file1.c file2.c |
在增强模式下,lint 生成 .ln 文件,这些文件存储的信息比在基本模式下生成的 .ln 文件存储的信息多。在增强模式下,lint 可以读取和理解由基本或增强 lint 模式生成的所有 .ln 文件。在基本模式下,lint 只能读取和理解由基本 lint 模式生成的 .ln 文件。
缺省情况下,lint 使用 /usr/lib 目录中的库。这些库采用基本 lint 格式。可以运行一次 makefile,并以新格式创建增强 lint 库,从而使增强 lint 更有效地工作。要运行 makefile 并创建新库,请输入以下命令:
% cd <install-directory>/prod/src/lintlib; make |
其中 <install-directory> 为安装目录。运行 makefile 之后,lint 在增强模式下使用新库,而不是使用 /usr/lib 目录中的库。
lint 过滤器是特定于项目的后处理程序,通常使用 awk 脚本或类似程序读取 lint 的输出,并放弃项目认为没有标识真正问题的消息(例如,对于字符串函数,返回值有时或总是被忽略)。当 lint 选项和指令未提供对输出的足够控制时,lint 过滤器会生成定制的诊断报告。
lint 的两个选项在开发过滤器的过程中特别有用:
使用 -s 调用 lint 会导致将复合诊断转换为简单诊断,并对诊断的每个具体问题发出一行消息。这种易于进行语法分析的消息格式适合由 awk 脚本进行分析。
使用 -k 调用 lint 会导致在输出中输出您在源文件中编写的某些注释,并且有助于记录项目决策和指定后处理程序的行为。在后一个实例中,如果注释标识了预期的 lint 消息,并且报告的消息相同,则会过滤掉该消息。要使用 -k,请在要注释的代码前面的行中插入 NOTE(LINTED(msg)) 指令,其中 msg 表示在使用 -k 调用 lint 时将输出的注释。
请参阅表 4–9 中的指令列表,以了解未调用 -k 时 lint 所执行操作的说明(对于包含 NOTE(LINTED(msg)) 的文件。