全球变量是否糟糕?

在C / C ++中,全局变量和我的教授认为的一样糟糕吗?

全局变量的问题在于,由于每个函数都可以访问这些函数,因此要弄清楚哪些函数实际读取和写入这些变量变得越来越困难。

要了解应用程序的工作原理,您几乎必须考虑修改全局状态的每个函数。 这可以做到,但随着应用程序的增长,它将变得越来越难以实际上不可能(或者至少完全浪费时间)。

如果不依赖全局变量,则可以根据需要在不同函数之间传递状态。 这样你就可以更好地理解每个函数的作用,因为你不需要考虑全局状态。

重要的是要记住总体目标:清晰度

“没有全局变量”规则是存在的,因为大多数时候,全局变量使得代码的含义不那么清晰。

但是,像许多规则一样,人们会记住规则,而不是规则的目的。

我已经看到程序似乎通过传递大量参数来简化代码的大小,以避免全局变量的恶。 最后,使用全局变量会使程序更清晰 。 通过无意识地坚持规则的话,原来的程序员已经失去了规则的意图。

所以,是的,全局变量通常很糟糕。 但是如果你觉得最终程序员的意图通过全局变量的使用变得更加清晰,那就继续吧。 但是,请记住,当您强迫某人访问第二段代码(全局变量)以了解第一部分的工作原理时,会自动产生清晰度。

我的教授曾经说过这样的话:如果正确使用它们,使用全局变量是可以的。 我认为我没有擅长正确使用它们,所以我很少使用它们。

只有在没有其他选择时才应使用全局变量。 是的,这包括单身人士。 90%的时候,全局变量被引入以节省传递参数的成本。 然后发生multithreading/unit testing/维护编码,你就遇到了问题。

所以是的,在90%的情况下,全局变量都很糟糕。 您在大学期间不太可能看到例外情况。 我能想到的一个例外是处理固有的全局对象,例如中断表。 像DB连接这样的东西似乎是全球性的,但事实并非如此。

全局变量为程序员创建的问题是它扩展了使用全局变量的各个组件之间的组件间耦合表面。 这意味着随着使用全局变量的组件数量的增加,交互的复杂性也会增加。 这种增加的耦合通常使得缺陷在更改时更容易注入系统,并且还使得缺陷更难以诊断和纠正。 这种增加耦合还可以在进行更改时减少可用选项的数量,并且可以增加更改所需的工作量,因为通常必须跟踪也使用全局变量的各个模块以确定更改的后果。

封装的目的,与使用全局变量基本相反,是减少耦合,以便更容易,更安全,更容易测试理解和更改源。 当不使用全局变量时,使用unit testing要容易得多。

例如,如果您有一个简单的全局整数变量用作枚举指示符,各种组件用作状态机,然后通过为新组件添加新状态进行更改,则必须跟踪所有其他组件组件,以确保更改不会影响他们。 可能的问题的一个示例是,如果在各个地方使用用于测试枚举全局变量的值的switch语句以及每个当前值的case语句,并且碰巧某些switch语句没有default情况下处理全局的意外值突然之间,就应用程序而言,您有未定义的行为。

另一方面,共享数据区域的使用可用于包含在整个应用程序中引用的一组全局参数。 这种方法通常用于具有小内存占用的嵌入式应用程序。

在这些应用程序中使用全局变量时,通常将写入数据区域的责任分配给单个组件,所有其他组件将该区域视为const并从中读取,从不写入该区域。 采用这种方法可以限制可能出现的问题。

以下是使用需要解决的全局变量的一些问题。

当修改诸如结构的全局变量的源时,必须重新编译使用它的所有内容,以便使用该变量的所有内容都知道其真实大小和内存模板。

如果多个组件可以修改全局变量,则可能会遇到全局变量中存在不一致数据的问题。 使用multithreading应用程序,您可能需要添加某种锁定或关键区域以提供一种方式,以便一次只有一个线程可以修改全局变量,并且当线程正在修改变量时,所有更改都是完整的并且在其他线程可以查询变量或修改它之前提交。

调试使用全局变量的multithreading应用程序可能会更加困难。 您可能遇到可能产生难以复制的缺陷的竞争条件 。 有几个组件通过全局变量进行通信,特别是在multithreading应用程序中,能够知道哪些组件正在改变变量的时间和方式,这很难理解。

