【发布时间】:2016-04-13 09:52:45
【问题描述】:
包含保护,定义为here,用于防止在编译时两次加载相同的代码。
为什么我的编译器 (GCC) 无法检测到它两次加载相同的代码并具有合理的默认行为?
【问题讨论】:
-
有些人会故意将同一个文件包含两次(其中第二次包含有实际效果)。这行得通,这是有效的,所以改变它会破坏现有的代码。
标签: c++ c gcc compiler-construction preprocessor
包含保护,定义为here,用于防止在编译时两次加载相同的代码。
为什么我的编译器 (GCC) 无法检测到它两次加载相同的代码并具有合理的默认行为?
【问题讨论】:
标签: c++ c gcc compiler-construction preprocessor
仅仅是因为您可能希望编译器加载该文件两次。
请记住,#include 只是加载一个文件并将其内容放在指令的位置。该文件可能是头文件,但也可能是有用且经常使用的源代码。
大多数现代编译器都会对 #pragma once 做出反应,完全按照您的意愿行事。但请记住,这是一个未包含在语言规范中的编译器扩展,并且坚持包含保护通常是一个好主意 - 您可以肯定,它适用于每个编译器,并且在任何情况下都适用。
【讨论】:
#pragma once:它不是 100% 可靠的,所以你仍然需要包含警卫。
#pragma once 设为标准,并拒绝了它,因为它不(可靠)可实施。
为什么我的编译器 (GCC) 无法检测到它两次加载相同的代码
它可以(或者,学究式地,处理标头包含的预处理器可以)。您可以使用非标准但广泛支持的扩展,而不是使用包含守卫
#pragma once
表示此标头应仅包含一次。
并且有一个合理的默认行为?
默认情况下,该语言不指定此行为,主要是因为该语言可以追溯到跟踪包含的标头可能非常昂贵的时代,部分原因是有时您确实希望多次包含标头。例如,标准的 <assert.h> 标头可以在定义或不定义 NDEBUG 的情况下重新包含以更改 assert 宏的行为。
【讨论】:
#pragma once 是预处理器功能,而不是编译器功能。
因为在某些奇怪的极端情况下,重新包含文件很有用。
人为的丑陋示例:假设您有一个像这样的#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,这是实现与包含防护相同效果的更好方法。但是,不幸的是,它是非标准的。
【讨论】:
#defineA 和B 都包含标题?
include文件可能会产生不同的效果。
MINTYPE_H_int)。
为什么我的编译器 (GCC) 无法检测到它两次加载相同的代码并具有合理的默认行为?
因为不是编译器在进行包含处理。它是由本质上是一个文本转换引擎的预处理器完成的。对于文本转换引擎,如果在处理一段文本时多次出现相同的包含,则非常有意义。
让我们沉思一下:编译器不处理#includes。这就是编译器无法对符号重新定义做出明智决定的原因。
其他语言将模块实现为该语言的一部分,在这些语言中,不会将内容作为文本替换处理,编译器实际上了解导入语义。
【讨论】:
包含防护可防止符号重新定义并多次包含相同的文件。
编译器需要这种机制,因为显而易见的原因,它不包括分析和决定要考虑哪个代码版本的机制。想想如果相同的函数签名在两个不同的头文件中只是返回类型不同会发生什么。
假设内容完全相同,只是它包含在多个标头中,编译器将需要额外的计算能力和内存来跟踪它已经包含的代码。
因此它容易出错且效率低下
【讨论】:
为什么我的编译器 (GCC) 无法检测到它两次加载相同的代码并具有合理的默认行为?
因为那样它就不是 C 编译器了。指定语言使得#include 创建文本嵌入,并且执行与规范不同的操作会破坏有效代码。
明显的后续问题,“我们可以更改 C 标准吗?”仍然必须找到一些方法来避免对现有有效代码的同样破坏。
编译器可以合法地做的一件事是当一个非空的(在处理#ifdef等之后)文件被多次包含而没有一些时发出一个警告表明这是故意的。如果你有足够的动力,也许你可以为你最喜欢的编译器准备一个合适的补丁?
顺便说一句,一旦你必须对“相同代码”提出一个很好的稳健定义,你就会发现问题变得非常困难。
【讨论】:
即使编译器决定这样做,它也需要跟踪大量文件,而且很多时候(正如 itwasntpete 所评论的),编译器无法区分实际代码和头文件。
【讨论】: