使用#ifdef头文件保护将.h文件与.c文件链接在一起

13

我遇到了链接.h和.c文件的问题,我已经阅读了一些关于这个问题的帖子,但所有这些帖子都有点模糊,我还无法完全掌握它的概念,而且我遇到了很多链接问题。 假设我有b.c和b.h,我将在a.c中使用它们,我困惑是否应该在a.c和b.c中都包含b.h,因为b.c本身需要了解b.h中定义的结构,我有一些函数在b.h中具有原型,并在b.c中定义,这些函数还使用b.h中的结构,我没有在b.c中包含b.h,因为据我所知,b.h更像是接口,用于a.c使用b.c中的函数...这里有一个更清晰的例子

b.h文件

typedef struct{
int x, y;
}myStruct;

void funct1(myStruct);
void funct2(myStruct);

b.c 文件

void funct1(myStruct x)
{
    //do something
}

void funct2(myStruct y)
{
     //do something
} 

a.c文件

#include "b.h"

int main()
{
myStruct x;
  funct1(x);
  funct2(y);
return 0;
}

在cygwin中执行了命令:gcc b.c a.c -g

现在令人困惑的是,当编译b.c时,存在链接错误,它无法检测到b.h中的结构和原型。因为我所知道的就是b.h用于从a.c链接b.c,但当两个.c文件都被编译时,似乎b.c找不到其结构和原型。

为什么我没有在b.c中包含b.h? 答案:因为据我所知,b.h已经包含在a.c中了,如果我在b.c中再次包含它,我将重复包含 <---这是我目前所学到的,我知道有#ifdef,但似乎它不起作用,也许我还不知道如何使用它,如果您知道,请随意讨论。

如果您有任何想法,请告诉我一些。

注意:假设上述所有代码都符合语法规则,如果有任何拼写错误,请忽略,我只关注.h和.c之间的包含。

4个回答

32
您确实需要在b.c中包含#include b.h。每个文件在链接器接管之前都会单独编译,所以即使您已经在a.c中包含了b.h,也没有问题,因为b.c是单独编译的,并且不知道b.h的内容,除非您将其包含进来。
以下是#include保护的示例。
// some_header_file.h
#ifndef SOME_HEADER_FILE_H
#define SOME_HEADER_FILE_H
// your code
#endif

当任何地方包含some_header_file.h时,如果SOME_HEADER_FILE_H已被定义,则在#ifndef和#endif之间的所有内容都将被忽略,这将在编译单元中第一次包含它时发生。
通常的做法是以文件名命名#define,以确保在项目内唯一。我也喜欢在前缀中加入我的项目或名称空间的名称,以减少与其他代码冲突的风险。
注意:即使有上述包含保护,同一头文件仍可以在项目中多次包含,只是不能在同一编译单元中包含两次。如下所示:
// header1.h
#ifndef HEADER_H
#define HEADER_H
int test1 = 1;
#endif

// header2.h
#ifndef HEADER_H
#define HEADER_H
int test2 = 2;
#endif

现在让我们看看当我们尝试包含上述两个文件时会发生什么。在单个编译单元中:
// a.cpp
#include "header1.h"
#include "header2.h"
#include <iostream>
int main()
{
   std::cout << test1;
   std::cout << test2;
};

这会生成编译器错误,因为test2没有定义 - 它在header2.h中被忽略,因为HEADER_H已经被定义了。现在,如果我们将每个头文件包含在单独的编译单元中:
// a.cpp
#include "header2.h"
int getTest2()
{
   return test2;
};

// b.cpp
#include "header1.h"
#include <iostream>
int getTest2(); // forward declaration
int main()
{
   std::cout << test1;
   std::cout << getTest2();
};

它能够成功编译并生成预期的输出(1和2),即使我们包含了两个定义了HEADER_H的文件。请注意,不需要解释,保留HTML标签。

