我的编译保护为什么不能防止重复定义的包含?

71

我有一个头文件x.h,被多个*.c源文件包含。这个头文件定义了一些结构变量。

我在头文件的开头放置了多重包含防护宏:

#ifndef X_H
#define X_H
...
..
//header file declarations and definitons.


#endif//X_H

在构建时,我遇到了与多个定义相关的链接器错误。我理解这个问题。

  1. 我在头文件顶部放置了一个多重包含预防守卫,如我所拥有的,这样做会防止头文件x.h的多次包含,从而避免在x.h中存在的变量的多重定义吗?

  2. #pragma once 在这个特定的编译器上不起作用,那么该怎么办?有人针对类似问题发布了这个答案。它对我似乎不起作用。这个解决方案是如何工作的?


1
@Stephen:如果您已经在编辑问题标题,请同时查看正文(以及可能的标签,尽管不是这里)。这可以避免重复编辑(和颠簸)。 - Paŭlo Ebermann
@Paulo错过了,不过我一直在尝试关注它,谢谢提醒! - Stephen
6个回答

94

如果连接器出现问题,这意味着您的头文件中有定义而不仅仅是声明。以下是一些错误示例:

#ifndef X_H
#define X_H

int myFunc()
{
  return 42; // Wrong! definition in header.
}

int myVar; // Wrong! definition in header.

#endif

你应该像这样将其拆分为源文件和头文件:

头文件:

#ifndef X_H
#define X_H

extern int myFunc();

extern int myVar; 

#endif

源代码:

int myFunc()
{
  return 42; 
}

int myVar; 

1
“int myVar;” 最多只是一个试探性的定义;通常不会引起问题。如果有初始化程序,它可能会引起问题。尽管如此,我总是使用显式的extern,就像您在“fixed”版本中所示。 - Jonathan Leffler
@Jonathan:C标准中是否有“暂定定义”这样的东西?我认为会出现多个这样的定义会导致“未定义行为”,但在许多编译器/链接器(如gcc...)中,您实际上得到的行为是您想要的! - Roddy
是否可以在函数的内联声明后立即跟随其定义呢? - vtd-xml-author
1
+1 为正确区分定义和声明(https://dev59.com/V3M_5IYBdhLWcg3wXx1N)而加分。一些相关问题的答案未能做到这一点,从而提供了技术上不正确的信息。 - Saheel Godhane

27

头文件保护只适用于单个编译单元,即源文件。如果您多次包含头文件,可能是因为从 main.c 中包含的所有头文件又依次包含了 stdio.h,那么头文件保护将有所帮助。

假设您在 x.h 中定义了函数 f,并在 main.cutil.c 中都包含了该文件,则在创建 main.o 时,就像将 f 的定义复制并粘贴到 main.c 中一样,对于 util.c 也是如此,以创建 util.o。然后链接器会报错,尽管您使用了头文件保护。当然,在 main.c 中有多个 #include "x.h" 语句是可能的,因为有这些保护。


1
我一直有这个疑问!你的解释解决了它 :-) - Shailesh
有用的解释。 - Shuai

17

使用预编译指令可以避免一个编译单元重复包含头文件。例如,如果头文件B.h包含A.h,并且B.cpp包含B.h和A.h,如果不使用预编译指令,则在编译B.cpp时,来自A.h的所有内容都会被声明两次。

使用预编译指令可以防止这种情况发生,目前一切都很好。

但是,在链接时会出现多个定义,即两个编译单元定义了同一事物。这可能意味着您在头文件中有实际的定义,请为所有变量使用extern关键字,确保函数要么是内联的,要么在cpp文件中定义。


14
如果函数不是很大,你可以在它们之前使用"inline"关键字,这样链接器就不会报错。

2

使用多重包含保护可以防止编译器错误,但是您会遇到链接器错误。您的头文件中是否有未使用extern的数据定义?


0

也许X_H已经在其他地方定义了?我遇到过这个问题,其中Xlib在/usr/include/X11/X.h中定义了X_H

要检查,您可以调用gcc -dM -E(如果您正在使用gcc),例如在我使用的构建系统中,它与CC=gcc CFLAGS="-dM -E" make一起工作。如果输出文件包含#define X_H,即使您从文件中删除它(例如使用Y_H),则它已在源代码之外定义。


如果预编译指令中的宏已经被定义,那么编译器会报错,提示没有定义,而不是出现多个定义,对吗? - VLL

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接