在头文件中的函数原型不匹配的定义,如何抓住这一点?(Function prototype in he

2019-08-17 06:13发布

(我发现这个问题是相似的,但不是重复: 如何检查头文件的有效性在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 <stdio.h>

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,没有错误或警告编译这一点,但勿庸置疑,当我运行它出现segfaults。

  • 编译器是它优良(这个我明白)
  • 链接器并没有抓住它(这个我有点惊讶)
  • 静态分析工具(Coverity公司)没有抓住它(这让我很惊讶)。

我怎样才能捕捉这些类型的错误?

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

Answer 1:

已包括在两个相同的头文件file1.cfile2.c 。 这将非常防止冲突的原型。

否则,这样的错误不能由编译器,因为该函数的源代码是不是当它编译,编译器可见检测file1.c 。 相反,它只能相信已经被赋予了签名。

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



Answer 2:

-Werror-implicit-function-declaration-Wmissing-prototypes或等同于您支持的编译器之一。 那么它要么错误或抱怨,如果该声明不先于全球的定义。

在某种形式的严格C99模式编译程序也应该产生这些消息。 GCC,ICC,以及锵都支持此功能(不知道MS的C编译器和其当前状态,截至2005年VS或2008年最新的和我已经用C)。



Answer 3:

您可以使用现有的邮资-C静态分析平台在http://frama-c.com 。

在您的例子,你会得到:

$ 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.

希望这可以帮助!



Answer 4:

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

但是,这是可能的C ++,因为它使用的名字改编取决于函数签名。 因此,在C ++ void foo(int)void foo(t_struct*)将对联动阶段和连接器不同的名称将提高它的错误。

当然,这不会是容易又一个巨大ç代码库切换到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

当然,这不会是那么容易了巨大的代码库,有可能是导致不能被固定在不改变基本代码编译错误等问题。 您可以通过保护部分缓解它.cpp文件的内容与条件#ifdef和只能使用定期检查,而不是正规的基础之上。



Answer 5:

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

每个函数应该原型一次

你可以拥有.h文件有没有相应.c文件,如果它们不包含任何原型。 唯一.c没有相应的文件.h文件应为包含一个main

你已经知道这一点,你的问题是,你有一个庞大的代码库,治所在今还没有得到遵守。

所以,你怎么从这里到那里? 以下是我大概会做到这一点。

步骤1(要求在代码库单次通过):

  • 对于每个文件foo.c ,创建一个文件foo.h ,如果它不存在。 添加"#include "foo.h"接近顶部foo.c如果你有其中的约定。 .h.c文件应该生活(无论是在同一目录下或并联includesrc目录,遵循它;如果不,尝试引入这样的约定)。
  • 对于每个功能foo.c ,复制其原型foo.h ,如果它不是已经存在。 使用拷贝和粘贴,以确保一切都保持一致。 (参数名称是可选的原型和定义强制性的;我建议保留的名称在这两个地方。)
  • 做一个完整的构建和修复出现的任何问题。

这不会赶上所有的问题。 你仍然可以有一些功能的多个原型。 但你必须抓住其中两个头具有相同的功能不一致的原型两个头都包含在同一个翻译单元的任何情况。

一旦一切都建立干净,你应该有一个系统,就是至少是正确的,你开始用什么。

第2步:

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

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

如果你不能做到这一切在一次,你也许可以做很多它递增。 开始与一个或几个.c文件,清理自己.h文件,并删除其他地方宣布任何额外的原型。

任何时候,你会发现这里的呼叫使用了不正确的原型的情况下,揣摩其中执行该呼叫的情况,以及它是如何使你的应用程序无法正常运作。 创建错误报告和测试添加到您的回归测试套件(你有一个,对吧?)。 你可以展示给管理该测试通过了,因为你所做的一切工作; 你真的不只是瞎搞。

自动化工具,可以解析C是可能有用。 艾拉巴克斯特有一些建议。 ctags也可能是有用的。 根据你的代码是如何格式化的,你也许可以拼凑一些工具,并不需要一个完整的C解析器。 例如,你可以使用grepsed ,或perl从提取的功能定义列表foo.c文件,然后手动编辑列表中删除误报。



Answer 6:

其明显的(“我有一个庞大的代码库”),你不能用手做到这一点。

你需要的是一个自动化的工具,它可以读取你的源文件编译器看到他们,收集所有的函数原型和定义,并确认所有的定义/原型匹配。 我怀疑你会发现这样的工具躺在附近。

当然,这场比赛多少检查签名,这就需要像编译器的前端比较的签名。

考虑

     typedef int T;
     void foo(T x);

在一个编译单元,和

      typedef float T;
      void foo(T x);

在另一个。 你不能仅仅比较平等的签名“行”; 你需要的东西检查时,可以解决的类型。

GCCXML可能能够帮助,如果您使用的是C的GCC方言; 从源文件作为XML块提取顶层声明。 我不知道这是否会解决的typedef,虽然。 显然,你必须建立(相当)的支持,以收集在一个中心位置(数据库)的定义并加以比较。 XML文档的等价物相比至少是相当简单的,如果他们在常规的方式被格式化很容易的。 这可能是最简单的赌注。

如果不工作,你需要的东西,有一个完整的C前端,您可以自定义。 GCC是著名的提供,以及著名很难定义。 铛是可用的,并且可能被压入服务这一点,但据我所知只有GCC方言的作品。

我们的DMS软件再造工具包具有对C的许多方言(GCC,MS,绿大地,...)C前端(全文预处理能力),并建立完整的类型信息的符号表。 利用DMS你也许可以(根据您的应用程序的实际规模)来简单地处理所有的编译单元,并建立只是为每个编译单元的符号表。 内置到C前端检查符号表中的条目“匹配”(根据规则编译器包括使用等效的typedef是兼容的); 所有的人需要做的是协调的阅读,并要求在跨越不同的编译单元的全球范围内的所有符号表项匹配的逻辑。

无论你做这与GCC /铛/ DMS,这是凑齐自定义工具相当数量的工作。 所以,你必须决定你需要更少suprises,相比于能源,建立这样一个自定义工具有多重要。



文章来源: Function prototype in header file doesn't match definition, how to catch this?