对以下三大类情况执行特定于 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)) ... |