知识点滴 - 关于头文件的重复包含问题

2023-05-16

使用Include guard的目的

C Language Tutorial => Header Include Guards

2.12 — Header guards – Learn C++

C/C++中,#include,这个预处理命令会将此命令后面的文件内容包含进来。

而一般情况下,后面是一个头文件, 比如name.h,因为一般都在源文件的开头来包含,所以叫头文件。

头文件里面的内容其实没有严格限制,可以包含变量或函数的声明或定义,还有类型的定义,宏定义等。

而对一个源文件,.c或.cpp文件,处理完预处理指令,将头文件包含进来并替换宏定义后,得到的就一个可编译单元 - translation unit (TU)。

对于这个可编译单元来讲,是可以包含很多个头文件,头文件里还可以包含其他头文件,所以才会有一个头文件会被间接的多次包含的问题。

对于函数声明来说,如果一个编译单元里出现多次相同的声明没有问题,但如果是一个编译单元里对同一个对象(变量)或类型有多个定义,或者是同一个作用域里有重复定义,比如全局空间有重复定义,就会编译报错。

在C/C++中,一个类型只能定义一次,比如对相同的结构体类型定义只能出现一次,不能重复。

所以在包含头文件时,里面的内容都是一些类型定义,就不能重复包含。这也包括一些宏定义,也不能重复定义。

重复包含一个头文件,可能会导致编译错误,就算头文件中都是可以重复出现的声明之类的内容,多次包含的话,也会增加预处理器或编译器的工作量,增加软件构建的时间。

而一般情况下, 头文件中包含较多的内容都是forward declaration,前向声明,表示还没有给出完整的定义的标识符(表示编程的实体,如数据类型、变量、函数)。有了这个声明,编译的源文件就可以使用此标识符,而不需要关系其定义与否。在链接阶段,才会使用到具体的定义。

尤其在很大的项目中,头文件很多,其内容很长时,这会降低开发效率,增加开发成本。

为了解决重复包含头文件的问题,引入了header guard(或者叫include guard),来防止头文件的多次包含。

header guard使用条件编译指令,一种预处理指令。

在一个源文件中包含此头文件my_include.h时,会检查此宏定义是否定义,如果是第一次包含,没有定义,则下一步定义这个宏,并将后面的内容包含进来。

然后预处理器对此源文件继续操作,后面再出现包含此头文件时,因为这个宏已经定义,则后面内容不会包含进来。

注意,这里宏生效的范围仅在单个源文件内。

例如:

my_include.h

#ifndef SOME_UNIQUE_NAME_HERE

#define SOME_UNIQUE_NAME_HERE

// your declarations (and certain types of definitions) here

#endif

#ifndef HEADER_1_H

#define HEADER_1_H

typedef struct {

    …

} MyStruct;

int myFunction(MyStruct *value);

#endif

#ifndef是预处理器指令,如果后面的宏未定义,则后面代码会被处理,否则略过这些代码。

而下面紧跟着就是#define预处理指令,定义一个宏,作为header guard,这样当前的编译单元再包含这个编译单元时,就会因为此宏已定义,而略过后面的代码。

另外,在Visual Studio Code中可以自动添加Include Guard:

C/C++ Include Guard - Visual Studio Marketplace

这个添加的include guard是一个GUID形式的定义:

// GUID

#define E8A33412_A210_4F05_99A4_F6E2019B7137

不适用Include guard的头文件

有几个头文件没有使用include guard的习惯。一个具体的例子是标准<assert.h>头。它可能在一个翻译单元中被多次包含,而这样做的效果取决于每次包含头文件时是否定义了宏NDEBUG。你可能偶尔会有类似的要求;这种情况很少。通常情况下,你的头文件应该受到include guard习惯用法的保护。

A few headers do not use the include guard idiom. One specific example is the standard <assert.h> header. It may be included multiple times in a single translation unit, and the effect of doing so depends on whether the macro NDEBUG is defined each time the header is included. You may occasionally have an analogous requirement; such cases will be few and far between. Ordinarily, your headers should be protected by the include guard idiom.

使用Include guard的注意事项

1,当创建头文件时,生成一次。

2,创建后无需再考虑

3,要防止重名,要比你中彩票的几率还小。

