包含防护,如此处所定义,用于防止在编译时加载相同的代码两次。
为什么我的编译器 (GCC) 无法检测到它正在加载相同的代码两次并具有合理的默认行为?
仅仅因为您可能希望编译器加载该文件两次。
请记住,
#include
只是加载一个文件并将其内容放在指令的位置。该文件可能是头文件,但也可能是有用且经常使用的源代码。
大多数现代编译器都会对
#pragma once
做出反应,完全按照您的意愿行事。但请记住,这是一个未包含在语言规范中的编译器扩展,并且坚持包含防护通常是一个好主意 - 您可以确定它适用于每个编译器和任何情况。
为什么我的编译器(GCC)无法检测到它正在加载相同的代码两次
它可以(或者,迂腐地说,处理标头包含的预处理器可以)。您可以使用非标准但广泛支持的扩展,而不是使用包含防护
#pragma once
表明此标头只能包含一次。
并且有一个合理的默认行为?该语言默认情况下不会指定此行为,主要是因为该语言可以追溯到跟踪包含的标头可能非常昂贵的时代,部分原因是有时您确实希望多次包含标头。例如,无论是否定义了
<assert.h>
,都可以重新包含标准
NDEBUG
标头,以更改
assert
宏的行为。
人为丑陋的例子:假设你有一个像这样的
#include
文件
mymin.h
:
// mymin.h : ugly "pseudo-template" hack
MINTYPE min(MINTYPE a, MINTYPE b)
{
return (a < b) ? a : b;
}
然后你可以做这样的事情:
#define MINTYPE int
#include "mymin.h"
#define MINTYPE double
#include "mymin.h"
现在,您有两个针对不同类型的
min
重载,并且是http://thedailywtf.com/ 的良好候选者。 谁需要模板? ;-) 请注意,许多现代预处理器支持
#pragma once
,这是实现与包含防护相同效果的更好方法。然而,不幸的是它是非标准的。
为什么我的编译器 (GCC) 无法检测到它正在加载相同的代码两次并具有合理的默认行为?因为不是编译器在进行包含处理。它是由预处理器完成的,预处理器本质上是一个文本转换引擎。对于文本转换引擎来说,如果在处理一段文本时相同的包含出现多次,那就非常有意义了。
让我们先理解一下:编译器不会处理
#include
。这就是编译器无法对符号重定义做出明智决定的原因。其他语言将模块实现为语言的一部分,并且在这些语言中,事物不会被处理为文本替换,并且编译器实际上具有有关导入语义的知识。
编译器需要这种机制,因为出于明显的原因,它不包含分析和决定考虑哪个代码版本的机制。想象一下如果两个不同头文件中的相同函数签名仅返回类型不同会发生什么。
假设内容完全相同,只是包含在多个标头中,编译器将需要额外的计算能力和内存来跟踪已包含的代码。
所以它很容易出错并且效率低下
为什么我的编译器 (GCC) 无法检测到它正在加载相同的代码两次并具有合理的默认行为?因为那样它就不是一个 C 编译器了。 指定语言使得
#include
创建文本嵌入,并且执行与规范不同的操作会破坏有效代码。明显的后续问题,“我们可以改变 C 标准吗?”仍然必须找到某种方法来避免现有有效代码的相同破坏。
编译器
可以合法做的一件事是,当非空(在处理#ifdef
等之后)文件被多重包含时发出警告
,而没有任何迹象表明这是故意的。 如果您有足够的动力,也许您可以为您最喜欢的编译器准备合适的补丁?顺便说一句,一旦您必须提出“相同代码”的良好稳健定义,您就会发现问题变得非常困难。