在C/C++中,全局变量和我的教授认为的一样糟糕吗?
全局变量的问题在于,由于每个函数都可以访问这些函数,因此越来越难以确定哪些函数实际读取和写入这些变量.
要了解应用程序的工作原理,您几乎必须考虑修改全局状态的每个函数.这可以做到,但随着应用程序的增长,它将变得越来越难以实际上不可能(或者至少完全浪费时间).
如果不依赖全局变量,则可以根据需要在不同函数之间传递状态.这样你就可以更好地理解每个函数的作用,因为你不需要考虑全局状态.
重要的是要记住总体目标:清晰度
存在"无全局变量"规则,因为大多数时候,全局变量使得代码的含义不太清楚.
但是,像许多规则一样,人们会记住规则,而不是规则的目的.
我已经看到程序似乎通过传递大量参数来简化代码的大小,以避免全局变量的恶.最后,使用全局变量可以使程序更清晰.通过无意识地坚持规则的话,原来的程序员已经失去了规则的意图.
所以,是的,全局变量通常很糟糕.但是如果你觉得最终,程序员的意图通过使用全局变量变得更加清晰,那就继续吧.但是,请记住,当您强迫某人访问第二段代码(全局变量)以了解第一部分的工作原理时,会自动产生清晰度.
我的教授曾经说过这样的话:如果正确使用它们,使用全局变量是可以的.我认为我没有擅长正确使用它们,所以我很少使用它们.
只有在没有其他选择时才应使用全局变量.是的,那包括单身人士.90%的时候,全局变量被引入以节省传递参数的成本.然后发生多线程/单元测试/维护编码,你就遇到了问题.
所以是的,在90%的情况下,全局变量都很糟糕.您在大学期间不太可能看到例外情况.我能想到的一个例外是处理固有的全局对象,例如中断表.像DB连接这样的东西似乎是全球性的,但事实并非如此.
全局变量为程序员创建的问题是它扩展了使用全局变量的各个组件之间的组件间耦合表面.这意味着随着使用全局变量的组件数量的增加,交互的复杂性也会增加.这种增加的耦合通常使得缺陷在更改时更容易注入系统,并且还使得缺陷更难以诊断和纠正.这种增加耦合还可以在进行更改时减少可用选项的数量,并且可以增加更改所需的工作量,因为通常必须跟踪也使用全局变量的各个模块以确定更改的后果.
封装的目的,与使用全局变量基本相反,是为了减少耦合,以便更容易,更安全,更容易测试,理解和更改源代码.当不使用全局变量时,使用单元测试要容易得多.
例如,如果您有一个简单的全局整数变量用作枚举指示符,各种组件用作状态机,然后通过为新组件添加新状态进行更改,则必须跟踪所有其他组件组件,以确保更改不会影响他们.一个可能的问题的一个例子是,如果在各个地方使用了一个switch
用于测试枚举全局变量值的case
语句,并且每个当前值都使用了switch
语句,而且有些语句没有default
处理的情况对于全局而言,一个意外的值突然间,就应用程序而言,您有未定义的行为.
另一方面,共享数据区域的使用可用于包含在整个应用程序中引用的一组全局参数.这种方法通常用于具有小内存占用的嵌入式应用程序.
在这些类型的应用程序中使用全局变量时,通常将写入数据区域的责任分配给单个组件,所有其他组件将该区域视为const
并从中读取,而不是写入该区域.采用这种方法可以限制可能出现的问题.
全局变量需要解决的一些问题
当修改诸如结构之类的全局变量的源时,必须重新编译使用它的所有内容,以便使用该变量的所有内容都知道其真实大小和内存模板.
如果多个组件可以修改全局变量,则可能会遇到全局变量中存在不一致数据的问题.使用多线程应用程序,您可能需要添加某种锁定或关键区域以提供一种方式,以便一次只有一个线程可以修改全局变量,并且当线程正在修改变量时,所有更改都是完整的并且在其他线程可以查询变量或修改它之前提交.
调试使用全局变量的多线程应用程序可能会更加困难.您可能遇到可能产生难以复制的缺陷的竞争条件.有几个组件通过全局变量进行通信,特别是在多线程应用程序中,能够知道哪个组件正在改变变量的时间和方式,这很难理解.
名称冲突可能是使用全局变量的问题.与全局变量同名的局部变量可以隐藏全局变量.使用C编程语言时,您还会遇到命名约定问题.解决方法是将系统划分为子系统,其中特定子系统的全局变量都以相同的前三个字母开头(请参阅目标C中的解析名称空间碰撞).C++提供了命名空间,使用C可以解决这个问题,方法是创建一个全局可见的结构,其成员是各种数据项,指向数据和函数的指针,这些数据和函数在文件中作为静态提供,因此只有文件可见性才能通过全局可见的结构.
在某些情况下,会更改原始应用程序意图,以便修改为单个线程提供状态的全局变量,以允许运行多个重复的线程.一个示例是为单个用户设计的简单应用程序,使用状态的全局变量,然后从管理层发出请求以添加REST接口以允许远程应用程序充当虚拟用户.因此,现在您不得不复制全局变量及其状态信息,以便单个用户以及来自远程应用程序的每个虚拟用户都拥有自己独特的全局变量集.
使用C++ namespace
和C语言struct
技术
对于C++编程语言,该namespace
指令有助于减少名称冲突的可能性.namespace
以及class
各种访问关键字(private
,protected
和public
)提供了封装变量所需的大多数工具.但是,C编程语言不提供此指令.这个stackoverflow发布,C中的Namespaces,为C提供了一些技术.
一种有用的技术是将单个内存驻留数据区域定义为struct
具有全局可见性的内容,并且在此内部struct
是指向所公开的各种全局变量和函数的指针.使用static
关键字为全局变量的实际定义赋予文件范围.如果您随后使用const
关键字指示哪些是只读的,则编译器可以帮助您强制执行只读访问.
使用该struct
技术还可以封装全局,使其成为一种恰好是全局的包或组件.通过拥有此类组件,可以更轻松地管理使用全局影响全局和功能的更改.
然而,尽管namespace
或者该struct
技术可以帮助管理名称冲突,但仍然存在使用全局变量尤其在现代多线程应用程序中引入的组件间耦合的潜在问题.
是的,但是在您停止使用全局变量的代码并开始编写使用全局变量的代码的其他代码之前,您不会产生全局变量的代价.但成本仍然存在.
换句话说,这是一个长期的间接成本,因此大多数人认为它并不坏.
全局变量和你制作它们一样糟糕,同样重要.
如果要创建完全封装的程序,则可以使用全局变量.使用全局变量是一种"罪恶",但编程犯罪是非常哲学的.
如果您查看L.in.oleum,您将看到一种语言,其变量仅为全局变量.它是不可扩展的,因为库除了使用全局变量外别无选择.
也就是说,如果你有选择,并且可以忽略程序员的理念,那么全局变量并不是那么糟糕.
如果你正确使用Gotos,它们都不是.
最大的"坏"问题是,如果你使用它们错了,人们尖叫,火星着陆器崩溃,世界爆炸......或类似的东西.
我会用另一个问题回答这个问题:你是否使用singeltons/singeltons不好?
因为(几乎所有)singelton的使用是一个美化的全局变量.
如果您的代码可能会在最高法院审判期间进行深入审查,那么您需要确保避免全局变量.
看到这篇文章: Buggy呼气测醉器代码反映了源审查的重要性
两项研究都确定了代码风格存在一些问题.涉及审稿人的风格问题之一是广泛使用未受保护的全局变量.这被认为是不好的形式,因为它增加了程序状态变得不一致或者无意中修改或覆盖值的风险.研究人员还对在整个代码中不能保持小数精度的事实表示了一些担忧.
伙计,我打赌那些开发者希望他们没有使用全局变量!
问题不在于它们是坏的,更多的是它们是危险的.他们有自己的优点和缺点,有些情况下,他们或者是实现特定任务的最有效或唯一的方式.但是,即使您采取措施始终正确使用它们,它们也很容易被滥用.
一些优点:
可以从任何功能访问.
可以从多个线程访问.
在程序结束之前永远不会超出范围.
一些缺点:
可以从任何函数访问,无需作为参数显式拖入和/或记录.
不是线程安全的.
污染全局命名空间并可能导致名称冲突,除非采取措施来防止这种情况发生.
请注意,如果你愿意的话,我列出的前两个专业人员和前两个缺点是完全相同的,只是用不同的措辞.这是因为全局变量的功能确实很有用,但是使它们有用的功能是所有问题的根源.
一些问题的一些潜在解决方案:
考虑它们是否真的是解决问题的最佳或最有效的解决方案.如果有任何更好的解决方案,使用来代替.
把它们放在一个命名空间[C++]或单结构[C,C++]一个独特的名字(一个很好的例子是Globals
或GlobalVars
),或使用全局变量(如标准化命名约定global_[name]
或g_module_varNameStyle
(由underscore_d在评论中提到)).这将记录它们的使用(您可以通过搜索命名空间/结构名称找到使用全局变量的代码),并最小化对全局命名空间的影响.
对于访问全局变量的任何函数,显式记录它读取的变量和写入的变量.这将使故障排除更容易.
将它们放在自己的源文件中并extern
在相关的头文件中声明它们,因此它们的使用可以限于需要访问它们的编译单元.如果您的代码依赖于许多全局变量,但每个编译单元只需要访问其中的少数几个,您可以考虑将它们分类为多个源文件,因此更容易限制每个文件对全局变量的访问.
设置一种锁定和解锁它们的机制,和/或设计代码,以便尽可能少的函数需要实际修改全局变量.阅读它们比编写它们要安全得多,尽管线程竞争可能仍然会导致多线程程序出现问题.
基本上,最小化对它们的访问,并最大化名称唯一性.您希望避免名称冲突,并尽可能减少可能修改任何给定变量的函数.
它们的好坏取决于你如何使用它们.大多数人倾向于使用它们,因此对他们一般保持警惕.如果使用得当,它们可能是一个重要的福音; 然而,如果使用不当,他们可以而且会回来咬你什么时候以及如何最不期望它.
看待它的一个好方法是它们本身并不坏,但它们能够实现糟糕的设计,并且能够以指数方式增加不良设计的效果.
即使您不打算使用它们,最好还是知道如何安全地使用它们并选择不使用它们,因为您不知道如何安全地使用它们.如果您发现自己处于需要维护依赖于全局变量的预先存在的代码的情况下,如果您不知道如何正确使用它们,则可能会遇到困难.
正如有人在另一个话题中说的那样(我在解读)"这样的规则不应该被打破,直到你完全理解这样做的后果."
有时全局变量是必要的,或者至少非常有用(例如,使用系统定义的回调).另一方面,由于你被告知的所有原因,它们也非常危险.
编程的许多方面应该留给专家.有时你需要一把非常锋利的刀.但是在你做好准备之前,你不能使用它......
全局变量通常很糟糕,特别是如果其他人正在处理相同的代码并且不想花20分钟搜索变量被引用的所有位置.添加修改变量的线程会带来全新的麻烦.
在单个翻译单元中使用的匿名命名空间中的全局常量在专业应用程序和库中很好并且无处不在.但是如果数据是可变的,和/或它必须在多个TU之间共享,你可能想要封装它 - 如果不是为了设计的缘故,那么为了任何人调试或使用你的代码.
使用全局变量有点像地毯下的污垢.这是一个快速解决方案,在短期内比使用除尘盘或真空吸尘器更容易清理.但是,如果你以后最后搬到地毯上,那么你下面就会有一个惊喜.
全局变量很糟糕,如果它们允许您操作应该只在本地修改的程序的各个方面.在OOP中,全局变量通常与封装思想相冲突.
我认为你的教授在它开始之前就试图阻止一个坏习惯.
全局变量有它们的位置,就像许多人说知道何时何地使用它们可能很复杂.因此,我认为而不是深入研究你的教授决定禁止的全局变量的原因,方式,时间和地点的细节.谁知道,他将来可能会禁止他们.
绝对不.滥用他们虽然......这很糟糕.
为了这个原因而无意识地移除它们只是......没有头脑.除非你知道优点和缺点,否则最好先明确并按照你所教导/学习的方式去做,但全局变量没有任何隐含的错误.当你了解利弊时,最好做出自己的决定.