名称冲突可能是使用全局变量的问题。 与全局变量同名的局部变量可以隐藏全局变量。 使用C编程语言时,您还会遇到命名约定问题。 解决方法是将系统划分为子系统,其中特定子系统的全局变量都以相同的前三个字母开头(请参阅目标C中的解析名称空间碰撞 )。 C ++提供名称空间,使用C可以解决这个问题,方法是创建一个全局可见的结构,其成员是各种数据项,指向数据和函数的指针,这些数据和函数在文件中作为静态提供,因此只有文件可见性才能通过全局可见的结构。

在某些情况下,会更改原始应用程序意图,以便修改为单个线程提供状态的全局变量,以允许运行多个重复的线程。 一个示例是为单个用户设计的简单应用程序,使用状态的全局变量,然后从管理层发出请求以添加REST接口以允许远程应用程序充当虚拟用户。 因此,现在您不得不复制全局变量及其状态信息,以便单个用户以及来自远程应用程序的每个虚拟用户都拥有自己独特的全局变量集。

是的,但是在您停止使用全局变量的代码并开始编写使用全局变量的代码的其他代码之前,您不会产生全局变量的代价。 但成本仍然存在。

换句话说,这是一个长期的间接成本,因此大多数人认为它并不坏。

我会用另一个问题回答这个问题:你使用singeltons / singeltons是不是很糟糕?

因为(几乎所有)singelton的使用是一个美化的全局变量。

如果您的代码可能会在最高法院审判期间进行深入审查 ,那么您需要确保避免全局变量。

看到这篇文章: Buggy呼气测醉器代码反映了源审查的重要性

这两项研究确定的代码风格存在一些问题。 涉及审稿人的风格问题之一是广泛使用未受保护的全局变量 。 这被认为是不良forms,因为它增加了程序状态变得不一致或者无意中修改或覆盖值的风险。 研究人员还对在整个代码中不能保持小数精度的事实表示了一些担忧。

伙计,我打赌那些开发者希望他们没有使用全局变量!

全局变量和你制作它们一样糟糕,同样重要。

如果要创建完全封装的程序,可以使用全局变量。 使用全局变量是一种“罪恶”,但编程犯罪是非常哲学的。

如果您查看L.in.oleum ,您将看到一种语言,其变量仅为全局变量。 它是不可扩展的,因为库除了使用全局变量之外别无选择。

也就是说,如果你有选择,并且可以忽略程序员的理念,那么全局变量并不是那么糟糕。

如果你正确使用Gotos,它们都不是。

最大的“坏”问题是,如果你使用它们错了,人们尖叫,火星着陆器崩溃,世界爆炸……或类似的东西。

正如有人在另一个话题中说的那样(我在解读)“这样的规则不应该被打破,直到你完全理解这样做的后果。”

有时全局变量是必要的,或者至少非常有用(例如,使用系统定义的回调)。 另一方面,由于你被告知的所有原因,它们也非常危险。

编程的许多方面应该留给专家。 有时你需要一把非常锋利的刀。 但是在你做好准备之前,你不能使用它……

全局变量通常很糟糕,特别是如果其他人正在处理相同的代码并且不想花20分钟搜索变量被引用的所有位置。 并且添加修改变量的线程会带来全新的麻烦。

在单个翻译单元中使用的匿名命名空间中的全局常量在专业应用程序和库中很好并且无处不在。 但是如果数据是可变的,和/或它必须在多个TU之间共享,你可能想要封装它 – 如果不是为了设计的缘故,那么为了任何人调试或使用你的代码。

使用全局变量有点像地毯下的污垢。 这是一个快速解决方案,并且在短期内比使用除尘盘或真空吸尘器更容易清理。 然而,如果你以后最后搬到地毯上,那么你下面会有一个惊喜。

问题不在于它们是坏的 ,更重要的是它们是危险的 。 他们有自己的优点和缺点,有些情况下,他们或者是实现特定任务的最有效或唯一的方式。 但是,即使您采取措施始终正确使用它们,它们也容易被滥用。

一些专业人士:

  • 可以从任何function访问。
  • 可以从多个线程访问。
  • 在程序结束之前永远不会超出范围。

一些缺点:

  • 可以从任何函数访问,无需作为参数显式拖入和/或记录。
  • 不是线程安全的。
  • 污染全局命名空间并可能导致名称冲突,除非采取措施来防止这种情况发生。

请注意,如果你愿意的话,我列出的前两个专业人员和前两个缺点是完全相同的,只是用不同的措辞。 这是因为全局变量的function确实很有用,但是使它们有用的function是所有问题的根源。

