如果我有多个标头文件:请说1.h
,2.h
,3.h
。
假设所有三个头文件都包含#include <stdlib.h>
,其中包含一个包含文件。
当我必须在C文件main.c
中使用所有3个头文件时,
它将在预处理器之后有3个#include <stdlib.h>
副本。
编译器如何处理这种冲突?
这是一个错误还是会产生任何开销?
如果没有标题保护,会发生什么?
答案 0 :(得分:7)
大多数C标头包含如下:
#ifndef FOO_H
#define FOO_H
/* Header contents here */
#endif
预处理器第一次扫描时,它将包含标头的内容,因为FOO_H
未定义;但是,它还定义了FOO_H
,以防止第二次添加标题内容。
多次包含标头会对性能产生很小的影响:预处理器每次都必须转到磁盘并读取标头。这可以通过在C文件中添加保护来减轻:
#ifndef FOO_H
#include <foo.h>
#endif
Large-Scale C++ Software Design(一本优秀的书)中详细讨论了这些内容。
答案 1 :(得分:5)
这通常通过预处理程序语句解决:
#ifndef __STDLIB_H
#include <stdlib.h>
#define __STDLIB_H
#endif
虽然我从未在stdlib.h
之类的常见头文件中看到过它,但它可能只是您自己的头文件所必需的。
答案 2 :(得分:5)
预处理器将包含所有三个副本,但header guards将阻止解析除第一个副本之外的所有副本。
标题保护将告诉预处理器将该头文件的后续副本转换为实际上没有任何内容。
回复编辑:
标准库标题将具有标题保护。他们没有守卫是非常不寻常和不正确的。
同样,您有责任在自己的标题上使用标题保护。
如果缺少标题保护,假设您会收到与重复定义相关的各种错误。
答案 3 :(得分:3)
另一点:你可以重新声明一个函数(或外部变量)数十亿次,编译器会接受它:
int printf(const char*, ...);
int printf(const char*, ...);
完全合法,编译开销很小,但没有运行时开销。
当无人看守的包含文件被包含多次时会发生这种情况。
请注意,包含文件中的所有内容都不是这样。例如,您无法重新声明枚举。
答案 4 :(得分:2)
这是通过两种流行技术中的一种来完成的,这两种技术都受到stdlib的责任。
一个是定义一个唯一常量并检查它,如果文件已经定义,则#ifdef出文件的所有内容。
另一个是特定于微软的#pragma once
,如果已经包含它(通过记住确切的路径),其优点是不必从硬盘驱动器中读取它(
必须也会在您生成的所有头文件中执行相同操作。或者,包含您的标题会有问题。
答案 5 :(得分:2)
据我所知,常规包括简单地抛出另一个文件的内容。标准库stdlib.h仅使用代码保护:http://en.wikipedia.org/wiki/Include_guard,因此最终只包含一个副本。但是,你可以打破它(尝试一下!)如果你这样做:#include A,#undef A_GUARD,#include A再次。
现在......为什么你把.h包含在另一个.h里面?这可以,至少在C ++中,但最好避免。您可以使用前向声明:http://en.wikipedia.org/wiki/Forward_declaration
只要代码不需要知道标题中导入结构的大小,就可以使用这些工作。您可能希望通过引用/指针将一些函数参数转换为值来解决此问题。
另外,请始终将include guard或#pragma一次用于您自己的头文件!
答案 6 :(得分:1)
正如其他人所说,对于标准库头,系统必须确保包含多次的头的效果与包含一次的头相同(它们必须是幂等的)。该规则的一个例外是assert.h
,其效果可能会根据是否定义NDEBUG
而改变。引用C标准:
标准标题可以包含在任何顺序中;每个可以包含多次 一个给定的范围,没有任何效果不同于只包括一次,除了 包含
<assert.h>
的效果取决于NDEBUG
的定义。
如何完成这取决于编译器/库。编译器系统可能知道所有标准头的名称,因此不会再次处理它们(如上所述除assert.h
之外)。或者,标准标头可能包含特定于编译器的魔术(主要是#pragma
语句)或“include guards”。
但是,不止一次包含任何其他标题的效果不一定相同,然后由标题编写者决定是否存在冲突。
例如,给定标题:
int a;
包括它两次将导致a
的两个定义。这是一件坏事。
避免这种冲突的最简单方法是使用上面定义的包含保护:
#ifndef H_HEADER_NAME_
#define H_HEADER_NAME_
/* header contents */
#endif
这适用于所有编译器,不依赖于编译器特定的#pragma
。 (即使如上所述,在头文件中定义变量也是一个坏主意。)
当然,在您的代码中,您应该确保include guard的宏名称满足:
E
后跟大写字符PRI
开头,后跟小写字符或X
,LC_
后跟大写字符SIG
/ SIG_
开头,后跟大写字符, ...等。 (这就是我更喜欢H_NAME_
形式的原因。)
作为一个反常的例子,如果你想让你的用户猜测某些缓冲区大小,你可以有一个像这样的标题(警告:不要这样做,它应该是一个笑话)。
#ifndef SZ
#define SZ 1024
#else
#if SZ == 1024
#undef SZ
#define SZ 128
#else
#error "You can include me no more than two times!"
#endif
#endif