#pragma once的作用以及全局变量的问题

2023-11-20

提出问题

第一次遇到#pragma once的时候,到网上找了些资料,大部分答案都大同小异,大概意思是让编译器只编译一次。我们都知道,一般不在头文件中定义全局变量,因为那会导致该变量多次定义。那么问题来了,既然用#pragma once预编译命令可以防止重复编译,为什么不能在头文件中定义全局变量呢?这个问题长期困扰着我,平时也就是记住这些规则而已。正所谓要知其所以然嘛,那我们就来探讨一下这个问题。

#pragma once的作用

我们从三个文件开始。
第一个文件是test1.h

//#pragma once
//test1.h

struct test1
{
	int val;
};

第二个文件是test2.h

#pragma once
//test2.h

#include "test1.h"

最后一个文件test.cpp

// test.cpp

#include "stdafx.h"		//我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"
#include "test2.h"

int main()
{
    return 0;
}

这种情况是可能的,因为我们可能会同时在源文件中使用这两个头文件的内容。
下面我们来编译一下:
编译结果
为什么会报错呢?一个源文件包含一个头文件,只是以头文件的形式展开,就是把头文件里的内容copy到源文件中去。在test.cpp文件中,#include "test2.h"展开后,会包含一个test1.h,而原本就包含了一个test1.h。最终结果就是结构体test1被定义了两次,然后报错了。
那么我把test1.h的注释(//#pragma once)去掉会怎么样?答案是yes!编译通过。#pragma once的作用是对于某一个文件,如果多次包含另一个文件的话,只编译一次。

头文件定义全局变量

我们继续创建3个文件。
第一个文件为test1.h

#pragma once
//test1.h

int a = 0;

第二个文件为test1.cpp

//test1.cpp
#include "stdafx.h"
#include "test1.h"

第三个文件为test.cpp
// test.cpp

#include "stdafx.h"		//我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"

int main()
{
    return 0;
}

编译看下会发生什么。
编译结果
oh no!不是说好的用#pragma once只编译一次吗,怎么又重定义了呢?宝贝你居然骗我。我们仔细观察错误提示发现,LNKxxx之类的。这是说明在编译的过程中,链接出错了。(?a@@3HA)是编译器对全局变量a生成的一个全局符号,这个符号的作用相当于链接器识别的一个id。
而test1.cpp和test.cpp两个源文件都包含了test1.h。因此int a = 0;会被编译器展开到那两个源文件中,因此,也会生成两个符号。在链接的过程中,这两个符号重复了,所以就报错了。
如果有同学对编译和链接的过程不是很明白的,可以参考深入理解计算机系统的第七章,那里讲的很透彻。

全局变量的解决方案

针对上一部分的情况,那么我想要在test1.cpp和test.cpp两个源文件中使用全局变量a怎么办?
我们修改这3个文件
test1.h:

#pragma once
//test1.h

extern int a;

int func();

test.cpp:

// test.cpp

#include "stdafx.h"		//我用的是vs2015,其他编译器可以不用包含这个头文件.
#include "test1.h"

int a = 0;

int main()
{
	a = 1;
	printf("%d\n", a);

	func();
	printf("%d\n", a);
    return 0;
}

test1.cpp:

//test1.cpp
#include "stdafx.h"
#include "test1.h"

int func() {
	a = 2;
	return a;
}

在test1.h中,使用extern表示变量a是外来的,在这里只是声明它,并没有定义。这样也就不会生成两个全局符号。
以下是运行结果,可以看到test1.cpp中的func函数成功地修改了变量a的值。
在这里插入图片描述

总结

#pragma once预编译指令防止重复编译是针对某一个源文件而言的,即某个源文件包含了两个头文件,而那两个头文件之一又包含了另一个,此时#pragma once会发生作用,起到只编译一次的作用。
全局变量不要定义在头文件中则适用于多个源文件的。多个源文件同时包含某一个头文件,那个头文件又定义了一个全局变量。此时会发生链接错误,解决方法是使用extern。
这些细节很容易被我们忽略掉,从而引起混淆。写篇文章mark一下。

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

#pragma once的作用以及全局变量的问题 的相关文章

  • OpenCv读/写视频色差

    我试图简单地使用 openCV 打开视频 处理帧并将处理后的帧写入新的视频文件 我的问题是 即使我根本不处理帧 只是打开视频 使用 VideoCapture 读取帧并使用 VideoWriter 将它们写入新文件 输出文件看起来比输入更 绿
  • 迭代变量并查找特定类型实例的技术

    我想迭代进程中内存中的变量 通过插件动态加载 并查找特定类型的实例 以前我可以找到特定类型 或内存中的所有类型 我可以创建类型的实例 我可以获取作为不同类型的字段包含的实例 但我无论如何都不知道只是 搜索 特定类型的实例 一种方法是使用 W
  • 我的线程图像生成应用程序如何将其数据传输到 GUI?

    Mandelbrot 生成器的缓慢多精度实现 线程化 使用 POSIX 线程 Gtk 图形用户界面 我有点失落了 这是我第一次尝试编写线程程序 我实际上并没有尝试转换它的单线程版本 只是尝试实现基本框架 到目前为止它是如何工作的简要描述 M
  • 使用具有现有访问令牌的 Google API .NET 客户端

    用例如下 移动应用程序正在通过 Google 对用户进行身份验证 并且在某些时候 我们需要将用户的视频发布到他的 YouTube 帐户 出于实际原因 实际发布应该由后端完成 已经存储在那里的大文件 由于用户已经通过应用程序的身份验证 因此应
  • C#动态支持吗?

    看完之后这个帖子 https stackoverflow com questions 2674906 when should one use dynamic keyword in c sharp 4 0k和链接 我还有 2 个问题 问题 1
  • 向 ExpandoObject 添加方法时,“关键字 'this' 在静态属性、静态方法或静态字段初始值设定项中无效”

    我尝试向 ExpandoObject 添加一个动态方法 该方法将返回属性 动态添加 给它 但它总是给我错误 我在这里做错了什么吗 using System using System Collections Generic using Sys
  • 从 MVC 迁移到 ASP.NET Core 3.1 中的端点路由时,具有角色的 AuthorizeAttribute 不起作用

    我正在尝试将我的项目从 UseMVC asp net core 2 2 兼容样式 升级到 UseEndpoint Routing 并且我的所有请求都被重定向到我的验证失败页面 它与声明有关 如果我删除 Authorize Roles Adm
  • JSON 数组到 C# 列表

    如何将这个简单的 JSON 字符串反序列化为 C 中的列表 on4ThnU7 n71YZYVKD CVfSpM2W 10kQotV 这样 List
  • 访问者和模板化虚拟方法

    在一个典型的实现中Visitor模式 该类必须考虑基类的所有变体 后代 在许多情况下 访问者中的相同方法内容应用于不同的方法 在这种情况下 模板化的虚拟方法是理想的选择 但目前这是不允许的 那么 模板化方法可以用来解析父类的虚方法吗 鉴于
  • ASP MVC:服务应该返回 IQueryable 的吗?

    你怎么认为 你的 DAO 应该返回一个 IQueryable 以便在你的控制器中使用它吗 不 您的控制器根本不应该处理任何复杂的逻辑 保持苗条身材 模型 而不是 DAO 应该将控制器返回给视图所需的所有内容 我认为在控制器类中看到查询 甚至
  • 通过 NHibernate 进行查询,无需 N+1 - 包含示例

    我有一个 N 1 问题 我不知道如何解决它 可以在这个问题的底部找到完全可重复的样本 因此 如果您愿意 请创建数据库 设置 NUnit 测试和所有附带的类 并尝试在本地消除 N 1 这是我遇到的真实问题的匿名版本 众所周知 这段代码对于帮助
  • 当我“绘制”线条时,如何将点平均分配到 LineRenderer 的宽度曲线?

    我正在使用线条渲染器创建一个 绘图 应用程序 现在我尝试使用线条渲染器上的宽度曲线启用笔压 问题在于 AnimationCurve 的 时间 值 水平轴 从 0 标准化为 1 因此我不能在每次添加位置时都在其末尾添加一个值 除非有一个我不知
  • 获取 2 个数据集 c# 中的差异

    我正在编写一个简短的算法 它必须比较两个数据集 以便可以进一步处理两者之间的差异 我尝试通过合并这两个数据集并将结果更改放入新的数据集来实现此目标 我的方法如下所示 private DataSet ComputateDiff DataSet
  • 如何一步步遍历目录树?

    我发现了很多关于遍历目录树的示例 但我需要一些不同的东西 我需要一个带有某种方法的类 每次调用都会从目录返回一个文件 并逐渐遍历目录树 请问我该怎么做 我正在使用函数 FindFirstFile FindNextFile 和 FindClo
  • 在类的所有方法之前运行一个方法

    在 C 3 或 4 中可以做到这一点吗 也许有一些反思 class Magic RunBeforeAll public void BaseMethod runs BaseMethod before being executed public
  • 耐用功能是否适合大量活动?

    我有一个场景 需要计算 500k 活动 都是小算盘 由于限制 我只能同时计算 30 个 想象一下下面的简单示例 FunctionName Crawl public static async Task
  • 使用 C# 从 DateTime 获取日期

    愚蠢的问题 给定日期时间中的日期 我知道它是星期二 例如我如何知道它的 tue 2 和 mon 1 等 Thanks 您正在寻找星期几 http msdn microsoft com en us library system datetim
  • 使用 CSharpCodeProvider 类编译 C# 7.3 的 C# 编译器版本是什么?

    我想使用 Microsoft CSharp CSharpCodeProvider 类来编译 C 7 3 代码 编译器版本在 IDictionary 中指定 在创建新的 CSharpCodeProvider 时将其作为输入 例如 Compil
  • 实例化 Microsoft.Office.Interop.Excel.Application 对象时出现错误:800700c1

    实例化 Microsoft Office Interop Excel Application 以从 winforms 应用程序生成 Excel 时 出现以下错误 这之前是有效的 但突然间它停止工作了 尽管代码和 Excel 版本没有变化 我
  • 使用 Crypto++ 获取 ECDSA 签名

    我必须使用 Crypto 在变量中获取 ECDSA 签名 我在启动 SignMessage 后尝试获取它 但签名为空 我怎样才能得到它 你看过 Crypto wiki 吗 上面有很多东西椭圆曲线数字签名算法 http www cryptop

随机推荐

  • 软件的可复用性

    1 什么是软件复用 软件复用就是利用已有的软件组件来实现或更新新的软件系统 2 软件复用的两个层面 for reuse 创造层面 开发可复用的软件 with reuse 使用层面 利用已有的可复用软件搭建新的软件应用系统 3 软件复用的优缺
  • 最简版Seq2Seq的英法机器翻译实践和详细代码解释

    Seq2Seq的英法机器翻译实践 本文的内容主要是基于英法平行语料库来实现一个简单的英法翻译模型 没有使用注意力机制和双向LSTM等技术 主要是为了掌握基本的Seq2Seq结构和TensorFlow函数使用 使用TensorFlow 1 1
  • 微信小程序预留ios安全区

    position fixed left 0 bottom 0 width 100 border top 1rpx solid ddd box shadow 0 1px 5px 0 eee background color fff z ind
  • Java使用jxl实现导出多sheet页Excel表格功能

    这篇文章主要介绍了使用jxl简化poi代码 实现导出Excel多sheet页功能 对大家的学习或者工作具有一定的参考学习价值 需要的朋友们下面随着小编来一起学习学习吧 以下代码就是一个小demo 大家可以直接自己创建maven项目然后跟着步
  • Linux Ubuntu 修改 /etc/apt/sources.list (镜像源)文件(非常实用)

    修改 etc apt sources list 文件 也即修改镜像源 能够加快在 Ubuntu 中下载和更新相关软件数据 否则默认情况下使用的是外网 下载起来比较慢 基本步骤 1 复制一份 etc apt sources list 文件 以
  • 时间序列特征构造:以电力负荷预测为例讲解(python语言)

    个人电气博文目录传送门 学好电气全靠它 个人电气博文目录 持续更新中 时间序列特征构造 时间序列问题 首先不管是回归问题 还是分类问题 一个模型的好坏 决定因素由数据集的大小 特征值的选取和处理 算法 其中最重要的是特征值的选取和处理 今天
  • 深入C++的拷贝构造和赋值函数 (深拷贝,浅拷贝)

    参考了 点击打开链接以及 高质量程序设计指南C C语言 说明 拷贝构造函数是一种特殊的构造函数 相同类型的类对象是通过拷贝构造函数来完成整个复制过程的 函数的名称必须和类名称一致 它的参数是唯一的 该参数是const类型的引用变量 例如 类
  • 尚硅谷微信小程序开发 仿网易云音乐App 小程序 后端接口服务器搭建

    目录 小程序学习 视频相关的教程文档与笔记分享 配套服务器 源码地址 接口使用说明文档 接口列表 启动服务 测试服务启动OK网页 http localhost 3000 test html 编辑 Postman测试服务器接口 postman
  • UDP协议与TCP协议的区别

    一 UDP的概述 User Datagram Protocol 用户数据报协议 UDP是传输层的协议 功能即为在IP的数据报服务之上增加了最基本的服务 复用和分用以及差错检测 二 UDP协议与TCP协议的区别 TCP连接时需要三次握手 有时
  • SparkSQL 操作数据库以及代码实践

    作者 禅与计算机程序设计艺术 1 简介 一 关于本文 SparkSQL是Apache Spark项目中用于处理结构化数据的开源模块 它提供了简单易用的API 能够将关系型数据库中的数据转换成DataFrame对象 方便进行各种分析查询 在实
  • 忘记阿里云远程连接密码怎么办

    此密码是连接终端控制台的密码 1 随便输入一个密码 点击确定 2 密码错误后网页右侧顶端出现1个修改远程连接密码的按钮 点击进去修改密码就OK了 QQ交流群 162136059
  • 二十四种设计模式之策略模式

    一 什么是策略模式 简单来说 策略模式是将每一个算法封装到拥有共同接口的不同类中 使得算法可以在不影响客户端的情况下发生变化 也可以理解为可供程序运行时选择的 不同的类 不同的解决方案 策略模式的特点 高内聚低耦合 可扩展 遵循ocp原则
  • rabbitmq 一般启动或服务启动

    rabbitmq managemen是管理后台的插件 我们要开启这个插件才能通过浏览器访问登录页面 进入到sbin目录下 rabbitmq plugins enable rabbitmq management 一般启动 服务启动 执行 ra
  • 计算机图形学---常用颜色模型汇总(RGB,CMY,HSV)

    本文整理自西安交通大学软件学院祝继华老师的计算机图形学课件 请勿转载 文章目录 常用颜色模型 RGB颜色模型 CMY颜色模型 HSV颜色模型 常用颜色模型 颜色模型 某个三维颜色空间中的一个可见光子集 包含某个颜色域的所有颜色 用途 在某个
  • 抓取微信文章:使用代理来处理反爬虫措施

    参考 崔庆才老师教程 目标网站分析 我们将从搜狗 微信这个网址来爬取微信的文章 https weixin sogou com 输入 程序员 并搜索 可以看到上方的URL有许多的信息 我们只保留query type page这几个参数即可 修
  • 多媒体开发计算机颜色相关知识

    颜色模式 颜色模式 颜色模型和颜色空间 计算机中的颜色格式 常用的颜色模型分类 RGB颜色模型 介绍 RGB模型的颜色空间 RGB555 RGB565 RGB24 RGB32 FFMPEG中定义的RGB色彩空间 显示器的颜色空间
  • nestjs:typeorm out of range value for column

    参考 nestjs How to store big int in nest js using typeorm Stack Overflow Column type bigint
  • 使用tensorflow时cuda报错Could not load dynamic library ‘libcudart.so.11.0‘; dlerror: libcudart.so.11.0:

    dlerror libcudart so 11 0 问题解决 首先找到你的电脑路径 usr local cuda lib64 查看自己的cuda版本 根据上图发现我的电脑的cuda版本为10 0 因此运行时会报错 这时有两种解决方案 方案一
  • 定时器详解

    1 什么是定时器 timer 定时器实际上就是Soc当中的一个内部外设 1 定时器与计数器 定时器常与计数器扯到一起 计数器也是soc当中的一个内部外设 计数器顾名思义是用来计数的 就和我们的秒表一样 秒表实际上就是一个计数器 每隔一个单位
  • #pragma once的作用以及全局变量的问题

    提出问题 第一次遇到 pragma once的时候 到网上找了些资料 大部分答案都大同小异 大概意思是让编译器只编译一次 我们都知道 一般不在头文件中定义全局变量 因为那会导致该变量多次定义 那么问题来了 既然用 pragma once预编