全球变量是否不好?

233 c c++ global-variables

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

Bri*_*sen 241

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

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

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

  • 将"应用程序"替换为"类",将"对象状态"替换为"全局状态",并且在类中不使用成员变量(也称为字段)时,可以使用完全相同的参数.真正的答案是在适当的时候使用它们. (16认同)
  • 这个答案非常好.将此与"最小化变量范围"答案相结合http://stackoverflow.com/questions/357187/global-variables-when-are-they-acceptable/357361#357361 (9认同)
  • @noɥʇʎԀʎzɐɹƆ你走了!https://i.imgur.com/RwRgJLZ.jpg (3认同)
  • 几个(也许是愚蠢的)问题:1)如果你想知道哪些函数读写这些变量,你不能只在编辑器中使用"find"函数来发现这些变量中的值被修改的情况吗?2)"那可以做,......完全浪费时间." 你能给我举个例子吗?3)"如果你不依赖于全局变量,......你不需要考虑全局状态." 我不明白这是多么有利.也许这方面的一个例子对我有用. (2认同)
  • @bobobobo断开链接,我们可以获得一个10k +用户的屏幕截图吗? (2认同)

Tom*_*est 79

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

存在"无全局变量"规则,因为大多数时候,全局变量使得代码的含义不太清楚.

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

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

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

  • 在适当的情况下建议全局变量是一种更清晰,更高性能的代码."传递"需要不断的堆栈动态内存分配,这对于应该是全局的东西来说是愚蠢的,例如传入套接字数据的全局缓冲区.例如,如果你有一个读取Winsock recv()的函数,为什么要在每次调用中不断创建和释放这个缓冲区?使缓冲区成为全局缓冲区.多线程无论如何都不会读它. (15认同)
  • 建议使用全局变量而不是传递变量是使代码不可重用,并且对多线程不安全的一种方法 (6认同)
  • 如果某人传递了约100个变量,那么他们还没有了解对象是什么。在最坏的情况下,使用对该对象的引用会绕过指针。我想说的规则不仅是清晰度,而且还有可测试性-使用非全局性的方法会使事情更容易测试。 (2认同)
  • “如果某人传递了约100个变量,那么他们就不会了解对象是什么。” 同意,但并非所有世界都是面向对象的。我个人将代码大小加倍的示例是大约1986年的大型Fortran程序。作为刚从大学毕业的新员工,我通过为每个调用添加大约30个参数来“改进”它,从而消除了所有的全局变量。然后,当我意识到自己所做的事情时,就束手无策了。 (2认同)
  • 谢谢你的回答。我看到很多“书呆子”称这个或那个“邪恶”(我讨厌这个词),却没有真正理解其背后的原因。没有任何一件事适用于所有情况。所以每当称某件事为“邪恶”时,必须强调:“在大多数情况下”。 (2认同)

bar*_*ron 60

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

  • 如此真实.它们就像是搞砸了,如果你不知道何时使用它们,那就永远不会. (24认同)
  • 在我现在的公司,他们使用`static`全局变量,语言是C.由于被限制在相对较小的翻译单元,它们开始类似于C++对象的类变量. (5认同)

小智 36

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

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

  • +1"像DB连接这样的东西*似乎*是全球性的,但不是." (10认同)
  • 我在大学时期看到的一个例外是图形回叫功能.在XWindows中,鼠标回调没有void*data参数,这些参数允许你传递任意程序状态的块......(不是说它最终比全局更好......) (2认同)
  • 中断表不是全局的,每个处理器都有一个中断表 - 但每个处理器也有一个程序实例,因此它“取消”。 (2认同)
  • 谁能告诉我为什么数据库连接不是全局的(以及什么是一个好的替代方案)?我一直认为连接是全局变量可以接受的极少数情况之一。 (2认同)

Ric*_*ers 31

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

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

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

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

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

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

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

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

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

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

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

使用C++ namespace和C语言struct技术

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

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

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

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


MSN*_*MSN 19

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

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


