286

在 C/C++ 中,全局变量是否像我的教授认为的那样糟糕?

4

28 回答 28

306

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

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

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

于 2009-01-27T20:11:27.710 回答
113

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

“无全局变量”规则之所以存在,是因为在大多数情况下,全局变量会使代码的含义变得不那么清晰。

然而,就像许多规则一样,人们记住的是规则,而不是规则的意图。

我见过一些程序,它们似乎通过传递大量参数来使代码大小增加一倍,只是为了避免全局变量的弊端。最后,使用全局变量会让阅读程序的人更清楚。由于盲目地遵守规则,最初的程序员违背了规则的意图。

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

于 2010-02-24T12:19:03.677 回答
69

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

于 2009-01-27T18:42:22.507 回答
47

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

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

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

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

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

需要解决的全局变量的一些问题

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

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

调试使用全局变量的多线程应用程序可能会更加困难。您可能会遇到竞争条件,这些条件会产生难以复制的缺陷。由于多个组件通过全局变量进行通信,特别是在多线程应用程序中,因此很难了解哪个组件在何时以及如何更改变量来更改变量。

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

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

使用 C++namespacestructC 技术

对于 C++ 编程语言,该namespace指令有助于减少名称冲突的可能性。namespace以及class各种访问关键字(private、、protectedpublic)提供了封装变量所需的大部分工具。但是,C 编程语言不提供此指令。这篇 stackoverflow 贴文,Namespaces in C,为 C 提供了一些技术。

一种有用的技术是拥有一个单一的内存驻留数据区域,该区域被定义为struct具有全局可见性的区域,其中包含struct指向正在公开的各种全局变量和函数的指针。全局变量的实际定义使用static关键字给出文件范围。如果您随后使用const关键字来指示哪些是只读的,编译器可以帮助您强制执行只读访问。

使用该struct技术还可以封装全局,使其成为一种恰好是全局的包或组件。通过拥有这种组件,可以更轻松地管理影响全局的更改和使用全局的功能。

然而,尽管namespacestruct技术可以帮助管理名称冲突,但使用全局变量(尤其是在现代多线程应用程序中)引入的组件间耦合的潜在问题仍然存在。

于 2014-07-01T02:10:43.013 回答
39

只有在别无选择时才应使用全局变量。是的,这包括单身人士。90% 的情况下,引入全局变量是为了节省传递参数的成本。然后发生多线程/单元测试/维护编码,你就有问题了。

所以是的,在 90% 的情况下,全局变量都是不好的。您在大学期间不太可能看到这些例外情况。我能想到的一个例外是处理固有的全局对象,例如中断表。数据库连接之类的东西似乎是全球性的,但不是。

于 2009-01-27T20:22:33.333 回答
22

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

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

于 2009-01-27T18:37:41.067 回答
22

全局变量和你做的一样糟糕,不少。

如果您正在创建一个完全封装的程序,您可以使用全局变量。使用全局变量是一种“罪恶”,但编程的罪恶通常是哲学上的。

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

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

如果您正确使用它们,Goto 也不是。

最大的“坏”问题是,如果你用错了,人们会尖叫,火星着陆器坠毁,世界会爆炸……或类似的事情。

于 2009-01-27T18:44:00.913 回答
20

如果您的代码最终可能会在最高法院审判期间接受密集审查,那么您需要确保避免使用全局变量。

请参阅这篇文章: Buggy 酒精测试仪代码反映了源代码审查的重要性

两项研究都发现代码风格存在一些问题。与审稿人有关的风格问题之一是广泛使用未受保护的全局变量。这被认为是糟糕的形式,因为它增加了程序状态变得不一致或值将被无意修改或覆盖的风险。研究人员还对整个代码中无法始终保持小数精度这一事实表示担忧。

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

于 2009-05-16T23:01:56.360 回答
17

我会用另一个问题来回答这个问题:你使用单数/单数不好吗?

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

于 2009-01-27T18:46:46.540 回答
17

问题不在于它们不好,而在于它们很危险。它们各有优缺点,在某些情况下,它们要么是完成特定任务的最有效方式,要么是唯一的方式。但是,即使您采取措施始终正确使用它们,它们也很容易被滥用。

几个优点:

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

几个缺点:

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

请注意,如果您愿意,我列出的前两个优点和前两个缺点是完全相同的,只是措辞不同。这是因为全局变量的特性确实很有用,但使它们有用的特性才是它们所有问题的根源。

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

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

它们的好坏取决于您如何使用它们。大多数人倾向于糟糕地使用它们,因此对它们普遍持谨慎态度。如果使用得当,它们可以成为一大福音;但是,如果使用不当,它们可以并且在您最不期望的时间和方式回来咬您。

看待它的一个好方法是它们本身并不坏,但它们会促成糟糕的设计,并且可以成倍地增加糟糕设计的效果。


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

于 2016-02-05T04:20:00.807 回答
11

正如有人在另一个线程中所说(我在解释)“在您完全理解这样做的后果之前,不应违反这样的规则。”

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

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

于 2009-01-27T20:22:36.127 回答
10

使用全局变量有点像在地毯下扫地。这是一个快速修复,在短期内比使用簸箕或真空吸尘器清理它要容易得多。然而,如果你后来最终移动了地毯,你会在下面有一个大的惊喜。

