Oracle® Developer Studio 12.5:C 用户指南

退出打印视图

更新时间: 2016 年 7 月
 
 

5.6 lint 参考和示例

本节提供有关 lint 的参考信息,包括由 lint 执行的检查、lint 库以及 lint 过滤器。

5.6.1 由 lint 执行的诊断

会针对以下三种广泛的条件类别发出特定于 lint 的诊断:不一致的使用、不可移植的代码和可疑的构造。本节将研究在每种条件下 lint 的行为示例,并针对它们引起的问题提供可能的解决方法建议。

5.6.1.1 一致性检查

在文件内部以及各文件之间检查使用变量、参数和函数的不一致性。一般说来,对原型的使用、声明和参数执行的检查与 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 可禁止后两种情况。

5.6.1.2 可移植性检查

在其缺省行为下,lint 会对某些不可移植代码进行标记,在使用 -p-pedantic 调用 lint 时,还会诊断一些其他些情况。后者导致 lint 检查不符合 ISO C 标准的构造。对于在 -p-pedantic 下发出的消息,请参见lint 库

示例:

  • 在某些 C 语言实现中,未显式声明为 signedunsigned 的字符变量会被视为范围通常在 -128 到 127 之间的带符号值。在其他实现中,它们会被视为范围通常在 0 到 255 之间的非负值。以下测试(其中 EOF 的值为 -1)在字符变量接受非负值的机器上将始终失败。

    char c;
    c = getchar();
    if (c == EOF) ...

    调用带 -plint 将检查隐含 plain char 可能具有负值的所有比较。然而,在示例中将 c 声明为signed char 可避免执行诊断,却无法避免出现该问题。getchar() 必须返回所有可能的字符和一个不同的 EOF 值,因此 char 无法存储其值。此示例可能是出自于实现定义的符号扩展的最常见示例,说明如何通过巧妙地应用 lint 的可移植性选项来帮助您发现与可移植性无关的错误。在任何情况下,将 c 声明为 int

  • 位字段也会出现类似的问题。 将常量值赋给位字段时,该字段可能太小,无法容纳该值。在将 int 类型的位字段视为无符号值的计算机上,int x:3 所允许的值在 0 到 7 的范围内;而在将其视为带符号值的计算机上,相应值的范围在 -4 到 3 之间。但是,声明为类型 int 的三位字段无法在后一种计算机上存储值 4。使用 -p 调用的 lint 会标记除 unsigned intsigned int 之外的所有位字段类型。它们仅是可移植的位字段类型。编译器支持 intcharshortlong 位字段类型,它们可以为 unsignedsigned无格式。编译器还支持 enum 位字段类型。

  • 在将较长的类型赋值给较短的类型时,会出现问题。如果有效位被截断,则失去准确性:

    short s;
    long l;
    s = l;

    lint 在缺省情况下会标记所有此类赋值;可通过调用 –-a 选项来禁止该诊断。请记住,使用此选项或任何其他选项调用 lint 时,可能会禁止其他诊断。请查看lint 库中的列表,以了解禁止多项诊断的选项。

  • 将指向某对象类型的指针强制转换为指向具有更严格对齐要求的对象类型的指针,可能将无法移植。lint 会标记以下示例,原因是在大多数计算机上,int 无法在任意字节边界启动,而 char 可以。

    int *fun(y)
    char *y;
    {
        return(int *)y;
    }

    可通过使用 -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’)

5.6.1.3 可疑的构造

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 会禁用该诊断。

5.6.2 lint

可以使用 lint 库检查程序是否与已在其中进行调用的库函数兼容,其中包括函数返回类型的声明以及函数所预期参数的数量和类型等。标准 lint 库与 C 编译系统提供的库对应,并且通常存储在系统上的标准位置。按照约定,lint 库的名称采用 llib-lx.ln 形式。

缺省情况下,lint 标准 C 库 llib-lc.ln 将附加到 lint 命令行末尾。通过调用 -n 选项可以禁止检查与该库的兼容性。其他 lint 库作为 -l 的参数进行访问。以下示例指示 lint 检查 file1.cfile2.c 中函数和变量的用法是否与 lintllib-lx.ln 兼容。

% lint -lx file1.c file2.c

仅由定义组成的库文件完全作为普通源文件和普通 .ln 文件处理,区别在于不会针对以下情况发出错误消息:函数和变量在库文件中的用法不一致,或者函数和变量在库文件中已定义但未在源文件中使用。

要创建您自己的 lint 库,请在 C 源文件的开头插入指令 NOTE(LINTLIBRARY),然后使用 -o 选项以及指定给 -l 的库名称针对该文件调用 lint:以下示例仅导致源文件中以 NOTE(LINTLIBRARY) 开头的定义写入文件 llib-lx.ln 中。

% lint -ox file1.c file2.c

请注意 lint -occ -o 的类似性。可以相同的方式从函数原型声明的文件创建库,不同的是必须在声明文件的最前面插入 NOTE(LINTLIBRARY)NOTE(PROTOLIB(n)) 如果 n 为 1,则原型声明写入库 .ln 文件,这与旧样式的定义相同。如果 n 为 0(缺省 值),则取消该进程。使用 -y 调用 lint 是创建 lint 库的另一种方法。以下命令行导致该行中命名的每个源文件被视为以 NOTE(LINTLIBRARY) 开头,并且只有其定义被写入 llib-lx.ln

% lint -y -ox file1.c file2.c

缺省情况下,lint 在标准位置搜索 lint 库。要指示 lint 在非标准位置的目录中搜索 lint 库,请使用 -L 选项指定目录路径:

% lint -Ldir -lx file1.c file2.c

在增强模式下,lint 生成 .ln 文件,这些文件存储的信息比在基本模式下生成的 .ln 文件存储的信息多。在增强模式下,lint 可以读取和理解由基本或增强 lint 模式生成的所有 .ln 文件。在基本模式下,lint 只能读取和理解由基本 lint 模式生成的 .ln 文件。

缺省情况下,lint 使用 /lib/usr/lib 目录中的库。这些库采用基本 lint 格式。可以运行一次 makefile,并以新格式创建增强 lint 库,从而使增强 lint 更有效地工作。要运行 makefile 并创建新库,请使用以下命令:

% cd install-directory/prod/src/lintlib; make

其中 install-directory 为安装目录。运行 makefile 之后,lint 在增强模式下使用新库,而不是使用 /lib/usr/lib 目录中的库。

将先搜索标准位置,然后再搜索指定的目录。

5.6.3 lint 过滤器

lint 过滤器是特定于项目的后处理程序,通常使用 awk 脚本或类似程序读取 lint 的输出,并放弃项目认为没有标识真正问题的消息,例如,对于字符串函数,返回值有时或总是被忽略。当 lint 选项和指令未提供对输出的足够控制时,lint 过滤器会生成定制的诊断报告。

lint 的两个选项在开发过滤器的过程中特别有用:

  • -s 选项会导致将复合诊断转换为简单诊断,并对诊断的每个具体问题发出一行消息。这种易于进行解析的消息格式适合由 awk 脚本进行分析。

  • -k 选项将导致您在源文件中写入的某些注释打印到输出中。在记录项目决策和指定后处理程序行为时,这会很有用。在后一个实例中,如果注释标识了预期的 lint 消息,并且报告的消息相同,则会过滤掉该消息。要使用 -k,请在要注释的代码前面的行中插入 NOTE(LINTED(msg)) 指令,其中 msg 表示在使用 -k 调用 lint 时将输出的注释。

    请参阅表 16 中的指令列表,以了解针对包含 NOTE(LINTED(msg)) 的文件调用 -klint 所执行操作的说明。