头文件中的函数原型与定义不匹配,如何捕获这个?

(我发现这个问题类似但不重复: 如何检查C编程语言中头文件的有效性 )

我有一个函数实现,以及一个头文件中的不匹配原型(同名,不同类型)。 头文件包含在使用该函数的C文件中,但不包含在定义该函数的文件中。

这是一个最小的测试用例:

header.h:

void foo(int bar); 

FILE1.C:

 #include "header.h" int main (int argc, char * argv[]) { int x = 1; foo(x); return 0; } 

文件2.c:

 #include  typedef struct { int x; int y; } t_struct; void foo (t_struct *p_bar) { printf("%x %x\n", p_bar->x, p_bar->y); } 

我可以使用VS 2010编译它,没有任何错误或警告,但毫不奇怪,当我运行它时会出现段错误。

  • 编译器很好用(我明白了)
  • 链接器没有捕获它(我有点惊讶)
  • 静态分析工具(Coverity)没有抓住它(我非常惊讶)。

我怎样才能发现这些错误?

[编辑:我意识到如果我在file2.c中#include“header.h”,编译器会抱怨。 但是我有一个庞大的代码库,并不总是可能或适当的保证函数原型的所有头都包含在实现文件中。

file1.cfile2.c包含相同的头文件。 这几乎可以防止原型冲突。

否则,编译器无法检测到这样的错误,因为编译file1.c时编译器看不到函数的源代码。 相反,它只能信任已经给出的签名。

至少在理论上,如果在目标文件中存储了额外的元数据,链接器就能够检测到这种不匹配,但我不知道这是否实际可行。

-Werror-implicit-function-declaration-Wmissing-prototypes或您支持的编译器上的等效项。 如果声明不在全局定义之前,它将会出错或抱怨。

以某种forms的严格C99模式编译程序也应该生成这些消息。 GCC,ICC和Clang都支持这个function(不确定MS的C编译器及其当前状态,因为VS 2005或2008是我用于C的最新版本)。

您可以使用http://frama-c.com上提供的Frama-C静态分析平台。

在你的例子中你会得到:

 $ frama-c 1.c 2.c [kernel] preprocessing with "gcc -C -E -I. 1.c" [kernel] preprocessing with "gcc -C -E -I. 2.c" [kernel] user error: Incompatible declaration for foo: different type constructors: int vs. t_struct * First declaration was at header.h:1 Current declaration is at 2.c:8 [kernel] Frama-C aborted: invalid user input. 

希望这可以帮助!

看起来这对于C编译器来说是不可能的,因为它将函数名称映射到符号对象名称的方式(直接,不考虑实际签名)。

但是这可以用C ++实现,因为它使用依赖于函数签名的名称修改 。 因此在C ++中, void foo(int)void foo(t_struct*)在链接阶段将具有不同的名称,并且链接器将引发有关它的错误。

当然,依次将巨大的C代码库转换为C ++并不容易。 但是您可以使用一些相对简单的解决方法 – 例如,将单个.cpp文件添加到项目中并将所有C文件包含在其中(实际上使用某些脚本生成它)。

以你的例子和VS2010为例,我将TestCpp.cpp添加到项目中:

 #include "stdafx.h" namespace xxx { #include "File1.c" #include "File2.c" } 

结果是链接器错误LNK2019:

 TestCpp.obj : error LNK2019: unresolved external symbol "void __cdecl xxx::foo(int)" (?foo@xxx@@YAXH@Z) referenced in function "int __cdecl xxx::main(int,char * * const)" (?main@xxx@@YAHHQAPAD@Z) W:\TestProjects\GenericTest\Debug\GenericTest.exe : fatal error LNK1120: 1 unresolved externals 

当然,对于庞大的代码库来说,这不会那么容易,可能会有其他问题导致无法在不更改代码库的情况下修复编译错误。 您可以通过使用条件#ifdef保护.cpp文件内容来部分缓解它,并且仅用于定期检查而不是常规构建。

每个foo.c文件中定义的每个(非静态)函数都应该在相应的foo.h文件中有一个原型,而foo.c应该有#include "foo.h" 。 ( main是唯一的例外。) foo.h不应包含未在foo.c定义的任何函数的原型。

每个函数都应该原型一次

如果.h文件不包含任何原型,则可以没有相应的.c文件。 唯一没有相应.h文件的.c文件应该是包含main

您已经知道这一点,而您的问题是您有一个庞大的代码库,其中没有遵循此规则。

那你怎么从这里到那里? 这是我可能会这样做的。

第1步(需要对代码库进行一次传递):

  • 对于每个文件foo.c ,如果文件尚不存在,则创建一个文件foo.hfoo.c的顶部附近添加"#include "foo.h" 。如果你有.h.c文件应该存在的约定(在同一目录或并行includesrc目录中,请遵循它;如果不,尝试引入这样的约定)。
  • 对于foo.c每个函数,将其原型复制到foo.h如果它还没有)。 使用复制粘贴确保一切都保持一致。 (参数名称在原型中是可选的,在定义中是强制性的;我建议在两个地方保留名称。)
  • 做一个完整的构建并修复出现的任何问题。

