C语言,include guards的正确使用方法

3

我想创建一个头文件(其中包含我为AVL树编写的函数),但是我遇到了一些关于包含保护语法的问题和误解。

现在我的代码看起来像这样:

#ifndef STDIO_H
#define STDIO_H
#endif
#ifndef STDLIB_H
#define STDLIB_H
#endif
#ifndef CONIO_H
#define CONIO_H
#endif

问题是,我认为它仅包括<stdio.h>。当我尝试使用malloc时,它会说未定义malloc,即使我已经包含了stdlib。
根据http://www.cprogramming.com/reference/preprocessor/ifndef.html的说法,如果我理解正确,ifndef检查标记是否已定义,如果没有定义,则定义#ifndef之后直到#endif之间的所有内容。所以我的代码应该可以工作。
stdio被定义了吗?没有。那就定义它。endif。stdlib被定义了吗?没有。那就定义它。endif。conio被定义了吗?没有。那就定义它。endif。我看不出问题出在哪里。
如果我想添加这3个头文件,正确的语法是什么?

4
你的代码没有包含任何内容。你是否认为可以使用包含保护代替包含?请注意,这两个是不同的概念。 - Wojtek Surowka
1
你不应该为标准头文件提供包含保护。你提供它们来围栏你的头文件。而且你没有 #include 任何东西,所以我只能想象你得到了多少个“未声明函数 xxxx 返回 int”警告。 - WhozCraig
1
@OriaGruber 不需要,因为标准头文件已经有它们自己的包含保护(而且确实有)。 - WhozCraig
那么在我编写的任何头文件中,我总是可以安全地包含 stdio.h、stdlib.h 和 conio.h 吗? - Oria Gruber
@OriaGruber 是的,你可以这样做。请注意,conio.h 不是标准库之一,而其他两个是标准库。 - Wojtek Surowka
显示剩余2条评论
5个回答

7

使用包含保护指令可以防止在头文件被多次包含时出现重复定义的问题。

标准的头文件已经有了必要的包含保护指令,因此你不需要为它们添加额外的包含保护指令。

你的代码应该是:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

1
所以我始终可以安全地在我编写的任何头文件中包含 stdio、stdlib 和 conio 吗?假设我编写了一个包含所有上述内容的 stack.h。现在我正在编写包含 stack 的 avltree.h。我是否也可以再次包含 stdio、stdlib 和 conio?我可以在同样包含 stack 或 avltree 的 .c 文件中包含它们吗? - Oria Gruber
1
是的!多次包含它们是安全的。 - Klas Lindbäck
@OriaGruber 不仅可以,而且你应该在没有依赖于 stack.h 自动引入它们的前提下再次包含它们。如果你需要头文件的服务,那么就引入头文件。如果头文件已经正确保护自己,这不会成为问题。而且,永远不要编写一个依赖于包含它的翻译单元之前已经包含其他内容的头文件。这同样是问题丰富和设计不良的标志。 - WhozCraig

2
以下声明将不包含您的头文件。
#ifndef STDIO_H
#define STDIO_H
#endif

如果你这样声明,不代表它会包括你的stdio.h头文件。这是“自有头文件”最合适的方法。
你需要将所有的声明和函数定义都像下面这样放在你自己的头文件(avltree.h)中 -
#ifndef AVLTREE_H
#define AVLTREE_H

/* YOUR HEADER FILE STUFF */

#endif

然后在主程序中包含该头文件。

stdio.hstdlib.hconio.h 是已经可用的头文件,您可以直接在主程序文件中包含所有文件-

#include<stdio.h>
#include<stdlib.h>
#include<conio.h>

1

你需要在自己的头文件中添加include guards。

例如:

//GameEntity.hpp
#ifndef __H_GAME_ENTITY
#define __H_GAME_ENTITY

class GameEntity{
  //whatever
};

#endif

那么它只会被编译单元包含一次。

现在类似这样的内容将不会失败:

