全球变量是否不好?

时间:2009-01-27 18:35:10

标签: c++ c global-variables

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

27 个答案:

答案 0 :(得分:232)

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

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

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

答案 1 :(得分:72)

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

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

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

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

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

答案 2 :(得分:57)

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

答案 3 :(得分:35)

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

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

答案 4 :(得分:25)

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

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

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

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

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

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

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

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

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

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

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

使用C ++ namespacestruct C技术

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

一种有用的技术是将单个内存驻留数据区定义为具有全局可见性的struct,并且在此struct内是指向正在公开的各种全局变量和函数的指针。使用static关键字为全局变量的实际定义提供文件范围。然后,如果您使用const关键字指示哪些是只读的,则编译器可以帮助您强制执行只读访问。

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

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

答案 5 :(得分:19)

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

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

答案 6 :(得分:18)

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

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

答案 7 :(得分:18)

全局变量和制作它们一样糟糕,不能少。

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

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

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

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

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

答案 8 :(得分:18)

如果在最高法院审判期间您的代码可能会在强化审核下结束,那么您需要确保避免全局变量。

请参阅此文章: Buggy breathalyzer code reflects importance of source review

  

有一些问题   已识别的代码样式   通过两项研究。其中一个风格   涉及审稿人的问题   是未受保护的广泛使用   全局变量。这被认为是   形式不好因为它增加了   程序状态的风险   变得不一致或那些价值观   将无意中修改或   覆盖。研究人员也   对这一事实表示了一些担忧   小数精度不是   整个过程中始终如一   代码。

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

答案 9 :(得分:11)

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

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

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

答案 10 :(得分:10)

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

一些专业人士:

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

一些缺点:

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

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

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

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

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

看待它的一个好方法是它们本身并不坏,但是它们会导致糟糕的设计,并且会以指数方式增加不良设计的影响。

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

答案 11 :(得分:9)

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

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

答案 12 :(得分:8)

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

答案 13 :(得分:7)

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

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

答案 14 :(得分:7)

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

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

答案 15 :(得分:7)

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

答案 16 :(得分:4)

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

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

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

答案 17 :(得分:3)

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

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

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

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

答案 18 :(得分:3)

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

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

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

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

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

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

答案 19 :(得分:3)

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

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

答案 20 :(得分:2)

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

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

例如: -

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

答案 21 :(得分:1)

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

使用全局而不是传递参数通常会更快但如果您正在编写多线程应用程序(现在经常这样做),它通常不能很好地工作(您可以使用线程静态但是性能增益是值得怀疑的。)

答案 22 :(得分:1)

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

答案 23 :(得分:1)

迟早你需要改变设置变量的方式或者访问变量时会发生什么,或者你只需​​要追踪变量的位置。

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

答案 24 :(得分:1)

全局配置方面表现良好。当我们希望 配置/更改 整个项目上产生全局影响时。

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

答案 25 :(得分:0)

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

答案 26 :(得分:-1)

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

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

此处有更多内容,https://software.intel.com/en-us/articles/use-intel-parallel-inspector-to-find-race-conditions-in-openmp-based-multithreaded-code