一些问题的一些潜在解决方案:

  • 考虑它们是否真的是解决问题的最佳或最有效的解决方案。 如果有更好的解决方案,请改用它。
  • 将它们放在具有唯一名称的命名空间[C ++]或singleton struct [C,C ++]中(一个很好的例子是GlobalsGlobalVars ),或者对全局变量使用标准化的命名约定(例如global_[name]g_module_varNameStyle (正如评论中的underscore_d所提到的那样))。 这将记录它们的使用(您可以通过搜索命名空间/结构名称找到使用全局变量的代码),并最小化对全局命名空间的影响。
  • 对于访问全局变量的任何函数,显式记录它读取的变量和写入的变量。 这将使故障排除更容易。
  • 将它们放在自己的源文件中并在相关的头文件中声明它们是extern ,因此它们的使用可以限于需要访问它们的编译单元。 如果您的代码依赖于许多全局变量,但每个编译单元只需要访问其中的一些,您可以考虑将它们分类为多个源文件,因此更容易限制每个文件对全局变量的访问。
  • 设置一种锁定和解锁它们的机制,和/或设计代码,以便尽可能少的函数需要实际修改全局变量。 阅读它们比编写它们要安全得多,尽管线程竞争仍可能导致multithreading程序出现问题。
  • 基本上,最小化对它们的访问,并最大化名称唯一性。 您希望避免名称冲突,并尽可能减少可能修改任何给定变量的函数。

它们的好坏取决于你如何使用它们。 大多数人倾向于严重使用它们,因此对它们一般保持警惕。 如果使用得当,它们可能是一个重要的福音; 但是,如果用得不好,他们可以而且在你最不期望的时候再次咬你。

看待它的一个好方法是它们本身并不坏,但是它们能够实现糟糕的设计,并且能够以指数方式增加不良设计的效果。


即使您不打算使用它们,最好知道如何安全地使用它们并选择不使用它们,因为您不知道如何安全地使用它们。 如果您发现自己处于需要维护依赖于全局变量的预先存在的代码的情况下,如果您不知道如何正确使用它们,则可能会遇到困难。

全局变量很糟糕,如果它们允许您操作应该仅在本地修改的程序的各个方面。 在OOP中,全局变量通常与封装思想相冲突。

我认为你的教授在它开始之前就试图阻止一个坏习惯。

全局变量有它们的位置,就像很多人说知道何时何地使用它们可能很复杂。 所以我认为而不是深入研究你的教授决定禁止的全局变量的原因,方式,时间和地点的细节。 谁知道,他将来可能会禁止他们。

绝对不。 滥用他们虽然……这很糟糕。

为了这个原因而盲目地删除它们就是……没有头脑。 除非你知道优点和缺点,否则最好先明确并按照你所教导/学习的方式去做,但全局变量没有任何隐含的错误。 当你了解利弊时,更好地做出自己的决定。

全局变量在小程序中很好,但如果在大型程序中使用相同的方式则很糟糕。

这意味着您可以轻松养成在学习的同时使用它们的习惯。 这就是你的教授试图保护你的。

当您更有经验时,在他们没事的时候会更容易学习。

不,他们一点都不坏。 您需要查看编译器生成的(机器)代码以进行此确定,有时使用本地而不是全局更糟糕。 还要注意,将“静态”放在局部变量上基本上使它成为一个全局变量(并创建一个真正的全局可以解决的其他丑陋问题)。 “本地全球化”特别糟糕。

Globals可以让您清楚地控制内存使用情况,这对当地人来说更难以实现。 这些日子只适用于内存非常有限的嵌入式环境。 在假设嵌入式与其他环境相同并假设编程规则全面相同之前需要先了解一些事项。

你质疑所教的规则是好的,大多数都不是因为你被告知的原因。 然而,最重要的教训并不是这是一个永远随身携带的规则,但这是为了通过这门课程并向前迈进而必须遵守的规则。 在生活中你会发现,对于公司XYZ,你将有其他的编程规则,你最终必须遵守,以便继续获得薪水。 在这两种情况下你都可以争论这个规则,但我认为你在工作上会比在学校里有更好的运气。 你只是众多学生中的另一个,你的座位将很快被替换,教授们不会,在工作中你是一个小团队的成员之一,必须看到这个产品到最后,在那个环境中制定的规则是为团队成员以及产品和公司的利益,所以如果每个人都喜欢或者对于特定的产品有很好的工程理由来违反你在大学学到的东西或者有关通用编程的书,那么就把你的想法卖给团队并将其写为有效(如果不是首选方法)。 在现实世界中,一切都是公平的游戏。