* generate once, when creating a header

* never have to think about again

* chance of duplicating is less than your chance of winning the lottery

关于include guard使用的宏的名字

c++ - Adding an include guard breaks the build - Stack Overflow

c++ - Naming Include Guards - Stack Overflow

Include guard conventions in C - Stack Overflow

宏定义一般都用大写字母, Include guard一般也使用大写字母,并且起名时尽可能防止出现不同文件会有相同定义的情况。

一般的Include guard的起名方式,个别方法以config.h文件为例:

1, CONFIG_H , _CONFIG_H , CONFIG__H, CONFIG_H__ , __CONFIG_H__ , _CONFIG_H_

2, PROJECT_CONFIG_H

3,  _PATH_CONFIG_H_ 或 <PROJECT>_<PATH>_<FILE>_H ,

4,  CONFIG_20201212_103820   

(使用时间后缀, <name>_<date>_<time> 或 <FILE>_<CREATION DATE>_H)

5,INCLUDE_GUARD_726F6B522BAA40A0B7F73C380AD37E6B

(使用UUID创建)

6,C++的boost库:

<project>_<path_part1>_..._<path_partN>_<file>_<extension>_INCLUDED

// include /pet/project/file.hpp

#ifndef PET_PROJECT_FILE_HPP_INCLUDED

7,Google's style guide:

<PROJECT>_<PATH>_<FILE>_H_ 

