本章介绍如何使用 lint 程序检查 C 代码中是否存在可能导致编译失败或在运行时出现意外结果的错误。在很多情况下,lint 会警告您存在编译器未对其作必要标志的不正确、有错误倾向或非标准的代码。
lint 程序会发出 C 编译器生成的每条错误消息和警告消息。它还发出关于潜在错误和可移植性问题的警告。由 lint 发出的许多消息有助于您提高程序的效率,其中包括减小其大小和减少必需的内存。
lint 程序使用与编译器相同的语言环境,并且 lint 的输出会定向到 stderr。有关在执行基于类型的别名歧义消除之前如何使用 lint 检查代码的更多信息和示例,请参见4.6.3 lint 过滤器。
基本(缺省模式)
增强(包括由基本 lint 执行的一切内容以及附加的详细代码分析)
在基本模式和增强模式下,lint 通过标记文件(包括已使用的任何库)间定义和用法中的不一致,来补偿 C 中的单独编译和独立编译。特别是在大型项目环境中,同一函数可能被不同程序员用在数百个单独的代码模块中,在这种情况下,lint 有助于发现借助其他方式很难发现的错误。例如,如果调用函数时使用的参数比所需的参数少一个,该函数在栈中查找该调用从未推的值,结果在一个条件下正确,在另一个条件下不正确,具体取决于内存中该栈位置发生的情况。通过标识类似的依赖性以及对计算机体系结构的依赖性,lint 可提高运行于您的计算机或其他计算机上的代码的可靠性。
在增强模式下,lint 提供比在基本模式下更详细的报告。在基本模式下,lint 的功能包括:
源程序的结构和流分析
常量传播和常量表达式求值
控制流和数据流的分析
数据类型使用的分析
在增强模式下,lint 可以检测以下问题:
未使用的 #include 指令、变量和过程
内存释放之后内存的使用
未使用的赋值
初始化之前使用变量值
未分配内存的释放
写入常量数据段时使用指针
非等价宏重定义
未执行到的代码
符合联合中值类型的用法
实际参数的隐式强制类型转换。
可从命令行调用 lint 程序及其选项。要在基本模式下调用 lint,请使用以下命令:
% lint file1.c file2.c |
可使用 -Nlevel 或 -Ncheck 选项调用增强 lint。例如,可以按如下所示调用增强 lint:
% lint -Nlevel=3 file1.c file2.c |
lint 会检查两遍代码。第一遍,lint 检查 C 源文件中的错误条件 ;第二遍,检查 C 源文件中的不一致性。除非使用 -c 调用 lint,否则对于用户该过程不可见:
% lint -c file1.c file2.c |
该命令指示 lint 仅执行第一遍检查,并在名为 file1.ln 和 file2.ln 的中间文件中收集 file1.c 和 file2.c 之间定义和用法的不一致信息(这些信息和第二遍检查相关):
% ls file1.c file1.ln file2.c file2.ln |
可见,lint 的选项 -c 类似于 cc 的选项 -c,可禁止编译的链接编辑阶段。一般说来,lint 的命令行语法严格遵循 cc 的语法。
针对 .ln 文件执行 lint 时:
% lint file1.ln file2.ln |
将执行第二遍检查。lint 按文件在命令行中出现的顺序处理任意多个 .c 或 .ln 文件。因此,
% lint file1.ln file2.ln file3.c |
指示 lint 检查 file3 .c 中的内部错误以及所有三个文件中的不一致性。
lint 按与 cc 相同的顺序在目录中搜索包含的头文件。可以像使用 cc 的选项 -I 那样使用 lint 的选项 -I。请参见2.16 如何指定 include 文件。
可以在同一命令行中指定 lint 的多个选项。除非其中一个选项带有参数或者选项有多个字母,否则选项可以串联:
% lint -cp -Idir1 -Idir2 file1.c file2.c |
该命令指示 lint 执行以下操作:
lint 有许多选项,可用来指示 lint 执行某些任务并报告某些情形。
lint 程序是一个静态分析器。它不能求出它检测到的依赖性的运行时结果。例如,某些程序可能包含数百个执行不到的 break 语句,这些语句并不重要,但是 lint 仍然会标记它们。下面是一个示例,其中包含 lint 命令行选项和指令(嵌入源代码文本的特殊注释):
可以使用 -b 选项调用 lint 以禁止关于执行不到的 break 语句的所有错误消息。
可以在任一执行不到的语句前面加上注释 /*NOTREACHED*/ 以禁止诊断该语句。
下面按字母顺序列出了 lint 选项,其中有几个 lint 选项与禁止 lint 诊断消息有关。在这些按字母顺序说明的选项之后有一个表 4–8,其中也列出了这些选项,并且列出了这些选项禁止的特定消息。用于调用增强 lint 的选项以 -N 开头。
lint 能够识别许多 cc 命令行选项,其中包括 -A、-D、-E、-g、-H、-O、-P、-U、-Xa、-Xc、-Xs、-Xt 和 -Y,尽管 -g 和 -O 会被忽略。未识别的选项被警告并忽略。
抑制某些消息。请参阅表 4–8。
抑制某些消息。请参阅表 4–8。
使用指定的文件名创建一个 .ln 文件。这些 .ln 文件仅是 lint 的第一遍检查产生的文件。filename 可以是完整路径名。
为命令行上的每个名为 .c 的文件创建一个 .ln 文件,该文件包含与 lint 的第二遍检查相关的信息。不执行第二遍检查。
指定目录 dir,其中存放 lint 输出文件(.ln 文件)。此选项会影响 -c 选项。
-err=warn 是用于 -errwarn=%all 的宏。请参见4.3.15 -errwarn=t。
执行由 l 指定的附加检查。缺省值为 -errchk=%none。指定 -errchk 与指定 -errchk=%all 等效。l 是一个以逗号分隔的检查列表,由下表中的一个或多个值组成。例如,-errchk=longptr64,structarg。
表 4–1 -errchk 标志
值 |
含义 |
---|---|
%all |
执行 -errchk 的所有检查。 |
%none |
不执行 -errchk 的任何检查。这是缺省值。 |
[no%]locfmtchk |
在 lint 的第一遍检查期间检查类似 printf 的格式字符串。无论是否使用 -errchk=locfmtchk,lint 都会在第二遍检查期间检查类似 printf 的格式字符串。 |
[no%]longptr64 |
检查是否可移植到其长整型和指针大小为 64 位、平整型大小为 32 位的环境。即使使用了显式强制类型转换,也检查指针表达式和长整型表达式是否赋值为平整型。 |
[no%]structarg |
检查通过值传递的结构参数,并在形式参数类型未知时报告情况。 |
[no%]parentheses |
检查代码中优先级的透明度。使用此选项可增强代码的可维护性。如果 -errchk=parentheses 返回一个警告,请考虑使用额外的括号明确地表示代码中操作的优先级。 |
[no%]signext |
检查如下情况:标准 ISO C 值保留规则允许在无符号整型的表达式中进行带符号整型值的符号扩展。仅当同时指定 -errchk=longptr64 时,该选项才会产生错误消息。 |
[no%]sizematch |
检查较长整数到较短整数的赋值并发出警告。对于具有不同符号的相同长度的整数之间的赋值(无符号整型数获取带符号整型数),也会发出这些警告。 |
指定 lint 输出的格式。f 可以是下列值之一: macro、simple、src 或 tab。
表 4–2 -errfmt 标志
值 |
含义 |
---|---|
macro |
显示错误的源代码、行号和位置,并展开宏。 |
simple |
对于一行(简单)诊断消息,在括号中显示错误的行号和位置号。类似于 -s 选项,但是包含错误的位置信息。 |
src |
显示错误的源代码、行号和位置(不展开宏)。 |
tab |
以制表格式显示。这是缺省值。 |
缺省值为 -errfmt=tab。指定 -errfmt 与指定 -errfmt=tab 等效。
如果指定了多种格式,则使用最后指定的格式,并且 lint 发出有关未使用格式的警告。
在同时指定了 -Ncheck 的情况下,允许 lint 报告某些头文件消息。h 是一个以逗号分隔的列表,它包含以下项中的一项或多项:dir、no%dir、%all、%none、%user。
表 4–3 -errhdr 标志
值 |
含义 |
---|---|
dir |
报告目录 dir 中包含的头文件的 -Ncheck 消息。 |
no%dir |
不报告目录 dir 中包含的头文件的 -Ncheck 消息。 |
%all |
检查使用的所有头文件。 |
%none |
不检查头文件。这是缺省值。 |
%user |
检查所有已使用的用户头文件,即,除 /usr/include 及其子目录中的头文件以及由编译器提供的头文件之外的所有头文件。 |
缺省值为 -errhdr=%none。指定 -errhdr 与指定 -errhdr=%user 等效。
示例:
% lint -errhdr=inc1 -errhdr=../inc2 |
检查目录 inc1 和 ../inc2 中已使用的头文件。
% lint -errhdr=%all,no%../inc |
检查除目录 ../inc 中的头文件之外的所有已使用的头文件。
t 是一个以逗号分隔的列表,它包含以下项中的一项或多项:tag、no% tag、%all、%none。
表 4–4 -erroff 标志
值 |
含义 |
---|---|
tag |
禁止由该 tag 指定的消息。可通过 -errtags=yes 选项来显示消息的标记。 |
no%tag |
启用由该 tag 指定的消息。 |
%all |
禁止所有消息。 |
%none |
启用所有消息。这是缺省值。 |
缺省值为 -erroff=%none。指定 -erroff 与指定 -erroff=%all 等效。
示例:
% lint -erroff=%all,no%E_ENUM_NEVER_DEF,no%E_STATIC_UNUSED |
仅输出消息 "enum never defined" 和 "static unused",并禁止其他消息。
% lint -erroff=E_ENUM_NEVER_DEF,E_STATIC_UNUSED |
仅禁止消息 "enum never defined" 和 "static unused"。
可使用 -errsecurity 选项检查代码中的安全漏洞。
v 必须是下列值之一:
表 4–5 -errsecurity 标志
如果未指定 -errsecurity 的设置,lint 会将它设置为 -errsecurity=%none。如果在指定 -errsecurity 时未指定参数,lint 会将它设置为 -errsecurity=standard。
显示每个错误消息的消息标志。a 可以是 yes 或 no。缺省值为 -errtags=no。指定 -errtags 与指定 -errtags=yes 等效。
可与所有 -errfmt 选项一起使用。
如果发出了指定的警告消息,lint 会以失败状态退出。t 是一个以逗号分隔的列表,它包含以下项中的一项或多项:tag、no% tag、%all、%none。这些值的顺序很重要;例如,如果发出了除 tag 以外的任何警告,%all,no%tag 会导致 lint 以致命状态退出。下表列出了 -errwarn 值:
表 4–6 -errwarn 标志
tag |
如果此 tag 指定的消息作为警告消息发出,将导致 lint 以致命状态退出。如果未出现 tag,则没有影响。 |
no%tag |
如果 tag 指定的消息仅作为警告消息发出,将防止 lint 以致命状态退出。如果未发出 tag,则没有影响。使用此选项,可防止发出先前使用该选项及 tag 或 %all 指定的警告消息时导致 lint 以致命状态退出。 |
%all |
如果发出了任何警告消息,将导致 lint 以致命状态退出。%all 可以后跟 no%tag,以避免该行为的特定警告消息。 |
%none |
如果发出了任何警告消息,将防止任何警告消息导致 lint 以致命状态退出。 |
缺省值为 -errwarn=%none。如果单独指定 -errwarn,它与 -errwarn=%all 等效。
引用命令行上命名的 .c 文件时,会输出命令行上提供的路径名而不仅仅是它们的基名。
使用文件 file 中包含的选项执行 lint。可在 file 中指定多个选项(每行一个)。
抑制某些消息。请参阅表 4–8。
改变 /* LINTED [message] */ 指令或 NOTE(LINTED(message)) 注释的行为。通常,lint 会针对这些指令后面的代码禁止警告消息。如果不设置该选项,lint 不禁止消息,而是输出包含指令或注释内部的注释的附加消息。
与 -l 一起使用时,在目录 dir 中搜索 lint 库。
抑制某些消息。请参阅表 4–8。
指定所分析程序的内存模型。还搜索与所选的内存模型(32 位/64 位)相对应的 lint 库。
使用 -m32 可验证 32 位 C 程序;使用 -m64 可验证 64 位程序。
在所有 Solaris 平台和不支持 64 位的 Linux 平台上,ILP32 内存模型(32 位 int、long、pointer 数据类型)是缺省值。在启用了 64 位的 Linux 平台上缺省为 LP64 内存模型(64 位 long 和指针数据类型)。-m64 仅允许在支持 LP64 模型的平台上使用。
请注意,在以前的编译器发行版中,内存模型 ILP32 或 LP64 是通过选择 -Xarch 选项来隐式指定的。从 Solaris Studio 12 编译器开始,就不再是这样了。在大多数平台上,只需在命令行上添加 -m64,就可以通过 lint 调用 64 位程序。
有关预定义的宏的更多信息,请参见以下 lint 选项列表后面的几节内容。
检查头文件中的相应声明;检查宏。c 是一个以逗号分隔的检查列表,它包含以下项中的一项或多项: macro、extern、%all、%none、no%macro、no%extern。
表 4–7 -Ncheck 标志
值 |
含义 |
---|---|
macro |
检查文件之间的宏定义的一致性。 |
extern |
检查源文件与关联的头文件(例如,file1.c 与 file1.h)之间声明的一一对应关系。确保头文件中既无多余的 extern 声明,也不缺少 extern 声明。 |
%all |
执行 -Ncheck 的所有检查。 |
%none |
不执行 -Ncheck 的任何检查。这是缺省值。 |
no%macro |
不执行 -Ncheck 的任何 macro 检查。 |
no%extern |
不执行 -Ncheck 的任何 extern 检查。 |
缺省值为 -Ncheck=%none。指定 -Ncheck 与指定 -Ncheck=%all 等效。
多个值可以用逗号分隔,例如 -Ncheck=extern,macro。
示例:
% lint -Ncheck=%all,no%macro |
执行除宏检查之外的所有检查。
通过指定用于报告问题的增强 lint 分析级别,打开增强 lint 模式。此选项允许您控制检测到的错误量。级别越高,检验时间越长。n 是一个数字: 1、2、3 或 4。没有缺省值。如果未指定 -Nlevel,lint 会使用其基本分析模式。如果指定不带任何参数的 -Nlevel,lint 会设置 -Nlevel=4。
有关基本和增强 lint 模式的说明,请参见4.2 使用 lint。
分析单个过程。报告发生在某些程序执行路径上的无条件错误。不执行全局数据和控制流分析。
分析整个程序,包括全局数据和控制流。报告发生在某些程序执行路径上的无条件错误。
分析整个程序,包括常量传播、常量用作实际参数时的情况以及在 -Nlevel=2 下执行的分析。
在此分析级别检验 C 程序所用的时间比前一级别长 2 到 4 倍。需要额外的时间是因为 lint 通过为程序变量创建可能值的集合对程序进行部分解释。这些变量集合是以常量以及包含程序中的常量操作数的条件语句为基础创建的。这些集合形成创建其他集合的基础(一种常量传播形式)。作为分析结果接收的集合根据以下算法来评估正确性:
如果对象的所有可能值之中存在一个正确值,则该正确值用作进一步传播的基础;否则诊断出一个错误。
分析整个程序,并报告使用某些程序执行路径时会发生的条件错误,以及在 -Nlevel=3 下执行的分析。
在此分析级别上,存在更多诊断消息。分析算法通常对应于 -Nlevel=3 的分析算法,所不同的是任何无效值现在会生成一条错误消息。在此级别上进行分析所需的时间增大两个数量级(大约慢 20 到 100 倍)。在这种情况下,所需的额外时间与以递归、条件语句等为特征的程序复杂性成正比。因此,对超过 100,000 行的程序使用此级别的分析可能很困难。
导致 lint 创建一个名为 llib-lx.ln 的 lint 库。该库是从 lint 在其第二遍检查中使用的所有 .ln 文件创建的。-c 选项会使 -o 选项的任何使用无效。要生成 llib-lx.ln 而不产生任何无关消息,可以使用 -x 选项。如果 lint 库的源文件仅为外部接口,则 -v 选项会很有用。如果使用 -lx 调用 lint,则生成的 lint 库可以在以后使用。
缺省情况下,使用 lint 的基本格式创建库。如果使用 lint 的增强模式,则创建的库将为增强格式,并且只能在增强模式下使用。
将 .ln 文件写入 file,以供 cxref(1) 使用。如果此选项打开,此选项将禁用增强模式。
生成具有 "warning:" 或 "error:" 前缀的简单诊断。缺省情况下,lint 会缓冲某些消息以生成复合输出。
抑制某些消息。请参阅表 4–8。此选项适用于对较大程序的文件子集运行 lint。
抑制某些消息。请参阅表 4–8。
将 .ln 文件写入 file,以供 cxref(1) 使用。如果此选项打开,此选项将禁用增强模式。
接受 C++ 样式的注释。特别是,可使用 // 指示注释的开始。a 可以是 yes 或 no。缺省值为 -XCC=no。指定 -XCC 与指定 -XCC=yes 等效。
仅当使用 -xc99=none 时,才需使用此选项。使用 -xc99=all(缺省值)时,lint 接受由 // 指示的注释。
其中,l 是下列值之一:any、basic、weak、layout、strict、std 或 strong。有关不同的歧义消除级别的详细说明,请参见表 B–13。
如果未指定 -Xalias_level,该标志的缺省值为 -Xalias_level=any。这意味着不存在基于类型的别名分析。如果指定了 -Xalias_level,但未提供级别,则缺省值为 -Xalias_level=layout。
请确保运行 lint 时所在的歧义消除级别不如运行编译器时所在的级别严格。如果运行 lint 时所在的歧义消除级别比编译时所在的级别更严格,结果将很难解释并且可能令人误解。
有关歧义消除的详细说明以及有助于歧义消除的 pragma 的列表,请参见4.6.3 lint 过滤器。
(Solaris 操作系统)已过时。不要使用。请参见4.3.25 -m32|-m64。
(Solaris 操作系统)已过时。不要使用。请参见4.3.25 -m32|-m64。
-Xc99 标志控制编译器对 C99 标准(ISO/IEC 9899:1999,编程语言-C)中已实现功能的识别。
o 可以是下列值之一: all 和 none。
-Xc99=none 会关闭对 C99 功能的识别。-Xc99=all 会打开对支持的 C99 功能的识别。
指定不带任何参数的 -Xc99 与指定 -Xc99=all 等效。
虽然编译器的支持级别缺省为表 C–6 中列出的 C99 功能,但 Solaris 软件在 /usr/include 中提供的标准头文件仍不符合 1999 ISO/IEC C 标准。如果遇到错误消息,请尝试使用 -Xc99=none 获取这些头文件的 1990 ISO/IEC C 标准行为。
保留执行 lint 期间创建的临时文件,而非自动删除它们。a 可以是 yes 或 no。缺省值为 -Xkeeptmp=no。指定 -Xkeeptmp 与指定 -Xkeeptmp=yes 等效。
将临时文件的目录设置为 dir。如果没有此选项,临时文件将放在 /tmp 中。
报告执行每遍 lint 检查所需的时间。a 可以是 yes 或 no。缺省值为 -Xtime=no。指定 -Xtime 与指定 -Xtime=yes 等效。
针对 K&R C 和 Sun ISO C 之间的差异发出警告。a 可以是 yes 或 no。缺省值为 -Xtransition=no。指定 -Xtransition 与指定 -Xtransition=yes 等效。
此选项允许编译器将 U"ASCII_string" 形式的串文字识别成无符号短整型数组。缺省值为 -Xustr=no,它禁止编译器识别 U"ASCII_string" 串文字。-Xustr=ascii_utf16_ushort 允许编译器识别 U"ASCII_string" 串文字。
抑制某些消息。请参阅表 4–8。
将命令行上命名的各个 .c 文件视为以指令 /* LINTLIBRARY */ 或注释 NOTE(LINTLIBRARY) 开头。lint 库通常是使用 /* LINTLIBRARY */ 指令或 NOTE(LINTLIBRARY) 注释创建的。
lint 的大多数消息都很简单,对于它们诊断的每个问题都输出一行语句。对于包含的文件中检测到的错误,编译器会报告多次,而 lint 仅报告一次(无论其他源文件中包含该文件多少次)。文件之间存在不一致时会发出复合消息,并且在少数情况下,文件内部存在问题时也会发出复合消息。单个消息描述所检查的文件中的各个问题。如果使用 lint 过滤器(请参见4.6.2 lint 库)要求对出现的每个问题都输出一条消息,可通过调用带有 -s 选项的 lint 将复合诊断转换为简单类型。
lint 的消息会写入 stderr。
可以使用几个 lint 选项禁止 lint 诊断消息。可以使用后跟一个或多个 tag 的 -erroff 选项禁止消息。可以使用 -errtags=yes 选项显示这些助记符标记。
下表列出了用于禁止 lint 消息的选项。
表 4–8 用于禁止消息的 lint 选项
选项 |
禁止的消息 |
---|---|
-a |
assignment causes implicit narrowing conversion conversion to larger integral type may sign-extend incorrectly |
-b |
statement not reached (unreachable break and empty statements) |
-h |
assignment operator "=" found where equality operator "==" was expected constant operand to op: "!" fallthrough on case statements pointer cast may result in improper alignment precedence confusion possible; parenthesize statement has no consequent: if statement has no consequent: else |
-m |
declared global, could be static |
-erroff=tag |
由 tag 指定的一条或多条 lint 消息 |
-u |
name defined but never used name used but not defined |
-v |
arguments unused in function |
-x |
name declared but never used or defined |
通过某些选项,lint 程序可以显示精确的源文件行以及指向发生错误的行所在位置的指针。启用此功能的选项是 -errfmt=f。如果设置了此选项,lint 会提供以下信息:
源代码行和位置
宏展开
有错误倾向的栈
例如,以下程序 Test1.c 包含一个错误。
1 #include <string.h> 2 static void cpv(char *s, char* v, unsigned n) 3 { int i; 4 for (i=0; i<=n; i++){ 5 *v++ = *s++;} 6 } 7 void main(int argc, char* argv[]) 8 { 9 if (argc != 0){ 10 cpv(argv[0], argc, strlen(argv[0]));} 11} |
如果针对 Test1.c 使用带有以下选项的 lint:
% lint -errfmt=src -Nlevel=2 Test1.c |
产生以下输出:
|static void cpv(char *s, char* v, unsigned n) | ^ line 2, Test1.c | | cpv(argv[0], argc, strlen(argv[0])); | ^ line 10, Test1.c warning: improper pointer/integer combination: arg #2 | |static void cpv(char *s, char* v, unsigned n) | ^ line 2, Test1.c | |cpv(argv[0], argc, strlen(argv[0])); | ^ line 10, Test1.c | | *v++ = *s++; | ^ line 5, Test1.c warning:use of a pointer produced in a questionable way v defined at Test1.c(2) ::Test1.c(5) call stack: main() , Test1.c(10) cpv() , Test1.c(5) |
第一个警告指示互相矛盾的两个源代码行。第二个警告显示调用栈以及导致错误的控制流。
另一个程序 Test2.c 包含一个不同的错误:
1 #define AA(b) AR[b+l] 2 #define B(c,d) c+AA(d) 3 4 int x=0; 5 6 int AR[10]={1,2,3,4,5,6,77,88,99,0}; 7 8 main() 9 { 10 int y=-5, z=5; 11 return B(y,z); 12 } |
如果针对 Test2.c 使用带有以下选项的 lint:
% lint -errfmt=macro Test2.c |
产生以下输出,并显示宏替换的步骤:
运行 lint 对 lint 标记进行预定义。有关预定义标记的列表,另请参见 cc(1) 手册页。
现有注释支持 /*...*/ 形式的 lint 指令,但将来的注释不支持这些指令。建议对所有注释使用源代码注释形式 (NOTE(...)) 的指令。
通过包括 note.h 文件可指定源代码注释形式的 lint 指令,例如:
#include <note.h>
Lint 与其他多种工具共享源代码注释方案。安装 Solaris Studio C 编译器时,还会自动安装 /usr/lib/note/SUNW_SPRO-lint 文件,该文件包含 LockLint 可识别的所有注释的名称。然而,Solaris Studio C 源代码检验器 lint 也会检查 /usr/lib/note 和 Solaris Studio 缺省位置 <install-directory>/prod/lib/note 下的所有文件以查找所有有效的注释。
可通过设置环境变量 NOTEPATH 来指定除 /usr/lib/note 以外的位置,如下所示:
setenv NOTEPATH $NOTEPATH:other_location |
下表列出了 lint 指令及其操作。
表 4–9 lint 指令
本节提供有关 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)) 的文件。