【问题标题】:Include Guards in C在 C 中包含守卫
【发布时间】:2019-04-25 04:47:59
【问题描述】:

我有 2 个必须相互包含的头文件。

config.h:

#ifndef CONFIG
#define CONFIG

#include "debug.h"

typedef struct Config_t {
    /* some stuff */
} Config;
#endif

调试.h

#ifndef DEBUG
#define DEBUG

#include "config.h"

void somePrintingFunction(Config* conf);
#endif

这是我得到的错误:

debug.h:错误:未知类型名称“配置”

config.c:警告:函数“somePrintingFunction”的隐式声明

debug.h:错误:未知类型名称“配置”

我猜它在标头声明中循环?


编辑:

修复了合并两个文件的问题,从而简化了项目设计。如果您想要真正的修复,请检查 cmets。

【问题讨论】:

  • 不应该是 #define 吗?您正在使用#def。
  • 是的,抱歉,这是错字。在我的代码中是#define
  • 您从 config.c 收到警告消息,但您没有显示它。请创建minimal reproducible example
  • 不存在2个头文件需要相互包含的场景。您的程序设计很混乱,唯一正确的解决方案是修复程序设计。

标签: c ifndef


【解决方案1】:

问题在于循环包含。在声明结构之前,您实际上已经包含了您的函数。 假设您包含config.h(假设您删除了错误的包含a.h,预处理器会这样做:

#include "debug.h"

typedef struct Config_t {
    /* some stuff */
} Config;

并定义符号CONFIG,这样该文件就不会被包含两次。然后它评估剩余的包含:

#include "config.h"

void somePrintingFunction(Config* conf);


typedef struct Config_t {
    /* some stuff */
} Config;

并定义符号DEBUG。由于定义了符号CONFIG,它不会再包含config.h,因此它完成了。现在注意函数声明是如何在结构声明之前进行的。要解决这个问题,请使用这样的前向声明

#include "config.h"

struct Config_t;
void somePrintingFunction(struct Config_t* conf);

所以编译器在你使用它之前就知道Config 是什么。请记住在c 文件中定义所有使用预声明结构或类的函数,因为预声明对象尚未定义,但将在c 文件中。

编辑:我应该提一下,循环包含并不是一件好事,您通常可以找到另一种不太危险的解决方案。

【讨论】:

  • 问题标记为 C。
  • @Lundin 哎呀我更正了苍蝇类型。无论如何,C 预处理器应该保持不变。
【解决方案2】:

config.h 包含debug.h 时,debug.h 将尝试包含config.h,但由于已经定义了CONFIG 保护宏,config.h 的追溯“包含”将被跳过,并且解析将在下一行继续:

void somePrintingFunction(Config* conf);

没有定义Config 类型。

正如StoryTeller 所指出的,您可以通过前向声明Config_t 结构来打破相互依赖:

struct Config_t;
void somePrintingFunction(struct Config_t* conf);

从 C11 开始,您还可以使用 typedef,因为 C11 可以处理重复的 typedef,只要它们引用相同的类型。

typedef struct Config_t Config;
void somePrintingFunction(Config* conf);

(聚合(结构或联合)的前向声明不允许您声明这些类型的对象(除非已经提供了完整的定义),但由于 C 保证所有指向结构或联合的指针必须看起来相同,它们足以让您开始使用指向这些类型的指针。)

【讨论】:

  • 我不知道 C11 有这种改进。不错。
  • 但是我会重新定义 Config 吗?
  • @BeGreen 你不会在 C11 和更新是我说的。在所有 C 中重复前向声明是完全可以的。从 C11 开始重复 typedef 到同一时间是可以的,在以前的版本中,您可能会收到警告(尤其是使用 -pedantic 编译时)。
  • struct Config 应该是 struct Config_t 以匹配 OP 的原始代码。在这种情况下,@BeGreen、Config 被用作结构标记,它与typedef 定义的Config 位于不同的命名空间中。 struct Config_t; 是一个不完整的类型,一旦遇到完整的 struct Config_t { ... }; 就会完成。 void somePrintingFunction(struct Config_t *conf); 声明很好,即使 struct Config_t 不完整。
  • @IanAbbott 谢谢。我没有注意到。
【解决方案3】:

debug.h 不需要其他标头。您可以单独使用 Config_t 的前向声明来定义该函数

struct Config_t;
void somePrintingFunction(struct Config_t* conf);

【讨论】:

  • 而且编译器会明白不是来自 debug.h 而是来自 a.h 的 Config_t?
  • @BeGreen 编译器不需要关心。保证所有结构指针看起来都一样。
  • @BeGreen - 在调用函数或定义函数时,您需要包含其他头文件。所以是的,它会的。在有效的 C 程序中,给定标记只能有一个结构定义。如果没有一个,你就会遇到更大的问题。
  • 当我这样做时,我的功能出现了一些问题。 “将指针指向不完整的 'Config” {aka struct Config_t}'
  • @BeGreen - 我确实说过在定义函数时需要在结构定义中包含标题。在对应的 *.c 文件中。
猜你喜欢
  • 2012-01-19
  • 2013-06-22
  • 2017-11-21
  • 2017-07-31
  • 2011-01-24
  • 2022-01-22
  • 1970-01-01
  • 2015-03-04
相关资源
最近更新 更多