#include <GameEntity.hpp>
#include <GameEntity.hpp>

int main(){ return 0; };

0

添加这三个标头的正确语法是:

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#ifndef是一个预处理器指令,后面跟着一个标记,假设它是X。它将检查是否已通过#define预处理器指令定义了X,如果没有,则编译器将处理随后的行,直到相应的#endif。例如:

// #define X
// #define X 123456

#ifndef X
/* some code */
#endif

上面的东西可以理解为如果X未定义。只有在X未定义的情况下,才会将/* some code */部分视为有效,并且在这种情况下,X是未定义的,所以它会生效。如果我取消注释上面的任何一个#define ...,编译器将忽略/* some code */部分。

包含保护是利用我上面解释的#ifndef的东西。在你自己制作头文件之前,你不必担心这样的事情。

头文件通常(通常)在自身中包含#include guard。无论它们做什么,它们首先检查某个特定的标记是否已被定义。如果没有定义,则它们将自己定义该标记并执行相应操作。如果已经定义了,那么它们什么都不做。这是为了防止不需要的重复定义。例如,如果你查看MSVC 2013的<stdio.h>,你会看到它的开头和结尾如下:

#ifndef _INC_STDIO
#define _INC_STDIO

// hundreds of lines in between

#endif

多亏了这个,如果您要写类似这样的内容:

#include <stdio.h>
#include <stdio.h>

// ...

在你的代码中,第二个#include几乎没有任何作用,因为第一个已经执行了#define _INC_STDIO这一行,它定义了_INC_STDIO并防止在后续的包含中再次执行<stdio.h>中的几乎所有内容。

这不是为了防止程序员犯下“愚蠢”的错误,而是在头文件包含另一个头文件时非常有用。例如,在MSVC 2013中,<stdio.h><stdlib.h>都尝试将<crtdefs.h>作为它们的第一个操作进行包含。现在,如果<crtdefs.h>被包含两次,里面的一堆类型定义就会被重复定义,这是不应该的。当然,我可以在我的代码顶部明智地编写以下内容:

#include <stdio.h>
#include <stdlib.h>

// ...

而且,#include守卫可以为我节省一天时间,防止<crtdefs.h>的内容被多次执行。


-1

正如之前的评论和大家指出的那样,您的主头文件中没有包含任何内容。

即使是我也准备了单个头文件,并且我更喜欢按照以下方式进行:

假设您有一个名为“includes.h”的主头文件,其中包含所有头文件。然后

#ifndef INCLUDES_H
#define INCLUDES_H

#ifndef STDIO_H
     #include <stdio.h>
#endif

#ifndef STDLIB_H
     #include <stdlib.h>
#endif

#ifndef CONIO_H
     #include <conio.h>
#endif

//If you have any of your own header files, then include them the same way
#ifndef USER_AVL_HEADER_FILE
     #include <user_avl_header_file.h>
#endif    

#endif   // INCLUDES_H

然后,就像你有你的头文件名“user_avl_header_file.h”一样,在该头文件中再次放置头保护:

#include "includes.h"

#ifndef USER_AVL_HEADER_FILE
#define USER_AVL_HEADER_FILE

/*your class and your code*/

#endif    //USER_AVL_HEADER_FILE

在您的源代码文件中,只需包含主头文件 "includes.h",无需担心任何问题。

1
抱歉,但我必须投反对票。在使用 #include 预处理器指令时,您绝对不应该担心 include-guard 标记是否已定义。被包含的头文件本身应该关心特定标记是否已定义。您在这里提出的建议只是一种混乱。 - Utkan Gezer
我知道在使用 #include 预处理器时,检查 include-guard 标记是否已定义并不重要,但这是我的组织的“编码风格”。很遗憾,我现在已经习惯了这种方式,这就是为什么我已经表明了我喜欢的方式。当再次包含“includes.h”时,编译器不会再次进入文件,因为 #INCLUDES_H 已经定义。 - Arpit

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