于 2009-01-27T20:44:37.720 回答
9

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

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

于 2009-01-27T20:10:46.120 回答
8

全局变量是不好的,如果它们允许您操作应该只在本地修改的程序的各个方面。在 OOP 中,全局变量经常与封装理念相冲突。

于 2009-01-27T18:39:13.700 回答
7

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

全局变量有自己的位置,就像许多人说的那样,知道何时何地使用它们可能很复杂。因此,我认为与其深入了解您的教授决定禁止的全局变量的原因、方式、时间和位置的本质。谁知道,他将来可能会取消他们的禁令。

于 2009-01-27T20:02:14.500 回答
7

绝对不。虽然滥用它们......那很糟糕。

为了……而盲目地删除它们就是……盲目的。除非您知道优点和缺点,否则最好避开并按照您所学的那样做,但全局变量并没有隐含的错误。当您了解利弊时,您可以更好地做出自己的决定。

于 2009-03-06T13:19:25.837 回答
6

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

大多数时候,当你做多线程时,你需要分享一些东西。例如,在生产者-消费者模式中,您可能会共享一些包含工作单元的线程安全队列。并且您可以共享它,因为该数据结构是线程安全的。就线程安全而言,该队列是否是全局的完全无关紧要。

在整个线程中表达的暗示希望在不使用全局变量的情况下将程序从单线程转换为多线程会更容易,这是天真的。是的,全局变量让你更容易射中自己的脚,但是有很多方法可以射中自己。

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

于 2016-03-10T06:55:59.763 回答
5

不,他们一点也不坏。您需要查看编译器生成的(机器)代码才能做出决定,有时使用本地比使用全局要糟糕得多。另请注意,将“静态”放在局部变量上基本上是使其成为全局变量(并产生其他真正的全局变量可以解决的丑陋问题)。“本地全局变量”特别糟糕。

全局变量还可以让您完全控制内存使用,这对于本地变量来说要困难得多。如今,这仅在内存非常有限的嵌入式环境中才重要。在您假设嵌入式与其他环境相同并假设编程规则全面相同之前,您需要了解一些事情。

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

如果您遵循在学校或书籍中教给您的所有编程规则,您的编程生涯将受到极大限制。您可能会生存下来并拥有丰硕的事业,但是您可以使用的环境的广度和宽度将非常有限。如果您知道规则如何存在以及为什么存在并且可以捍卫它,那很好,如果您的唯一理由是“因为我的老师这么说”,那就不太好了。

请注意,像这样的话题经常在工作场所争论并将继续存在,随着编译器和处理器(和语言)的发展,这些规则也在不断发展,并且不会捍卫你的立场,并且可能会被其他观点的人教导你不会前进。

在此期间,那么就按照说话最大声或拿着最大棍子的那个人所说的去做(直到你是那个喊得最大声并拿着最大棍子的人)。

于 2009-01-28T16:39:38.307 回答
5

是的,因为如果你让不称职的程序员使用它们(阅读 90% 尤其是科学家),你最终会得到 600 多个全局变量,分布在 20 多个文件和一个 12,000 行的项目中,其中 80% 的函数取 void、返回 void 并运行完全基于全局状态。

除非您了解整个项目,否则很快就不可能了解任何时候发生的事情。

于 2012-02-04T08:22:11.790 回答
4

全局变量在小程序中很好,但如果在大程序中以同样的方式使用,那就太糟糕了。

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

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

于 2009-01-27T20:16:13.613 回答
3

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

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

于 2017-02-19T08:33:17.610 回答
2

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

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

例如:-

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

于 2014-06-07T15:59:25.240 回答
2

归根结底,您的程序或应用程序仍然可以工作,但需要保持整洁并完全了解正在发生的事情。如果您在所有函数之间共享一个变量值,则可能很难跟踪哪个函数正在更改该值(如果该函数这样做的话)并使调试变得困难一百万倍

于 2016-02-05T04:24:44.003 回答
1

我通常将全局变量用于很少更改的值,例如单例或指向动态加载库中函数的函数指针。在多线程应用程序中使用可变全局变量往往会导致难以跟踪错误,因此我尽量避免这种情况作为一般规则。

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

于 2011-07-12T09:18:02.470 回答
1

迟早您将需要更改该变量的设置方式或访问它时发生的情况,或者您只需要查找更改的位置。

没有全局变量实际上总是更好。只需编写 dam get 和 set 方法,并在一天、一周或一个月后需要它们时成为您的腺体。

于 2009-01-27T20:28:08.160 回答
1

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

于 2016-08-13T11:58:10.230 回答
-1

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

当多个线程访问共享资源时会出现竞争条件,其中至少一个线程具有对数据的写访问权限。那么,程序的结果是不可预测的,取决于不同线程访问数据的顺序。

更多关于这里,https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code

于 2014-09-07T09:47:52.270 回答
-1

安全性较低意味着如果变量被声明为全局变量,则任何人都可以操纵变量,为了解释这一点,如果您在银行程序中将余额作为全局变量,则用户函数可以操纵它,银行官员也可以操纵它这就是一个问题。只有用户应该被赋予只读和提款功能,但银行职员可以在用户亲自在办公桌上提供现金时添加金额。这就是它的工作方式

于 2012-11-23T07:34:12.837 回答