一个问题,它能检测到SOME_HEADER_FILE是否已经在其他.c文件中定义了吗? - lemoncodes
#ifndef 表示 如果未定义,无论何时您包含 .h 文件,它都会定义该符号,因此如果您再次包含相同的 .h 文件,则该符号已经被定义。 - BenjiWiebe
@lemoncodes:不,将 b.h 包含在 a.c 中对 b.c 没有影响。当第一次将 b.h 包含到 b.c 中时,SOME_HEADER_FILE 仅在 b.c 中定义。随后,在 b.c 中使用 #ifdef#ifndef 语句可以检测到它是否在 b.c 中。 - Remy Lebeau
@lemoncodes 我已经编辑了我的答案,进一步解释了Remy的观点(实际上在我注意到他的评论之前,我正在输入)。 - JBentley
哦,你的意思是说当header1.h被包含在a.cpp中时,HEADER_H仅存在于该编译中,而当我将另一个HEADER_H的header2.h包含到b.cpp中时,header1.h中存在的那个HEADER_H就不存在了? - lemoncodes
显示剩余4条评论

3
你需要在所有使用定义的结构的文件中包含。因此,你需要在两个文件中都加入#include <b.h>。为了避免多次加载,你需要使用指令#ifdef。在你的情况下:

b.h

#ifndef B_H
#define B_H

typedef struct{
    int x, y;
}myStruct;

void funct1(myStruct);
void funct2(myStruct);

#endif

和 b.c:

#include "b.h"

void funct1(myStruct x)
{
    //do something
}

void funct2(myStruct y)
{
     //do something
} 

先生,当将b.h包含在a.c中时,是否b.c会检测到B_H已经定义了? - lemoncodes
是的。指令 #define B_H 定义变量已定义。因此,当 #ifndef 搜索 B_H 时,它将返回 false 并跳过所有代码直到 #endif。重要的是,变量 B_H 只在一个 .h 文件中定义。 - William Seiti Mizuta
@WilliamSeitiMizuta:你的“Yeap”是错误的。当b.h被包含在a.c中时,b.c无法检测到它。b.h必须被包含在b.c中,这样B_H才能在b.c中定义。 - Remy Lebeau
我认为@WilliamSeitiMizuta理解了我的观点,我不是在谈论b.h,而是在谈论b.h中的B_H。 - lemoncodes
@lemoncodes Remy 是正确的。如果你在 b.c 中包含了 b.h,那么对于该编译单元来说 B_H 尚未被定义,即使你已经在 a.c 中包含了它。我已经更新了我的答案以进一步解释这个问题。 - JBentley
我想我明白他们的意思。b.h在每次编译时只读取一次,即如果您在a.c文件中放置#include "b.c",则在编译a.c时只读取一次b.h。但是,如果同时编译两个.c文件,则每次编译都会读取b.h。 - William Seiti Mizuta

1

正确的编码应该包括 b.h 在 b.c 中。

这里是一个应该可以工作的头文件保护:

#ifndef B_H_INCLUDED
#define B_H_INCLUDED
//header file
#endif

将您的声明放在注释所在的位置,并在需要的地方包含它们。
编辑 我的理解是,因为a.c依赖于b.c,所以gcc首先编译b.c。但是当它首先编译b.c时,b.h尚未被包含。

我认为当我编译时,它是分别编译和链接的,所以顺序可能并不重要? - lemoncodes
是的,最重要的是当前编译单元(即您不希望在同一单元内两次包含相同的文件)。 - JBentley
哦,如果是这样的话,其他的.c文件能够检测到,比如在你的例子中,如果B_H_INCLUDED已经存在了? - lemoncodes
嗯,我的意思是你的B_H_INCLUDED在一个.h文件中,对吧?所以当这个.h文件被包含在a.c中,并再次被包含在b.c中时,它会不会出现B_H_INCLUDED已经定义的问题? - lemoncodes
@lemoncodes:不。在编译器处理合并的代码之前,预处理器会将.h文件的内容复制到.c文件的内容中。在预处理后的.c文件中,“标志”仅存在于每个文件的基础上。多个.c文件是相互独立编译的。 - Remy Lebeau
显示剩余6条评论

0
你需要在 b.c 中 #include b.h。这不仅仅是为了 a.c 的接口,b.c 也需要知道相同的定义来编写自己的代码。你不在 b.c 中包含 b.h 的理由是错误的。每个 .c 文件都是单独编译的。当编译器完成 a.c 后,它会重新开始处理 b.c。即使 a.c 包含了 b.h,因为 b.c 没有意识到 a.c 的存在,所以这并不重要。头文件保护的目的是防止在编译给定的 .c 文件时多次处理 .h 文件。如果没有保护,声明将被多次编译,导致现有符号的多个声明错误。

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