C/C++代码中的预处理指令#ifndef

17

在Eclipse中,每当我创建一个新的C++类或者C头文件时,我会得到如下所示的结构。比如我创建名为example.h的头文件,我将得到以下内容:

/*Comments*/
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
/* Place to put all of my definitions etc. */
#endif

我认为#ifndef是在说如果EXAMPLE_H_没有被定义,那么就定义它,这取决于你用来编译和链接项目的工具可能会很有用。不过我有两个问题:

  1. 这种做法常见吗?我并不经常看到它。使用这种准则是否是一个好主意?或者你应该直接开始定义你的代码。

  2. EXAMPLE_H_到底是什么?为什么不是example.h,或者只是example?这有什么特别之处,还是只是eclipse首选自动构建项目的结果?


请注意,无论您作为应用程序员遇到什么情况,所有内容都必须放在#ifndef和#endif之间。因此,不仅仅是#define被定义并包含一次,而且所有内容只被定义和包含一次。 - dash-tom-bang
好的,非常抱歉没有表达得更清楚,谢谢。 - Leif Andersen
https://en.wikipedia.org/wiki/Include_guard - sancho.s ReinstateMonicaCellio
7个回答

16

这是一种常见的结构。其目的是只在翻译单元中包含头文件的内容一次,即使物理头文件被多次包含。例如,如果您直接在源文件中包含头文件并且它也通过另一个头文件间接包含,则会发生这种情况。

在内容周围放置#ifndef包装器意味着编译器仅解析头文件的内容一次,并避免重新定义错误。

有些编译器允许使用“#pragma once”完成相同的操作,但#ifndef结构在任何地方都起作用。


啊,好的,所以#define语句只是用来定义某些东西的,我可以在两种情况下将EXAMPLE_H_替换为FOO_BAR_,它仍然可以工作,但最好使用EXAMPLE_H_,因为它比任何其他名称更不可能发生命名空间冲突,对吗? - Leif Andersen
通常情况下,人们会使用带有下划线的头文件名称来代替标识符中无效的字符。使用可能会冲突的常见名称会破坏其目的 :) - user308405
@Leif Andersen:像 example 这样的常见名称很可能会发生冲突。个人建议在文件名前加上完整的命名空间,这样冲突的可能性较小。我也见过其他人生成完整的 GUID 来避免冲突的可能性。 - Martin York

4

这是常见的做法吗?是的 - 所有 C 和 C++ 头文件都应该按照这种结构来组织。EXAMPLE_H 是一个头文件保护,它防止头文件中的代码在同一翻译单元中被包含多次,否则会导致多重定义错误。选择 EXAPMLE_H 作为名称是为了与所保护的头文件的名称匹配 - 它需要在您的项目中以及全局范围内是唯一的。为了确保这一点,通常会在其前缀或后缀中加上项目名称:

#define MYPROJ_EXAMPLE_H

例如,如果你的项目叫做 "myproj"。不要被诱惑认为在前面加下划线会使它变得独一无二,因为像 _EXAMPLE_H___EXAMPLE_H__ 这样的名称是非法的,因为它们被保留给语言实现使用。

4

这只是一种常见的保护包含文件的方式 - 这样可以防止代码被重复包含。使用的标识符可以是任何东西,按照约定俗成的方式进行即可。


2
这是一个包含保护。它可以确保头文件只被包含一次。
例如,如果您这样做:
#include "example.h"
#include "example.h"

第一次包含头文件时,EXAMPLE_H_未被定义,if块将被执行。然后通过#define指令定义EXAMPLE_H_,并评估头文件的内容。
第二次包含头文件时,EXAMPLE_H_已经定义,因此if块不会再次执行。
这是确保您不违反一个定义规则的重要措施。如果在没有包含保护的头文件中定义类并将该头文件包含两次,则会由于违反一个定义规则而导致编译错误(类将被定义两次)。
虽然上面的示例很简单,您可以轻松地看到两次包含了example.h,但通常头文件包含其他头文件,并且这并不那么明显。

不是这样的!它保证只被包含不超过一次 :-) - Vicky

2

请考虑以下内容

文件foo.c:

#include foo.h
#include bar.h

文件 bar.h

#include <iostream>
#include foo.h

现在,当我们编译foo.c时,我们把foo.h包含进去两次!这绝对是不想要的,因为所有的函数第二次编译时都会抛出编译错误。
为了防止这种情况,我们将“INCLUDE GUARD”放在文件顶部。这样,如果已经被包含过了,我们就定义一个预处理器变量来告诉我们不要再次包含它。
这非常普遍(通常是必需的),如果有人没有加入头文件保护,那么就非常令人沮丧。当你包含一个.h文件时,你应该期望它有一个头文件保护。当然,你知道如果你做出了一些假设的话会出现什么问题(“让你和我看起来像个傻瓜”),但这应该是你期望看到的东西。

2

在头文件的顶部始终要这样做。它通常被称为头文件保护或包含保护。

它的作用是使得如果一个头文件会被多次包含,它只会被包含一次。如果你不这样做,那么你最终会遇到有关重复定义等错误。

确切的“#define”并不那么重要,尽管通常它与文件名有关。基本上,你正在检查给定的宏是否已被定义。如果没有,则定义它,并继续包含该文件。如果已经定义了,则必须之前已经包含了该文件,并且文件的其余部分将被忽略。


0

这被称为包含保护,是C/C++头文件的常见习语。这允许多次包含头文件而不重复包含其内容。

名称EXAMPLE_H_是任意约定,但必须遵守C预处理器宏的命名规则,这排除了像example.h这样的名称。由于C宏都定义在单个全局命名空间中,因此重要的是您不要有不同的头文件使用相同的包含保护名称。因此,通常最好在包含保护名称中包含您的项目或库的名称:

#ifndef __MYPROJECT_EXAMPLE_H__
...

3
除下划线后紧接着大写字母或另一个下划线的名称被保留给实现(即不要以下划线开头的名称作为你的包含保护符名称)。 - James McNellis
@James 和任何包含连续两个下划线的标识符(即不仅仅在开头)都是违规的。换句话说,这个答案充满了违规行为。 ;) - dash-tom-bang

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接