在多个编译单元的情况下,避免由于包含相同的头文件而导致多个定义

Posted

技术标签:

【中文标题】在多个编译单元的情况下,避免由于包含相同的头文件而导致多个定义【英文标题】:Avoid multiple definition due to inclusion of same header file in case of multiple compilation units 【发布时间】:2014-08-13 08:10:39 【问题描述】:

我有一个经常使用的函数f()。我希望f() 在头文件util.h 中,这样我就可以轻松使用f(),而无需任何额外的编译:

user1.c:

#include "util.h"
int main()
    f();
    return 0;

util.h:

void f()
    // do some job

编译user1.c

gcc -o user1 user1.c

当有两个编译单元unit2.ounit3.o时会出现问题。它们的源代码如下:

user2.c:

#include "util.h"

void user2_function()
     f();
     // do other jobs

user3.c:

#include "util.h"
extern void user2_function();
int main()
    f();
    user2_function();
    return 0;

当我尝试按如下方式编译这些源代码时出现multiple definition of f 错误:

gcc -c user2.c
gcc -c user3.c
gcc -o user user2.o user3.o

问题是如何解决multiple definition 问题?或者有没有更好的解决方案?

在实际情况下,util.h 中有数百个函数,大约有 50 个不同的编译单元。

我试图避免使用库和实用函数的编译步骤,因为:

    我使用了许多不同的平台。 不知道有没有简单的解决方案,即不使用cmake等。 我还对协处理器使用交叉编译。 我希望编译实用程序函数时使用的标志与编译用户代码时使用的标志相同。

【问题讨论】:

如果要在多个文件中使用,要么在header中设为static,要么在header中声明并在自己的源文件中定义。尝试使用头文件中的非静态函数定义是......不会很好地工作。正如你所发现的,艰难的道路。 printf() 等函数没有在<stdio.h> 中定义是有原因的;就是这个! 使用staticstatic inline在运行时性能、TLB未命中计数、指令缓存未命中计数等方面是否有任何反作用?编译时间对我来说并不重要。 如果是静态的,那么每个包含头文件的文件都会得到一个函数的副本,不管它是否被使用,所以程序最终会得到函数代码的多个副本。如果您使用static inline,则只有在您使用时才会生成代码。因此会浪费空间,而且如果大量使用它,您最终可能需要在内存中复制相同代码的多个副本,每个文件对应一个调用它的文件,这增加了指令缓存未命中的可能性。使用多个目标文件向程序添加一个额外的目标文件很容易。使用库使其更容易。 【参考方案1】:

您实际上是在头文件中指定函数的主体。因此,目标代码将被发送到两个目标文件中,这是不允许/有效的。

要纠正这个问题:

标记函数inline 在头文件中仅放置函数的前向声明,并在单独的源代码文件中指定主体,然后与其他源代码文件一起编译

【讨论】:

我尽量避免you then compile along with your other source code files。我需要一个简单的解决方案,因为有很多平台,交叉编译,标志一致性,... 声明函数static 也可以,是的,尽管它具有不同的含义/效果。 static 函数实际上会在两个翻译单元中发出它的主体,但编译器/链接器会使用魔法,因此实际上不会有两个定义。 IE。您将拥有该功能的两个副本。在inline 的情况下,该函数被发送到两个翻译单元中,但链接器随后删除了其中一个定义。 IE。您将只有该函数的一份副本。 在头文件中将函数定义为static 函数会起作用,但会复制每个使用头文件的目标文件中的代码。此外,如果文件需要标头而不需要函数,则函数仍将被定义(但未使用,可能导致编译器警告)。 static inline 函数定义可能会更好地工作,如果函数足够小,inline 是一个明智的选择——尽管如果它不明智,编译器根本不会内联它。 声明一个函数inline与它的大小无关。您没有覆盖编译器的内联逻辑。 inline 关键字的含义完全不同。【参考方案2】:

util.h 更改为util.c

并使用包含

的新util.h
void f(); 

改为。

【讨论】:

以上是关于在多个编译单元的情况下,避免由于包含相同的头文件而导致多个定义的主要内容,如果未能解决你的问题,请参考以下文章

C - 头文件

delphi文件打包方式(将多个文件编译到一个单元中)

c ++包含不同的头文件,在多个源文件中具有相同的类实现

如何让g ++搜索特定目录中的头文件?

如何让g ++搜索特定目录中的头文件?

为啥我在写C语言的头文件,编译时会出现重新定义变量的情况?