代码分析器 LCLint

作者:David Santo Orcero

在 C 语言诞生之初,还没有函数原型,人们就一致认为代码调试是一项极其复杂的任务。因此,创建了一个非常强大的工具,lint,它能够对代码进行大量的静态验证。这个有趣的程序是由 S.C. Johnson 在 70 年代早期编写的,它是第一个静态代码验证工具。

随着 ANSI C 标准的创建,一些 lint 验证变得多余。然而,lint 仍然被使用,同时其他的工具也被创建出来。John Guttag 和 Jim Horning 更进一步,编写了 LCLint。粗略地说,它的工作方式与 lint 相同,lint 的用户只需很少的时间就能理解如何使用它。然而,通过一些工作,我们可以通过使用 LCLint 来改进从 lint 获得的分析细节。自诞生以来,它得到了 ARPA、NSF 和 DEC ERP 的财政支持。

今天,LClint 源代码是免费提供的。它是用 C 语言编写的;因此,任何拥有标准 ANSI C 编译器的用户都可以为他们的机器重新编译它。Linux 用户可以将其下载为 tar 归档文件和 RPM 包。它以源代码和可执行格式提供。一些发行版,如 SuSE Linux 5.3,目前将其作为发行版的一部分提供。可以从 http://linuxdoc.org/ 下载。

原始的 lint 命令远少于 LCLint,所有命令都可以使用 LCLint 模拟。这就是为什么我们可以使用 LCLint 来替代 lint。lint 和 LCLint 之间的大部分区别在于,lint 的注释与最小化误报消息的数量有关,而 LCLint 的注释具有更多的功能,其中只有一些用于此目的。

lint 命令

LCLint 完全支持所有 lint 命令,但我们可以使用 LCLint 注释获得更准确的分析。侧边栏“lint 命令”中显示了 lint 最常见注释与更准确版本命令之间的等效性。

LCLint 的基本思想是在编译之前在我们的代码上运行它。lint 将搜索程序中许多可能的错误并返回其发现。然而,错误候选的数量,虽然大于 gcc-Wall 选项,但仍然很小,因为 LCLint 对指定的代码进行分析,但不知道该代码是否是根据其语义编写的。因此,我们可以以几种形式使用 LCLint。第一种是将其用作原始代码的简单静态分析器。虽然在这种模式下,LCLint 在没有大量时间投入的情况下识别出许多错误,但建议仅用于调试其他程序员编写的代码。这种分析模式不是最好的,因为 LCLint 不知道编写代码时使用的语义,因此它无法发现程序是否做了它理论上应该做的事情。

当我们希望在自己的程序上使用它时,我们通过注释来指示我们程序的语义,这些注释为 LCLint 提供了更多关于程序函数的信息。LCLint 使用这些信息来推断程序是否做了我们想要的事情,而不是其他事情。这种使用 LCLint 的形式允许我们以最小的时间投入来减少大量代码的调试工作。我将讨论一些注释来演示 LCLint 的使用。有关更多信息,请阅读手册。

第三种分析方法是基于抽象数据类型的规范。我们可以使用伪语言 LCL 指定接口、函数、类型和谓词。LCLint 将自动生成与此类规范对应的头文件,并验证代码是否与这些规范一致。然而,尽管 LCL 是一种代数规范语言,但 LCLint 不会证明定理或属性,验证代数定义的正确性或完整性,或生成代码。它将生成标题并验证代码是否与代数定义相对应。

LCLint 中的分析深度级别

