何时使用包括警卫?

时间:2017-08-05 22:15:56

标签: c

我知道在头文件中使用包含警戒是为了防止某些内容被定义两次。但是,使用此代码示例完全没问题:

foo.c的

#include <stdio.h>
#include <string.h>
#include "bar.h"

int main() {
    printf("%d", strlen("Test String"));
    somefunc("Some test string...");
    return 0;
}

bar.h

#ifndef BAR_H_INCLUDED
#define BAR_H_INCLUDED
void somefunc(char str[]);
#endif

bar.c

#include <stdio.h>
#include <string.h>
#include "bar.h"

void somefunc(char str[]) {
    printf("Some string length function: %d", strlen(str));
}

以上代码段使用gcc -Wall foo.c bar.c -o foo进行编译,没有错误。但是,<stdio.h><string.h>都包括在内而没有包含警戒。当我将bar.h剥离到单个语句void somefunc(char str[]);时,仍然没有错误。为什么没有错误?

4 个答案:

答案 0 :(得分:5)

首先,包含警卫的主要目的是防止某些内容在同一个翻译单元中被声明两次 。 “在同一个翻译单元”部分是这里的关键。您使用两个不同的翻译单元进行的实验与包含警卫的目的无关。它没有展示任何东西。它甚至没有远程相关。

为了利用包含保护,您必须(明确地或隐含地)将相同的头文件包含两次到一个实现文件中。

其次,仅仅因为某些头文件没有包含防护,并且因为您将该头文件两次包含在同一个翻译单元中并不意味着它必然会触发错误。为了导致错误,标题必须包含特定“不可重复”类型的声明。没有每个标题包含这样的违规声明。从这个意义上说,并不是每一个宣言都是冒犯的。

您的bar.h(已发布)实际上是无害的。在形式上,您bar.h中不需要包含警戒。它有一个函数声明,可以在一个翻译单元中重复多次。因此,多次包含此标题不会导致错误。

但是在bar.h

中添加类似内容
struct SomeStruct
{
  int i;
};

然后只在同一个实现文件中包含它两次,最终会出错。此错误包括警卫旨在防止的内容。该语言禁止在同一翻译单元中重复相同结构类型的完整声明。

包含警卫通常无条件地放在头文件中。我很确定,它们也出现在<stdio.h><string.h>内。目前还不清楚为什么你声称这些标题“包含在没有包含警戒的情况下”。你检查过这些文件了吗?在任何情况下,再次,您使用两个不同的翻译单元进行的实验无论如何都不会表现出任何相关性。

答案 1 :(得分:3)

重复声明不是问题;重复(类型)定义是。如果包含bar.h标头,例如:

enum FooBar { FOO, BAR, BAZ, QUX };

然后在单个TU中包含两次(翻译单元 - 源文件加上包含的标题)一般会出错。

此外,多重包含方案不是您展示的内容。假设头文件中没有标题保护,可能会导致以下问题:

  • bar.h

    enum FooBar { FOO, BAR, BAZ, QUX };
    void somefunc(char str[]);
    
  • quack.h

    #include "bar.h"
    extern enum FooBar foobar_translate(const char *str);
    
  • main.c

    #include "bar.h"
    #include "quack.h"
    
    …
    

请注意,GCC有一个选项-Wredundant-decls来标识冗余声明 - 其中相同的声明存在多次(通常来自多个文件,但如果同一声明在单个文件中出现两次)。

在C11之前,您无法重复typedef(在文件范围内;您始终可以在块范围内隐藏外部typedef)。 C11放宽了这个约束:

  

§6.7声明

     

¶3如果标识符没有链接,则标识符的声明不得超过一个   (在声明符或类型说明符中)具有相同的作用域和相同的名称空间,除外   的是:

     
      
  • 可以重新定义typedef名称以表示与其当前相同的类型,   只要该类型不是可变修改类型;
  •   
  • 标签可以按照6.7.2.3中的规定重新申报。
  •   

但是,您仍然无法在TU的单个范围内定义两次结构类型,因此符号如下:

typedef struct StructTag { … } StructTag;
必须使用标头保护装置保护

。如果使用不透明(不完整)类型,则不会出现此问题:

typedef struct StructTag StructTag;

至于为什么你可以包含标准标题,那是因为标准要求你可以:

  

§7.1.2标准标题

     

¶4标准标题可以包含在任何顺序中;每个可以包含多次   一个给定的范围,没有任何效果不同于只包括一次,除了   包含<assert.h>的效果取决于NDEBUG的定义(见7.2)。如果   使用时,标题应包含在任何外部声明或定义之外,并且它   应首先包括在第一次提及任何功能或对象之前   声明,或声明它定义的任何类型或宏。但是,如果声明了标识符   或者在多个标题中定义,第二个和后续的相关标题可以是   包含在初始引用标识符之后。该计划不得有任何   名称与当前在包含之前定义的关键字词汇相同的宏   标题的扩展或标题中定义的任何宏的扩展。

使用标题保护可以使标题符合标准标题符合的标准。

另见一般主题的其他dia骂(答案),包括:

答案 2 :(得分:1)

您的代码中没有错误的原因是您的头文件已声明但未定义somefunc()。对事物的多个声明是好的,只要它们不是定义 - 编译器可以接受多次声明的事情(当然,只要声明是兼容的)。

一般来说,需要使用保护来避免头文件之间的循环依赖,例如

  • 标题A和标题B在某些情况下相互包含。至少有一个头部需要包含保护,以防止预处理器中出现无限循环。
  • 标题A包括标题B,因为它取决于其中的定义(例如内联函数,typedef),但其他标题文件或编译单元可能包括标题A,标题B或两者的不同情况。需要包含防护以防止包含这两个标头的编译单元中的多个定义。包含定义的头文件至少需要有一个包含保护。

由于头文件可以按任何顺序包含彼此,因此上述问题可能会变得非常复杂。

防止某些类型的多重定义是上述的副作用,但不是包括警卫的主要目的。

尽管如此,我使用头文件处理的经验法则是&#34;在所有头文件中使用包含警戒,除非我有特殊的理由不去&#34;。通过这样做,避免了与不提供包括防护相关的所有潜在问题。在实践中,必须避免包含保护的情况(例如,声明或定义不同事物的标题,取决于在编译单元中定义/未定义的宏)。而且,如果您使用的是需要此类操作的技术,您应该已经知道不应该在受影响的标题中使用包含保护。

答案 3 :(得分:0)

为什么呢?这是因为:

 <a href="@Ajax.ActionLink("Detail", "Skies", new {id = s.Id }, new AjaxOptions() { InsertionMode = InsertionMode.Replace, UpdateTargetId = "modalContent", OnBegin = "openModalWindow" } )">Detail</a>

函数原型不会错误它们是相同的和函数