在C语言中,什么时候使用全局变量是可以接受的?

62

显然,人们对于这个问题有许多不同的意见,从“永远不要使用全局变量!即使只是一个宏也要封装起来!”到“没有什么大不了的——在需要时使用它们比不使用更方便。

因此,需要具体而明确的原因(最好附带示例):

  • 为什么全局变量是危险的
  • 在何时应该使用全局变量代替其他选项
  • 那些想要不适当地使用全局变量的人有哪些替代方法

虽然这是主观的,但我会选择一个答案(对我来说最能代表每个开发者与全局变量之间的爱恨关系),而社区将投票选出他们认为最好的答案。

我认为新手有这样的参考资料很重要,但如果存在与您的答案实质上相似的另一个答案,请不要混淆它——可以添加评论或编辑其他人的答案。

16个回答

60

变量应该尽可能具有较小的作用域。这背后的原因是,每当您增加作用域时,就会有更多的代码可能修改变量,从而在解决方案中引入更多的复杂性。

因此,很明显,如果设计和实现自然允许,避免使用全局变量是首选。由于这个原因,除非真正需要,我不喜欢使用全局变量。

我也不能同意“永远不”的说法。像任何其他概念一样,全局变量只有在需要时才应该使用。我宁愿使用全局变量,而不是使用一些人工构造(如传递指针),这只会掩盖真正的意图。

一些使用全局变量的好例子包括单例模式实现或嵌入式系统中的寄存器访问。

关于如何实际检测过度使用全局变量:检查、检查、检查。每当我看到一个全局变量时,我都必须问自己:这真的需要在全局范围内吗?


此外,在生产代码中,当您的项目必须是可重入和/或线程安全时,这会让人非常沮丧。如果有人在未来继承了您的 20,000 行项目,想象一下他们需要重构整个项目的工作量,到最后可能更有效地从头开始重新编写它。 - Cloud

21

唯一让全局变量工作的方法就是给它们命名以确保它们是唯一的。

通常这个名字有一个前缀,与一些“模块”或函数集合相关联,这些函数特别关注或具有意义的全局变量。

这意味着这个变量“属于”这些函数——它们的一部分。事实上,全局变量通常可以与其他函数一起“包装”在同一 .h 文件中,使用相同的名称前缀。

奖励。

这样做后,它不再是真正的全局变量了。它现在是一些相关函数的模块的一部分。

这始终可以做到。通过一点思考,每个以前的全局变量都可以被分配给一些函数集合,分配给特定的 .h 文件,并使用函数隔离,允许您更改变量而不会破坏任何东西。

与其说“永远不要使用全局变量”,不如说“将全局变量的职责分配给一些最合适的模块。”


3
+1 - 好极了。这些是在我们意识到全局变量是如此"邪恶"之前,人们在旧时代做的事情。令人震惊的是,那些旧程序仍然能够正常工作,并且通常仍然易于维护。 :) - Torlack

12
考虑这个公案:“如果范围足够狭窄,则一切都是全局的”。
在这个时代,仍然有可能需要编写一个非常快速的实用程序来执行一次性任务。
在这种情况下,为变量创建安全访问所需的能量大于在这样一个小实用程序中调试问题所节省的能量。
这是我能想到的唯一情况,在这种情况下使用全局变量是明智的,而且相对较少。 有用,新颖且如此小的程序,以至于可以完全保存在脑部短期记忆中,正在变得越来越少,但它们仍然存在。
事实上,我可以大胆地声称,如果程序不是这么小,那么全局变量应该是非法的。
- 如果变量永远不会改变,则它是一个常量,而不是变量。 - 如果变量需要通用访问,则应存在两个子程序来获取和设置它们,并且它们应该同步。 - 如果程序开始很小,而可能稍后变得更大,则应像今天的程序一样编写代码,并消除全局变量。 并非所有程序都会增长!(尽管当然,这假设程序员有时愿意扔掉代码。)

11

C语言中的全局变量在多个方法中需要使用同一个变量时可以提高代码的可读性(而不是将变量传递给每个方法)。但是,它们很危险,因为所有位置都有修改该变量的能力,这可能会导致难以追踪错误。如果必须使用全局变量,请始终确保只有一个方法直接修改它,并让所有其他调用者使用该方法。这将使跟踪与该变量更改相关的问题更加容易。


10

当你不用担心线程安全问题时:可以在适合的地方使用全局变量,换言之,在适合表达为全局状态的任何地方都可以使用。

当你的代码可能会被多线程调用时:尽量避免使用全局变量。将抽象的全局变量转换成工作队列或其他线程安全结构,或者如果绝对必要,将其包装在锁中。但请记住,这些很可能是程序中的瓶颈。


5