LCLint 是一个多功能工具,因为它可以根据我们希望的任何抽象级别进行代码分析。LCLint 有几个分析级别,对应于或多或少的不同检查技术。这些级别是

  • weak,执行最少的监控。此级别应仅用于没有注释的 C 代码。LCLint 静态分析可以用作第一步。

  • standard 是默认分析。它执行与 weak 分析相同的操作,外加一些。它需要一些注释才能真正有用。

  • checks 执行 standard 的所有检查,加上对函数参数和不一致性的完整监控。这是一个很好的级别,可以证明在使用其他软件测试技术之前,确实不存在静态可验证的错误。

  • strict 进行极其严格的检查。仅当存在非常细微但令人恼火的错误,或者我们是强类型和强结构化语言的狂热爱好者时才应使用。它包括检查,例如在函数中指定的全局变量是否在该函数中可用(根据注释标记为可用或不可用),并且类型检查非常严格。LCLint 的程序员向我保证,他们将为任何编写真实程序并在这种模式下使用 LCLint 时没有收到警告的人颁发奖品。

除了这些通用分析选项外,对于每个可能的检查,我们都有一个选项来启用一个并禁用另一个。

我们可以通过增量方法获得 LCLint 的最佳使用。首先,使用 Wall 编译程序可以帮助我们找到最大的错误;例如,真正奇怪的指针操作或以错误方式传递给函数的参数。

在此之后,我们可以从 weak 分析开始。如果我们使用命名约定——强烈推荐——这是一个使用标志来测试我们是否使用了正确的名称,并以正确的方式使用变量和宏的好时机。这比我们不使用注释所能做的分析更深入;如果我们使用了注释,我们可以获得那种分析。

常用分析的最高级别是 checks。如果我们只获得虚假分析,那么是时候使用动态调试技术了。

strict 级别的分析适用于我们无法找到已知存在的错误的代码片段。

LCLint 注释

所有 LClint 注释都有一个通用语法,即

/*@command@*/

所有注释都插入到代码中我们可以插入注释的相同位置;因此,它在代码中的存在不会影响正常编译。

注释最常见的位置是在修改语义的位置附近;例如,在类型的定义中,如果注释将影响该类型。

LCLint 注释

LCLint 几乎有一百个命令。“LCLint 注释”侧边栏中显示了一些最有用的命令。我们不是用这些命令激活或停用检查类型,而是丰富代码,以便 LCLint 获得有关程序语义的信息,并能够更准确地进行分析。

命名约定

命名约定的使用是一种编程技术,它有很多用户,但也有很多反对者。LCLint 不强制您使用命名约定,但它包含对其中一些约定的支持。支持的命名约定是斯洛伐克语、捷克语和捷克斯洛伐克语。

斯洛伐克语命名约定

斯洛伐克语命名约定的规则是标识符使用方案 abstracttypeVarname 构建。抽象类型和标识符名称以标识符名称的第一个字符大写分隔。侧边栏中显示了与斯洛伐克语命名约定相关的 LCLint 注释。请记住,在使用斯洛伐克语命名约定时,类型的名称绝不能有大写字母。

捷克语命名约定

捷克语命名约定的规则是标识符使用方案 abstracttype_varname 构建。抽象类型名称和标识符名称用下划线字符分隔。侧边栏中显示了与捷克语命名约定相关的修饰符。请记住,在使用捷克语命名约定时,类型的名称绝不能有下划线字符。

捷克斯洛伐克语命名约定

捷克斯洛伐克语命名约定与同时使用捷克语和斯洛伐克语命名约定相同。这就是为什么在捷克斯洛伐克语命名约定中存在有效的捷克语和斯洛伐克语标识符。侧边栏中显示了与捷克斯洛伐克语命名约定相关的修饰符。请记住,在使用捷克斯洛伐克语命名约定时,类型的名称绝不能有下划线字符或大写字母。

The Code Analyser LCLint
David Santo Orcero (irbis@df.ibilce.unesp.br) 正在巴西 IBILCE 攻读分子生物物理学博士学位。(www.biocristalografia.df.ibilce.unesp.br/irbis) 他获得了 FAPESP 研究项目“分子生物物理学并行计算方法”的资助。他使用 Linux 集群对血红蛋白和蛇毒素的三维结构进行科学研究。
加载 Disqus 评论