【C语言】头文件重复包含问题的解决方式

  • 🐚作者简介:花神庙码农(专注于Linux、WLAN、TCP/IP、Python等技术方向)
  • 🐳博客主页:花神庙码农 ,地址:https://blog.csdn.net/qxhgd
  • 🌐系列专栏:C语言编程
  • 📰如觉得博主文章写的不错或对你有所帮助的话,还望大家三连支持一下呀!!! 👉关注✨、点赞👍、收藏📂、评论。
  • 如需转载请参考转载须知!!

ifndef

  • include guards,最常见的一种方法,此方法依赖于宏名字不冲突,可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件不会被不小心同时包含。
  • 但如果不同头文件中的宏名相互冲突,可能会导致编译器找不到声明的情况。
  • 示例如下:
// header.h 
#ifndef HEADER_H
#define HEADER_H

// Some Declarations and definition

#endif // HEADER_H

缺陷

  • 命名冲突
    像UTILS_H这样命名不当的头文件保护宏(#ifndef相关的宏)可能会与头文件或源文件中的其他标识符发生冲突。当宏意外地变得未定义时,调试工作就会变得极其混乱。
  • 调试阻碍
    你必须在脑海中解析这些保护宏,以追踪头文件是从哪里被包含进来的。这会妨碍调试工作流程。
  • 语法繁琐
    多行的包装结构(#ifndef、#define、#endif这些组合)增加了视觉上的杂乱感,损害了头文件的可读性。
  • 重复的预处理
    保护宏可以防止内容重复,但预处理器在多个源文件中仍然需要多次处理该头文件。这会减慢编译速度。
  • 包含顺序依赖问题
    如果头文件在不同的源文件中以不同的顺序被包含,就可能会出现一些难以察觉的问题。而保护宏并不能解决这个问题。

pragma once

  • 在C/C++标准中,#pragma是一条预处理的指令(preprocessor directive)。简单地说,#pragma是用来向编译器传达语言标准以外的一些信息。
  • If ‘#pragma once’ is seen when scanning a header file, that file will never be read again, no matter what. It is a less-portable alternative to using ‘#ifndef’ to guard the contents of header files against multiple inclusions.
  • 使用示例:
/*
* Copyright 2014 Cloudbase Solutions Srl
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#pragma once

#include "precomp.h"

/*
 * These are error codes to be used by netlink transactional operations.
 * The error code is assigned to the "error" field (INT) of the NL_MSG_ERR
 * struct.
*/

typedef enum _NL_ERROR_
...

pragma once的机制

  • 当预处理器遇到#pragma指令时,预处理器将记录包含此指令的文件,并确保在单次编译过程中不会多次包含该文件。预处理器会跟踪通过#include包含的头文件的唯一文件路径。在处理带有#pragma once的头文件之前,它会检查该文件路径是否已经被包含过。如果是,就会跳过该头文件的内容。这意味着#pragma once是基于文件来防止多次包含,而不像保护宏那样是基于符号来防止多次包含。
  • 当预处理器遇到#pragma once时,它会做以下几件事:
    – 预处理器将在内部维护一个列表,记录已经处理过的头文件。每当遇到#pragma once,预处理器都会检查这个列表。
    – 如果头文件已经在列表中,预处理器就会跳过这个头文件,不再处理它。这就防止了同一个头文件在单次编译中被重复包含。
    – 如果头文件还不在列表中,预处理器就会将其添加到列表中,然后正常处理这个头文件。

优势

  • 更快的编译速度
    预处理器会完全跳过对重复头文件的处理,而不是每次都要解析头文件保护宏。

  • 避免棘手的 “包含顺序” 问题
    头文件可以以任意顺序包含,而不必在各个源文件中保持一致的包含顺序。

  • 编译速度更快
    由于编译器将不会在翻译单元中的文件的第一个 #pragma once 后打开和读取文件,因此,使用 #include 可减少生成次数。 它称为多包含优化。
    头文件包含保护机制(#ifndef等)会迫使预处理器在不同的翻译单元中多次解析相同的头文件,以检查宏的定义情况。而#pragma once通过跟踪文件路径,能够完全跳过对重复文件的再次处理,从而加快编译速度。

  • 消除命名冲突:命名不佳的头文件包含保护宏很容易与头文件或源文件中的其他符号发生冲突,以一些难以察觉的方式导致编译失败。#pragma once依赖文件路径来工作,而不是容易出错的宏名称,从而避免了这个问题。

  • 语法简洁
    传统的#ifndef包装代码每个头文件会额外增加 3 - 5 行代码,这一眼看去就会影响代码的可读性。
    #pragma once只需一行代码就能清晰地表达其意图,简洁、精炼且易于维护。

  • 提高可读性
    头文件包含保护机制会打断头文件中逻辑内容的连续性,从而模糊了代码的控制流程。
    #pragma once不会造成任何视觉干扰,能让声明部分清晰明了,便于浏览。

  • 更易于调试
    追踪头文件的包含位置需要解读保护宏的名称。#pragma once与文件路径是一一对应的关系,提高了代码的可调试性,让开发者更容易理解代码。

  • 避免 “包含顺序” 问题
    如果相同的头文件在不同源文件中的包含顺序不同,可能会出现一些难以发现的问题。头文件包含保护机制无法避免这类问题。#pragma once会跳过重复的头文件,而不考虑它们的包含顺序,从而避免了顺序依赖问题。

#pragma once的好处不仅体现在加快编译速度和语法简洁上,它还能提升代码质量、可读性和可维护性。

最佳实践

  • 仅在头文件中使用
    不要将 #pragma once 应用于通过 #include “source.cpp” 包含的源文件。

  • 置于代码之前
    为保证一致性和可移植性,应将 #pragma once 放在文件的第一行。

  • 留意作用域问题
    像 stdint.h这类既会被全局包含,又会在局部作用域内包含的头文件,要避免使用 #pragma once。

  • 警惕平台兼容性问题
    一些生僻的旧平台可能不支持 #pragma once。在采用之前,需权衡对代码可移植性的要求。

  • 包含保护宏混用
    最重要的是,不要同时使用 #pragma once 和包含保护宏。同时使用这两者就失去了使用 #pragma once 的意义。

_Pragma(“once”)

  • C99 标准引入了_Pragma操作符。这个特性解决了#pragma的一个主要问题:由于#pragma是一条指令,它无法作为宏展开的结果生成。而_Pragma是一个操作符,类似于sizeof,并且可以嵌入在宏中使用。
  • _Pragma(“once”)与#pragma once有相同的效果。
  • _Pragma(“once”)与#pragma once的区别:前者是语言层面的标准,而后者是编译器的扩展,

如本文对你有些许帮助,欢迎大佬支持我一下(点赞+收藏+关注、关注公众号等),您的支持是我持续创作的不竭动力
支持我的方式

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花神庙码农

你的鼓励是我码字的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值