use*_*650 19

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

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

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

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

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

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

  • 淡化使用全局变量给困惑的学生的问题并不是一个好主意IMO. (15认同)
  • 富是对的.这个答案没有说明什么是/不坏(或者如何安全地使用全局变量),只是说"它们没有那么糟糕.因此,它只是淡化了问题. (7认同)
  • 我不同意全局变量只是"你做的那么糟糕".我认为其中一个主要问题,特别是在我们大多数人生活,工作和编程的多开发人员互联世界中,全局变量让某些人有机会让你的代码变坏. (4认同)
  • 设计哲学不客观.至少没有.仅仅因为大多数程序员不喜欢某些东西,并不意味着人们不应该考虑那些东西.在没有世界末日的情况下,很容易一般性地使用全局变量.让他做,奋斗(知道他会),并学习如何. (3认同)

Gav*_*ler 18

我会用另一个问题回答这个问题:你是否使用singeltons/singeltons不好?

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

  • 我准备发表一个明智的评论说,"如果你称他们为全球而不是单身人士,他们就是坏人",但是你打败了我. (11认同)
  • 对于记录,单例是一个全局变量,具有美化的设计模式(tm)(lol)名称,使其合理.出于所有相同的原因,它同样糟糕. (10认同)

Cas*_*sey 18

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

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

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

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

  • 那是我一段时间以来最好的笑声.一个真正的例子,说明为什么闭源开发利润是坏的,并且全球变量的一个很好的例子出了问题! (5认同)

Jus*_*ica 13

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

一些优点:

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

一些缺点:

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

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

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

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

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

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


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

  • 实用主义+1。单例通常只是添加样板来使实例和重构为成员,最终得到......全局变量,只是伪装成不同的名称。除了避免纯粹技术问题上的全局罪恶之外,为什么要费心呢?命名空间作为屏障是很好的,但我发现一个简单的“g_module_varNameStyle”完全清晰。需要明确的是,如果我可以轻松避免它,我就不会使用全局变量 - 关键词_轻松_,因为自从我不再相信它们必须被避免 - 或者更确切地说 **混淆** - 不惜一切代价,我有一个更好的时间,我的代码(震惊!)更加整洁 (2认同)

Bri*_*tow 11

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

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

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


Mic*_*hel 9

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

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


gno*_*ice 8

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

  • 没有上下文的懒惰隐喻!=答案 (2认同)
  • @underscore_d:我不同意。这是一个讨论问题,尽管它没有被标记为这样(可能是由于它的年龄),所以像这样的答案是完全有效的,并且它提出了一个确实解决OP问题的观点。 (2认同)

Leo*_*das 7

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


小智 7

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

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


jhe*_*iko 7

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

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

  • -1有许多理由要警告全局变量:对我来说最大的一个原因是隐藏的依赖关系和全局使用任何可预测的方式测试代码都非常困难.除非你不重视以自动方式测试你的代码的能力,否则我会建议全局变量只会给你带来痛苦.此外,在结构良好的计划中,总有*替代品. (3认同)
  • 你所说的是一种大规模的过度概括,仔细使用全局状态并不会阻止自动化测试 - 事实上,几乎所有应用程序都具有全局状态,无论它是包装为封装良好的对象的动态分配实例还是完全暴露的静态数据从概念上讲没有区别,仍然存在依赖关系 - 这只是关于它们的编码方式。 (2认同)

And*_*urg 6

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

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

整个线程所表达的隐含希望是,当不使用全局变量时,将程序从单线程转换为多线程会更容易是幼稚的。是的,全局变量可以更轻松地用脚射击自己,但是有很多方法可以射击自己。

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


Dar*_*ron 5

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

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

当你更有经验时,当他们没问题时,学习就会更容易。


old*_*mer 5

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

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

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

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

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

与此同时,然后就按照那个说话最大声或拿着最大棍子的人说的去做(直到你是那个喊得最大声并拿着最大棍子的人)。

  • 这只是“没有人因为购买 IBM 被解雇”的另一种说法吗? (4认同)

小智 5

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

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