这不会解决你所有的问题。 对于某些function,您仍然可以拥有多个原型。 但是你会发现任何情况,其中两个标题具有相同function的不一致原型, 并且两个标题都包含在同一个翻译单元中。

一旦所有东西都干净利落,你应该拥有一个至少和你开始时一样正确的系统。

第2步:

  • 对于每个文件foo.h ,删除未在foo.c定义的函数的任何原型。
  • 做一个完整的构建并修复出现的任何问题。 如果bar.c调用了一个在foo.c定义的函数,那么bar.c需要一个#include "foo.h".

对于这两个步骤,“修复任何出现的问题”阶段可能是漫长而乏味的。

如果你不能一次完成所有这些,你可以逐步做很多事情。 从一个或几个.c文件开始,清理他们的.h文件,并删除在别处声明的任何额外原型。

每当您发现调用使用不正确的原型的情况时,请尝试找出执行该调用的情况,以及它如何导致您的应用程序行为不当。 创建一个错误报告并将测试添加到回归测试套件(你有一个,对吧?)。 您可以向管理层certificate,由于您已完成的所有工作,测试现已通过; 你真的不只是搞乱了。

可以解析C的自动化工具可能很有用。 艾拉巴克斯特有一些建议。 ctags也可能有用。 根据代码的格式化,您可以将一些不需要完整C解析器的工具放在一起。 例如,您可以使用grepsedperlfoo.c文件中提取函数定义列表,然后手动编辑列表以删除误报。

它显而易见(“我有一个巨大的代码库”)你不能手工完成这个。

您需要的是一个自动化工具,可以在编译器看到源文件时读取源文件,收集所有函数原型和定义,并validation所有定义/原型是否匹配。 我怀疑你会找到这样的工具。

当然,这个匹配很多检查签名,这需要像编译器的前端来比较签名。

考虑

  typedef int T; void foo(T x); 

在一个编译单元,和

  typedef float T; void foo(T x); 

在另一个。 你不能只是比较签名“行”是否相等; 你需要一些可以在检查时解决类型的东西。

如果您使用GCC方言C,GCCXML可能会有所帮助; 它从源文件中提取顶级声明为XML块。 但我不知道它是否会解析typedef。 您显然必须构建(相当大的)支持以在中心位置(数据库)收集定义并进行比较。 比较XML文档的等价物至少相当简单,如果它们以常规方式格式化,则非常容易。 这可能是你最容易的赌注。

如果这不起作用,您需要具有可以自定义的完整C前端的东西。 GCC是着名的,并且很难定制。 Clang是可用的,可能会为此服务,但AFAIK只适用于GCC方言。

我们的DMS软件再造工具包具有C前端(具有完全预处理function),适用于C(GCC,MS,GreenHills等)的多种方言,并构建具有完整类型信息的符号表。 使用DMS,您可以(根据应用程序的实际规模)简单地处理所有编译单元,并为每个编译单元仅构建符号表。 检查符号表条目是否“匹配”(根据编译器规则兼容,包括使用等效的typedef)内置于C前端; 所有人需要做的是编排读数,并在各种编译单元的全局范围内调用所有符号表条目的匹配逻辑。

无论你是用GCC / Clang / DMS做到这一点,拼凑一个自定义工具都是相当多的工作。 因此,与构建此类自定义工具的能量相比,您已经决定了减少吸引力所需的重要性。