C中双头包含的实际用例是什么?

时间:2017-01-16 17:13:36

标签: c c-preprocessor include-guards

C 标题文件中的包含保护(以及#pragma once)经常被使用并在wikipedia article中有详细描述,并且有一种方法可以防止错误地双重包含标题文件。

这个问题询问在 C 编程中,在所有情况下,最终需要进行双重包含?

更新 这并不是说我先验假设有一个很好的理由(某种预处理器魔法)值得知道要求每个程序员生成包括警卫的开销的负面影响。似乎奇怪的是,预处理器的行为非常不方便,我必须有一个合理的理由吗?问题是出于这样的原因,即双重包含的有用性

更新2 作为对这个问题的解释,我会假设以下问题:

双重包含问题(由包含警卫& #pragma once解决)是否只是一个预处理器缺陷,它会导致各种历史问题?

4 个答案:

答案 0 :(得分:2)

没有不完美。找到包含文件的次数与#include语句的次数是它应该做的。

检查标准的第7.2章:

  

根据assert的当前状态重新定义NDEBUG宏   每次包含assert.h

这意味着您可以像这样使用它:

#define NDEBUG
#include <assert.h>
assert( <expr1> );

#undef NDEBUG
#include <assert.h>
assert( <expr2> );

这是否非常有用可能是另一回事。

您还可以使用标题在标题中定义类似于X-Macros的内容。

header.h

  ELEMENT(x, _a_, _b_)
  ELEMENT(y, _a_, _b_)
  ELEMENT(z, _a_, _b_)

由source.c

#define ELEMENT(_x_, _a_, _b_) printf("%s: %d, %d", _x_, _a_, _b_);
  #include <header.h>

#define ELEMENT(_x_, _a_, _b_) {_x_, _a_, _b_},
struct something xy[] = {
  #include <header.h>
};

可能存在无法通过X-Macros解决的情况

答案 1 :(得分:1)

#include指令不仅限于标题。有一种不常见的做法,在源代码中包含准备好的可执行代码块。

例如:

max = calculateMax(a, b, c);
#include "debug-helper.c"; // NOTE: It may be cleaner to organize helper into functions
...

这将无法正确(即在所有情况下)使用隐式和强制性的标题保护。

答案 2 :(得分:0)

  

双重包含问题(通过包含guards&amp; #pragma一次解决)只是一个预处理器缺陷,它会导致各种历史问题吗?

没有双重包含问题;相反,存在多重定义问题。虽然它取决于标题的内容,但是如果没有多包含保护,则多次包含相同标题以实现不允许的一个或多个标识符的多重定义将是相对常见的。使用多包含保护的惯例是为了防止这种情况而开发的,即使对于实际上不存在多重包含问题的标题,它也已成为常规做法。

函数和对象不应该在头文件中定义,尽管在头文件中声明这些是头文件的主要用例之一。避免问题的这一方面相当容易。然而,过去的情况是,即使使用相同的定义,重新声明相同的typedef名称在技术上也是错误的,并且一些预标准化编译器甚至拒绝相同的宏重新定义。这些都不是现代C(C2011)中的问题。

仍然存在的问题是structunionenum类型的重新定义 - 即使重新定义没有任何改变,这仍然是不允许的。其次,头文件的内容的含义依赖于可能在翻译单元中的其他地方定义的符号并不罕见。如果在TU中不止一次包含这样的标题内容,则它们可能对两个包含具有不同的含义。这可能导致不兼容的多个声明。

要理解的一点是,给定标题的内容的多重包含是否错误是这些内容的函数。

此外,我已经描述了条件编译如何使标题的含义根据它相对于翻译单元其余部分的包含位置而有所不同。虽然这有助于产生不允许的不兼容声明或多个定义,但它也可以生成完全有效的代码。

TL; DR :多次包含头文件本身并不是错误的,并且可以起到有用的作用。因此,C预处理器不会自动防止多重包含,这不是一个缺陷。当该标题不止一次包含在同一个翻译单元中时,依赖每个标题的内容来展示合适的行为是合理的。

答案 3 :(得分:0)

我使用多个包含相同文件的地方就像一个美化的超大尺寸宏。

#define ARG1 "First arg"
#define ARG2 345
#include "run.macro"

“run.macro”文件在完成后将取消定义ARG1和ARG2,因此可以在整个地方使用它。

我已经使用这个技巧将一个庞大的宏系统重构为更容易管理的东西(并且评论得更好),但从长远来看,我会继续重构它直到它被彻底删除。

简而言之,它可用于重构,但我不建议将其作为一般做法。