【问题标题】:Multiply-included header with guards带有防护装置的多重包含的标头
【发布时间】:2026-02-12 01:55:02
【问题描述】:

假设我有一个特殊的头文件,它被设计为多次包含,并根据某些宏的值生成不同的代码,这些宏必须在包含它之前定义。比如下面的文件dumb.h

#define RETFUNC return_ ## VALUE
static inline int RETFUNC() {
  return VALUE;
}

您可以像这样包含它:

#define VALUE 100
#include "dumb.h"
#define VALUE 200
#include "dumb.h"

它会生成两个版本的函数,例如:

static inline return_100 {
  return 100;
}

static inline return_200 {
  return 200;
}

太棒了1

如何为这种类型的文件制作标头保护?如果没有保护,如果头文件的两个不同使用者都请求相同的VALUE,您会收到错误,因为会生成两个相同版本的return_* 函数。

现在是一个普通的标头守卫,例如:

#ifndef DUMB_H_
#define DUMB_H_
...
#endif // DUMB_H_

不会起作用,因为它只会有效地包含文件一次(上面的#include 序列将生成函数的_100 版本,但不会生成_200 版本)。

原则上,我想使用令牌粘贴生成宏的 名称,以作为警卫进行检查,例如:

#ifndef DUMB_H_ ## VALUE
#define DUMB_H_ ## VALUE
...

但是不能像那样使用令牌粘贴(在宏扩展之外)。

是否有任何其他选项可以防止该文件被多次包含在同一个VALUE 中,但仍然允许为每个不同的VALUE 请求有效地包含一次正文?


1 不是伟大,因为这是一个很好的模式或类似的东西,但它至少看起来有效。

【问题讨论】:

  • 我想这里有一个XY Problem。即使假设真正的函数比这里显示的更复杂,为什么数字不是函数的参数?您如何使功能有所不同?或者您是否需要一个函数指针来包装一个常量以供在某处使用,并且与 C++ 不同的是,您不能创建一个具有存储记录数字并提供函数指针的对象。
  • @JonathanLeffler - 是的,实际情况更复杂。这是一个 MCVE 来说明这个问题。显然我实际上并不想生成像return_100 这样的函数。 VALUE 不一定是数字,但可能不能简单地传递给函数(例如,类型)。多重包含模式有时很有用,因此我对该问题的特定解决方案感兴趣,即使其他一些潜在问题承认完全不同的解决方案。也就是说,我很了解 XY,但在这里我主要对这个狭隘的问题感兴趣。
  • 当所谓的“受保护的头文件”以相同的 VALUE 被多次包含时,您预计会发生什么。编译错误?即使你没有守卫,如果你定义了两个同名的函数,也会抛出链接错误。
  • @aneeshjose - 不,在这种情况下,对于给定的VALUE,它应该只包含一次,如问题末尾所述。在上面的 MCVE 中,它会为每个调用它的不同 VALUE 生成一个 return_* 函数。
  • 理想情况下,您的澄清 cmets 应该是对问题的更新。

标签: c c-preprocessor c11


【解决方案1】:

有了comment 1comment 2中的解释,我觉得正常的流程是这样的。我将使用一个类型作为参数,并为不同类型生成一个“最小”函数:

dumb.h

#ifndef DUMB_H_INCLUDED
#define DUMB_H_INCLUDED

#define MIN_TYPE(x) \
    static inline x min_ ## x(x v1, x v2) { return (v1 < v2) ? v1 : v2; }

#endif /* DUMB_H_INCLUDED */

消费者代码

#include "dumb.h"
#include <inttypes.h>

MIN_TYPE(int)          // No semicolon; no empty declaration!
MIN_TYPE(double)
MIN_TYPE(uint64_t)

void write_minima(int i1, int i2, double d1, double d2, uint64_t u1, uint64_t u2)
{
    printf("Minima: (%d, %g, %" PRIu64 ")\n",
           min_int(i1, i2), min_double(d1, d2), min_uint64_t(u1, u2));
}

这不会阻止您尝试在单个 TU(翻译单元)中两次实例化相同的函数,但如果您这样做,编译器会反对。坦率地说,让编译器抱怨比试图让 C 预处理器发现你搞砸的地方并纠正你的错误更简单。

如果 C 代码将由另一个程序生成,那么您需要确保代码生成器跟踪它所请求的内容,并确保它不会重复生成代码。

【讨论】:

  • 如果dumb.h 只被一个.c 文件直接使用,那很好(OP 中的解决方案也是如此)。但问题是某些特定的MIN_TYPE(int) 可能会被头文件到处使用。在这种情况下,如果没有不合理的标头之间的协调,就不可能解决“双重包含”问题,即使这样,当您包含两个都想要MIN_TYPE 的标头时,它也会中断。
  • 我假设函数是static inline。这意味着 if 它们被调用,那么它们是文件的本地函数,并且希望这些函数实际上甚至不存在(它们将被内联)。有一个“那是作弊”的元素,但在某些情况下它会起作用。 C 编译器无法发现您在不同的 TU 中创建了相同的外部函数名称;它一次只能在一个 TU 上工作,直到将它们链接在一起,然后链接器会抱怨。如果您有一组太大而无法内联且必须实例化的函数,[...continued...]
  • [...continuation...] 如果您有一组太大而无法内联且必须实例化的函数,则应使用标头来声明它们。重复声明不是问题。而且您有一个或多个实现文件来定义实际需要的函数,使用与我所展示的相关的某种机制(除了定义不是static inline)。正如我在主要答案中所说,感觉就像您正在尝试使用预处理器来阻止实际上是程序员错误的问题,应该由编译器诊断并由程序员修复。
  • 我认为你误解了(或者我误解了)。我不是在谈论在不同的 TU 中重复声明相同的 static inline 函数的问题(这是常见的并且保证可以正常工作)。我是说你最终会在 same TU 中重新声明相同的函数。你在你的问题中解决它:这不会阻止你尝试在一个 TU 中两次实例化相同的函数。 - 这意味着你可以简单地“不这样做”。问题是两个独立的头文件都想使用相同的MIN_TYPE(int) 需要MIN_TYPE(int) 行来声明函数。
  • 好的;我知道你现在在哪里开车。传统的 C 回避在头文件中定义(而不是声明)函数的想法。这种……好吧,我会称它为一团糟,但这可能是过度的贬义;如果您只在标头中声明函数,则可以完全避免这种混乱。在这一点上,我需要确信能够定义相同功能的独立标头是一个好主意。但是,我可以看到,一旦您开始在标头中嵌入函数定义的路线,您可能会开始遇到您概述的问题。