如果您遵循在学校或书籍中教给您的所有编程规则,您的编程职业将非常有限。 您可能会生存下来并拥有丰富的职业生涯,但您可以获得的环境的广度和宽度将非常有限。 如果你知道规则是如何以及为什么存在并且可以为其辩护,那很好,如果你只是因为“因为我的老师这么说”,那就不那么好了。

请注意,像这样的主题经常在工作场所争论,并将继续,因为编译器和处理器(和语言)发展,所以做这些规则,而不是捍卫你的立场,并可能被另一个意见的人教一课你不会前进。

与此同时,只要做出声音最响的那个或带着最大的棒说的话(直到你是那个大声喊叫并携带最大棒的那个时候)。

是的,因为如果你让不称职的程序员使用它们(阅读90%特别是科学家)你最终会有超过20个文件的600+全局变量和12,000行的项目,其中80%的函数无效,返回无效,并且操作完全在全球国家。

除非你了解整个项目,否则很快就无法理解任何一点上发生的事情。

全局变量的使用实际上取决于要求。 它的优点是,它减少了重复传递值的开销。

但是你的教授是对的,因为它引发了安全问题,因此应该尽可能避免使用全局变量。 全局变量也会产生有时难以调试的问题

例如:-

运行时 修改变量值的情况。 那时很难确定哪部分代码正在修改它以及在什么条件下。

我想反对在整个post中提出的观点,即它使multithreading本身变得更难或不可能。 全局变量是共享状态,但全局变量(例如传递指针)的替代方案也可能共享状态。 multithreading的问题是如何正确使用共享状态,而不是该状态是否恰好通过全局变量或其他东西共享。

大多数情况下,当你进行multithreading时,你需要分享一些东西。 例如,在生产者 – 消费者模式中,您可能共享一些包含工作单元的线程安全队列。 并且您可以共享它,因为该数据结构是线程安全的。 在线程安全方面,该队列是否是全局的是完全无关紧要的。

在不使用全局变量的过程中,在整个线程中表达的隐含的希望将程序从单线程转换为multithreading会更容易。 是的,全局变量可以让你更容易在脚下射击,但是有很多方法可以射击自己。

我不是提倡全局变量,因为其他要点仍然存在,我的观点仅仅是程序中的线程数与变量范围无关。

迟早你需要改变设置变量的方式或者访问它时会发生什么,或者你只需​​要寻找它变化的地方。

没有全局变量几乎总是更好。 只需写下大坝获取和设置方法,并在一天,一周或一个月后需要它们时压紧你。

我通常使用全局变量来表示很少改变的值,如单例或动态加载库中函数的函数指针。 在multithreading应用程序中使用可变全局变量往往导致很难跟踪bug,所以我试图避免这种情况作为一般规则。

使用全局而不是传递参数通常会更快,但是如果你正在编写一个multithreading应用程序,你现在经常这样做,它通常不能很好地工作(你可以使用线程静态但是性能提升是有问题的) 。

在一天结束时,您的程序或应用程序仍然可以工作,但这是一个整洁的问题,并完全了解最新情况。 如果你在所有函数中共享一个变量值,可能很难跟踪哪个函数正在改变值(如果函数这样做)并使调试变得更难一百万次

Global配置方面很好。 当我们希望我们的配置/更改整个项目 产生全局影响时。

因此,我们可以更改一个配置 ,并将更改定向到整个项目 。 但我必须警告你必须非常聪明才能使用全局变量。

安全性较少意味着任何人都可以操纵变量,如果它们被声明为全局变量,为了解释这个例子,如果你在银行程序中将余额作为全局变量,用户函数可以操纵它,以及银行官员也可以操纵这样就出现了问题。只有用户才能获得只读和撤销function,但银行职员可以在用户亲自在桌面上提供现金时添加金额。这就是它的工作方式

在企业内部的Web应用程序中,出于优化的原因,可以用于在服务器上保存会话/窗口/线程/用户特定数据,并防止在连接不稳定的情况下丢失工作。 如上所述,需要处理竞争条件。 我们使用类的单个实例来获取此信息,并对其进行了精心管理。

在multithreading应用程序中,使用局部变量代替全局变量以避免竞争条件。

当多个线程访问共享资源时发生竞争条件,其中至少一个线程具有对数据的写访问权。 然后,程序的结果是不可预测的,并且取决于不同线程对数据的访问顺序。

更多相关信息, 请访问https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code