我曾经一直坚持“从不使用全局变量”的立场,直到我开始在国防工业领域工作。有一些行业标准要求软件必须使用全局变量而非动态内存(在C语言中是malloc)。因此,我需要重新考虑我对于某些项目的动态内存分配方式。如果你能够使用合适的信号量、线程等来保护“全局”内存,那么这种内存管理方法是可以被接受的。


4
有趣。国防工业让我坚定地站在“永远不会”的立场上。 - Ben Collins
1
我曾经在一个嵌入式系统上工作过,那里没有动态内存分配功能。在这种情况下,我们需要通过链接器在RAM空间中分配“全局”变量(用于图像数据),并且必须控制多个线程之间的访问。这是一种不同的思想方式,但它确实可行。 - Adam Hawes

4
代码复杂性并不是唯一需要优化的方面。对于许多应用程序来说,性能优化更为重要。但更重要的是,在许多情况下使用全局变量可以极大地减少代码复杂性。有许多可能是专业的情况,在这些情况下,全局变量不仅是可接受的解决方案,而且是首选方案。我最喜欢的专业示例是它们用于在实时线程中运行的音频回调函数与应用程序的主线程之间提供通信。
如果任何变量暴露于超过一个线程的更改,则无论作用域如何,都可能成为潜在风险,因此认为全局变量在多线程应用程序中是一种负担是误导人的。
请谨慎使用全局变量。尽可能使用数据结构来组织和隔离全局命名空间的使用。
变量作用域为程序员提供了非常有用的保护措施,但也可能会带来一些代价。今晚我写关于全局变量的原因是,我是一名经验丰富的Objective-C程序员,经常对面向对象编程所放置的数据访问障碍感到沮丧。我认为反对全局变量的狂热主要来自那些年轻的、理论化的程序员,他们主要经验是在孤立的面向对象API中,而没有深入实际的系统级API和它们在应用程序开发中的相互作用。但我必须承认,当供应商松散使用命名空间时,我会感到沮丧。例如,几个Linux发行版已经全局预定义了“PI”和“TWOPI”,这破坏了我的大量个人代码。

3
  • 什么时候不使用:全局变量很危险,因为唯一的方法是跟踪声明它们的.c文件中的整个源代码(或所有.c文件,如果它也是extern)。如果您的代码出现故障,您必须搜索整个源文件以查看哪些函数更改了它,以及何时更改。当它出错时,调试是一场噩梦。我们经常认为本地变量优雅地超出作用域的概念背后的创意是理所当然的 - 很容易追踪
  • 什么时候使用:应该在其利用不被过度掩盖且使用本地变量的成本过于复杂以至于会损害可读性的情况下使用全局变量。我的意思是需要将额外的参数添加到函数参数和返回值中并传递指针等其他内容。三个经典例子:当我使用pop和push堆栈时 - 这是在函数之间共享的。当然,我可以使用本地变量,但是我必须传递指针作为额外的参数。第二个经典示例可以在K&R的“C编程语言”中找到,他们定义了一个getch()ungetch()函数,这些函数共享全局字符缓冲区数组。再次强调,我们不需要使其全局,但是当使用缓冲区的使用非常困难时,增加的复杂性是否值得?第三个示例是您将在Arduino爱好者的嵌入式空间中找到的某些功能。主loop函数中的许多函数都共享millis()函数,该函数是调用函数时的瞬时时间。因为时钟速度不是无限的,millis()将在单个循环中不同。为了使它恒定,请在每个循环之前拍摄时间快照并将其保存在全局变量中。时间快照现在将与许多函数访问时相同。
  • 替代方案:没有太多。尽可能坚持本地作用域,特别是在项目开始时,而不是反之。随着项目的发展,如果您觉得可以使用全局变量降低复杂性,则可以这样做,但仅当满足第二点要求时。记住,使用本地作用域并具有更复杂的代码比不负责任地使用全局变量更少的罪恶感。

2

你需要考虑全局变量将在什么上下文中使用。未来是否希望这段代码复制。

例如,如果您正在使用系统内的套接字访问资源。未来是否希望访问多个这些资源,如果是,则首先避免使用全局变量,以免需要进行重大重构。


2
全局变量应该在多个函数需要访问数据或写入对象时使用。例如,如果您需要将数据或引用传递给多个函数,如单个日志文件、连接池或需要跨应用程序访问的硬件引用。这可以防止非常长的函数声明和大量重复数据的分配。
通常情况下,除非绝对必要,否则不应使用全局变量,因为全局变量仅在明确告知或程序结束时才会被清除。如果您正在运行多线程应用程序,则多个函数可以同时写入变量。如果出现错误,跟踪错误可能会更加困难,因为您不知道哪个函数正在更改变量。除非使用显式地为全局变量命名的命名约定,否则还会遇到命名冲突的问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接