(the full path in a project's source tree)

foo/src/bar/baz.h

#ifndef FOO_BAR_BAZ_H_

#define FOO_BAR_BAZ_H_

...

#endif  // FOO_BAR_BAZ_H_

8,FILE>_<LARGE RANDOM NUMBER>_H

需要注意的是,下划线或双下划线开头的宏定义标识符,一般是保留给语言内部使用的,所以如果需要加下划线可以在结尾加。

总结:

建议的名字:  MOD_FULL_PATH_FILENAME_H_INCLUDED

MOD可以是模块名字或缩写,也可以是项目名。

另外,有时会在最后的#endif命令后加上注释,表示include guard的结束,最为一种良好的编程实践。

The C style comment after the #endif directive is not mandatory, but it is considered good style.

关于使用#pragma once

#pragma once 与 #ifndef 解析 - 瘦狐狸 - 博客园

What does #pragma once mean in C? - Stack Overflow

为了避免同一个文件被include多次,C/C++中有两种预处理指令使用方式,一种是#ifndef方式,一种是#pragma once方式。

方式一:

    #ifndef __SOMEFILE_H__

    #define __SOMEFILE_H__

    ... ... // 声明、定义语句

    #endif

    方式二:

    #pragma once

    ... ... // 声明、定义语句

#ifndef的方式受C/C++语言标准支持。它不光可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。

当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,编译器却硬说找不到声明的状况——这种情况有时非常让人抓狂。

由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

#pragma once一般由编译器提供保证:同一个文件不会被包含多次。但它不属于C/C++语言标准,注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件,而且当文件的引用是远程地址或不同硬盘上时,不保证会生效。你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。

其好处是,你不必再费劲想个宏名了,当然也就不会出现宏名碰撞引发的奇怪问题。大型项目的编译速度也因此提高了一些。

对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名碰撞引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。

#pragma once方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。一般而言,当程序员听到这样的话,都会选择#ifndef方式,为了努力使得自己的代码“存活”时间更久,通常宁愿降低一些编译性能。

在C和C++编程语言中,#pragma once是一个非标准但被广泛支持的预处理器指令,旨在使当前源文件在一次编译中只被包含一次。因此,#pragma once的作用与#include guards相同,但有几个优点,包括:代码少,避免名称冲突,提高编译速度。

In the C and C++ programming languages, #pragma once is a non-standard but widely supported preprocessor directive designed to cause the current source file to be included only once in a single compilation. Thus, #pragma once serves the same purpose as #include guards, but with several advantages, including: less code, avoiding name clashes, and improved compile speed.

关于使用双重包含开关的讨论

macros - The use of double include guards in C++ - Stack Overflow

比如在包含一个头文件时,同时使用一个和其内部一样的预处理宏,来防止多次包含:

#ifndef __HEADER_A_HPP__

#include "header_a.hpp"

#endif

添加另一个include guard是一个不好的做法这里有一些原因:

1,为了避免双重包含,只需在头文件本身中添加一个普通的包含防护。它能很好地完成这项工作。在包含的地方再加一个包含防护,只会把代码弄得一团糟,降低可读性。

2,它增加了不必要的依赖性。如果你在头文件中改变了include guard,你必须在所有包含头文件的地方改变它。

3,在整个编译/链接过程中,这绝对不是最昂贵的操作,所以它很难减少总的构建时间。

4,任何有价值的编译器都已经优化了文件范围内的包含保护。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

知识点滴 - 关于头文件的重复包含问题 的相关文章

  • ubuntu系统X86环境下配置TX2的ARM环境的交叉编译链

    总步骤 安装qtcreator的IDE安装aarch64 linux gnu g 43 43 交叉编译链下载Qt库的源码 xff0c 配置编译选项 xff0c 编译生成针对TX2的ARM环境的qmake工具配置qtcreator xff0c
  • ROS中Eigen库的引用

    在CmakeList txt中添加两个地方 find package Eigen REQUIRED include directories Eigen INCLUDE DIR 如果找不到Eigen xff0c 我们将第一句改成 find p
  • ROS tf使用

    1 静态tf发布 lt node pkg 61 34 tf 34 type 61 34 static transform publisher 34 name 61 34 link1 link2 broadcaster 34 args 61
  • SLAM算法配置——使用Realsense D435i结合ROS跑通ORB-SLAM2的RGB-D节点

    ORB SLAM2源地址 配置环境依赖 Pangolin xff0c OpenCV xff0c Eigen3 xff0c DBoW2 and g2o xff08 源代码里有 xff0c 不用自己装 xff09 xff0c ROS xff08
  • 代码编写及阅读规范

    阅读常识 1 C语言中在函数名或关键字前加下划线 一般情况是标识该函数或关键字是自己内部使用的 xff0c 与提供给外部的接口函数或关键字加以区分 规范 综述 C 43 43 是一门十分复杂并且威力强大的语言 xff0c 使用这门语言的时候
  • 流媒体开发之路

    其实很早之前 xff0c 就想写属于自己的博客 xff0c 大二就有了CSDN账号 xff0c 很讽刺的是 xff0c 工作几年了 xff0c 账号里面的内容竟然和小鲜肉脸一样干净 干净的让人尴尬 回顾自己的这几年的开发之路 xff0c 接
  • matlab图像处理实例详解---note

    1 直方图均衡及直方图规定化 可以优化图像的亮度及gamma效果 2 图像的标准差 当图像越清晰的时候 xff0c 图像的标准差就越大 是否可以用来做af的判定标准 作为fv的值 另外是否可以用图像的相关系数作为caf的一个trigger
  • promise限制并发请求数量

    所谓并发请求 xff0c 就是指在一个时间点多个请求同时执行 当并发的请求超过一定数量时 xff0c 会造成网络堵塞 xff0c 服务器压力大崩溃或者其他高并发问题 xff0c 此时需要限制并发请求的数量 假如等待请求接口1000个 xff
  • 一个跨平台的 C++ 内存泄漏检测器(转载)

    一个跨平台的 C 43 43 内存泄漏检测器 吴咏炜 adah 64 netstd com 2004 年 3 月 内存泄漏对于C C 43 43 程序员来说也可以算作是个永恒的话题了吧 在Windows下 xff0c MFC的一个很有用的功
  • printf和wprintf、printf输出结束标识符、c++按值返回临时对象是否是const的实验

    ifndef TEST H define TEST H include lt iostream gt include lt string gt using namespace std int x 61 5 struct s public s
  • 自己搭深度学习环境踩坑血泪史

    自己搭深度学习环境踩坑的血泪史 从一个沮丧的事情开始问题1 强行更新了一次win10后 双系统里的ubuntu的启动项就没了 xff0c 直接进入win10系统问题2 sudo apt get update 总是超时问题3 conda in
  • 电脑串口延迟/缓冲设置方法

    使用串口做精确信号发送的时候会经常出现不能时间不精确的问题 xff0c 使用两个u口转串口串连之后一个接收一个发送的情况下 收到的时间延迟数据如下 xff1a 注意 xff1a 这里的因为有一个接收缓冲区和一个发送缓冲区 xff0c 所以这
  • apt-get install 连同诸多依赖包一并安装的指令

    apt get install 连同诸多依赖包一并安装 如题 xff0c apt get安装某个包的时候 xff0c 经常会碰到很多依赖包 xff0c 需要一一安装了才行 xff0c 非常麻烦 当然 xff0c 可以使用以下指令一步到位 a
  • git 分支操作记录

    查看分支 xff1a 查看本地分支 xff1a git branch 查看远程分支 xff1a git branch r 查看全部分支 xff08 本地和远程 xff09 git branch a 新建分支 xff1a 创建新分支 xff1
  • C++ 简析容器Vector

    向量 xff08 Vector xff09 是一个封装了动态大小数组的顺序容器 xff08 Sequence Container xff09 跟任意其它类型容器一样 xff0c 它能够存放各种类型的对象 可以简单的认为 xff0c 向量是一
  • SLAM初始化

    本节的学习要点 xff1a 初始化的目的 单目 双目 初始化的两种方法初始化过程 初始化的目的 单目SLAM初始化的目的是 61 61 构建初始的三维点云地图 xff08 空间点 xff09 并为之后的计算提供初始值 61 61 由于仅从单
  • tensorflow lite example label_image 分析【二】

    接上文 3 代码分析 main函数首先将入参写入参数结构体 Settings s struct Settings bool verbose 61 false bool accel 61 false bool input floating 6
  • 利用Kalibr标定双目相机与IMU

    本文介绍如何利用Kalibr标定工具进行双目相机与IMU的联合标定 主要过程包括以下四步 xff1a 生成标定板标定双目相机标定IMU联合标定 1 生成标定板 使用AprilTag rosrun kalibr kalibr create t
  • FreeRTOS常见知识点

    FreeRTOS常见知识点 1 临界段代码 临界段代码也叫做临界区 xff0c 是指那些必须完整运行 xff0c 不能被打断的代码段 xff0c 比如某些外设的初始化需要严格的时序 xff0c 且不能被打断 FreeRTOS提供的解决方案是
  • linux ssh 登录报hosts错误

    问题分析 问题在于 xff1a Users liuhanlin ssh known hosts xff0c 这个目录中纪录了你之前机器的配置 如果你更换了系统 xff0c 并且重新绑定了密钥 就会出现这个hosts的报错 解决方法 cd U

随机推荐

  • linux内核-进程的调度与切换

    在多进程的操作系统中 xff0c 进程调度是一个全局性的 关键性的问题 xff0c 它对系统的总体设计 系统的实现 功能设置以及各个方面的性能都有着决定性的影响 根据调度结果所做的进程切换的速度 xff0c 也是衡量一个操作系统性能的重要指
  • STM32芯片VDD、VDDA和VREF的关系

    今天碰到一个48pin stm32F103CBT6芯片 xff0c AD参考电压输入引脚的问题 通过cubemx查看引脚 xff0c 发现没有VREF引脚 xff0c 只有VDD 和VDDA电压输入 xff1b 通过查资料和手册 xff0c
  • RTSP的WEB播放方案Streamedian

    因项目需要 xff0c 查找rtsp视频流web播放方法 xff0c 这是文档 原文文档连接 xff1a https streamedian com docs Streamedian是一个 Javascript 库 xff0c 它实现了 R
  • 解决UnsatisfiedLinkError: Unable to load library:Native library not found in resource path

    span class hljs keyword public span span class hljs class span class hljs keyword class span span class hljs title Test
  • TM4C123系列ARM单片机开发入门介绍

    初学TM4C123GH6PZ 以前未接触过ARM 所以感觉一头雾水 根据自己以前C51的简单经验 xff0c 对照资料很少的ARM4教程 摸索着终于明白了开发流程 xff0c 从软件到硬件用自己的程序点亮了LED 现将自己的学习过程记录下来
  • Kinect For Windows SDK 2.0的解读之《KinectV2开发手册》

    转载自 自己的博客 xff0c 由于百度迟迟没有收录 xff0c 在这里转发 Kinect For Windows SDK 2 0的解读之一 开发手册 这二天在外面出差 xff0c 回来才发现26号早晨微软已经通知我可以下载最新的SDK了
  • 使用QZXing识别图片二维码

    欢迎访问http brightguo com 试了下QZXing这个识别二维码库 xff0c 下载地址 xff1a 百度网盘 CSDN下载链接 本站下载连接 在github上下载qzxing xff08 https github com z
  • Ubuntu 16.04中用bazel交叉编译tensorflow lite

    首先在csdn上着了大神关于这个的实践如下链接 https www cnblogs com jojodru p 7744630 html 但是报错如下 xff0c 说是找不到opt选项 INFO Reading rc options for
  • 【原创】岁月如歌 一款网易歌单生成pdf的软件

    介绍 这是一款可以将网易云音乐的歌单中所有歌词输出为pdf的软件 项目持续维护地址 http brightguo com song list to pdf 目前没有搜到相关网易歌单导出为pdf的软件 xff0c 因此我特地将此软件开发出来免
  • 国内人脸识别公司哪家强,人脸比对跑个分比较下!

    前不久 最强大脑 第四季第一期的舞台上 xff0c 王峰对阵小度机器人进行了 人机大战 xff0c 其中最精彩和有趣的是第一场pk 从小时候照片判别长大后对应的人 xff0c 而她有个姐姐 xff0c 这对姐妹恰恰是双胞胎 xff01 百度
  • 2011年终总结

    2011年终总结 4月8日 xff0c 研究生面试 xff0c 和同学第一次来到上海 xff0c 当时的我又经受一次失败 xff0c 也许说我本该走这条路 也经常听到很多人说 xff0c 自己考得烂了 xff0c 最后到这个学校上学 从高中
  • 永久音乐外链

    使用skydrive上传速度变慢了 xff01 2013 2 14 文摘 xff1a http tieba baidu com p 1735575571 被删掉了 xff0c 2013 2 14 一直在申请吧主 xff0c 估计申请不上了
  • Apache2.2+MySql5.5+PHP5.4的安装和配置(windows)

    Apache2 2 43 MySql5 5 43 PHP5 4的安装和配置 phpMyAdmin的安装和配置 安装 Apache2 2 http httpd apache org download cgi apache24 Win32 Bi
  • 反思了一下过去几年的程序员之路

    最近回忆起一年前的找工作时的面试时的题目 xff0c 很多基础题都没做好 xff0c 很多概念也混淆不清 虽然自己这几年写的代码不少 xff0c 但都使用自己熟悉的东西写 xff0c 而已经有很多新的技术新的方法却没有使用过 一方面公司自己
  • 怎样使用OpenCV进行人脸识别 [停止更新]

    唯一持续维护地址 xff1a http guoming me face recognition with opencv 更新 2013 6 27 停止人脸识别的研究 xff0c 具体人脸识别系统可以参见文章 使用Kinect进行人脸识别 K
  • OpenCV矩阵运算

    一 矩阵 Mat I img I1 I2 dst A B double k alpha Scalar s 1 加法 I 61 I1 43 I2 等同add I1 I2 I add I1 I2 dst mask dtype scaleAdd
  • OpenCV官方学习文档[2013-7-4更新][最新版本2.4.6]

    最新的OpenCV2 4 6文档更新参见 xff1a http guoming me opencv OpenCV配置视频 OpenCV2 4 6 2013 7 3更新 http opencv org opencv 2 4 6 is out
  • ANSI与UNICODE字符函数对照表

    宽字符处理函数函数与普通函数对照表 字符分类 xff1a 宽字符函数普通 C 函数描述 iswalnum xff08 xff09 isalnum xff08 xff09 测试字符是否为数字或字母 iswalpha xff08 xff09 i
  • 做 LeetCode 有感

    今天周六 xff0c 公司一个人也没有 xff0c 下午花了5个半小时 xff0c 安安静静的再看 xff0c 再做了4道 LeetCode 的题目 看完之后 xff0c 现在仔细回想 xff0c 获得了什么 xff0c 好像什么也没有 但
  • 知识点滴 - 关于头文件的重复包含问题

    使用Include guard的目的 C Language Tutorial 61 gt Header Include Guards 2 12 Header guards Learn C 43 43 C C 43 43 中 xff0c in