全局变量 静态全局变量
C ++:反对全局变量的案例 摘要本文探讨了使用全局变量的负面影响。 全局变量的使用是C ++架构师称之为的问题
污染全局名称空间 。 本文探讨了当全局命名空间被污染时会发生什么以及如何避免这种情况。尽管许多观点已经出现在各种出版物和开发人员论坛中,但本文表达的观点仅是作者的观点。
什么是全局变量?全局变量是在范围之外定义的变量。 也就是说,它不在任何括号内。 因此,该变量不受任何作用域所有,因此对于任何作用域都是可见的。
全局变量分为两种,具有外部链接的变量和具有内部链接的变量。 具有外部链接的全局变量可在多个源文件中共享,而具有内部链接的全局变量则不可。
为什么要使用全局变量?全局变量是避免许多函数共有的信息的函数参数的便捷方法。 函数可以使用全局变量,而不必将其作为参数传入。
不幸的是,许多不了解如何使用函数参数的程序员都滥用了此功能。 他们只是将变量设置为全局变量,然后不必传递或返回它。 该函数可以没有参数,因此可以完全避免参数传递问题。
为什么要反对全局变量?根据多年的经验,
没有绝对需要全局变量的实际情况。 也就是说,全局变量是一种便利,它以潜在灾难的形式付出了代价,这些灾难可能导致您的程序无法按设计运行,如果不是一开始就无法运行,或者将来可能由于更改而无法运行。 灾难1:局部变量隐藏全局变量在C ++中,本地范围始终是默认范围。 因此,如果您声明与全局变量同名的局部变量,则将使用局部变量代替全局变量。 注意,局部变量甚至不必与全局变量相同。 它只需要具有相同的名称。
int data = 10;
int main()
{
double data = 25.0;
cout << data << endl; //You see 25
}
为避免这种灾难,您必须小心确定所使用变量的范围。
也就是说,您必须使用范围解析运算符来指示要使用的变量。
int data = 10;
int main()
{
double data = 25.0;
cout << data << endl; //You see 25
cout << ::data << endl; //You see 10
}
无范围的范围解析运算符(::)引用未命名的命名空间 ,通常称为全局命名空间。
这里的弱点是,以这种方式使用范围解析运算符取决于不存在的可靠程序员。
避免此灾难的可靠方法是首先没有全局变量。
灾难2:变量名冲突声明全局变量使该变量可从多个源文件访问。 当您的代码与其他人的代码合并时,这为命名冲突打开了大门。 变量名的任何重复都可能在链接时导致错误。
C解决方案是遍历代码,或者更改一个有冲突的变量的名称,否则,将其中一个变量定义为静态。 静态全局变量只能从声明它的文件中访问。 如果需要从多个源文件访问静态变量,则将源文件合并在一起以形成一个巨型源文件。
C ++解决方案是使用一种称为名称空间的东西。 这是一种将范围名称添加到全局变量的名称,以使其与冲突的全局变量区分开的方法。 这称为
完全合格的名称 。
int data;
namespace MyStuff
{
int data;
}
命名空间外部的全局变量是:: data,而命名空间内部的全局变量的名称是MyStuff :: data。
至少这消除了一个名称冲突。
但是,由于C ++名称空间是开放式的,因此不能保证以后在其他一些源文件中:
namespace MyStuff
{
int data;
}
可以定义。
仅在这一次有两个MyStuff :: data变量时,名称冲突才再次浮出水面,而您又回到了C解决方案。
或者,创建第二个名称空间,并将其中一个变量放入其中。
但是,然后可以在其他一些源文件中的第二个名称空间中重新声明该变量,并且整个名称冲突一团糟。
这里的教训是,无论是否使用名称空间,都无法防止名称冲突。 最多可以说的是,通过使用名称空间,名称冲突的可能性有所减少,但并没有消除。
当然,您可以为全局变量使用匿名名称空间,但是只能在定义了匿名名称空间的文件中访问它。 这与C静态全局变量相同–您将返回一个巨大的源文件。
避免此灾难的可靠方法是首先没有全局变量。
灾难3:实施风险定义全局变量需要您还定义变量的名称。 这与具有公共数据变量相同。 结果,此变量的名称分散在整个用户代码中。 如果将来需要重新设计,那么您会发现进行更改的工作会更多,因为必须在所有这些位置更改或删除此变量名。
考虑由名为TheLimit的全局变量定义的应用程序限制的情况。 假设管理层决定在程序中需要时必须通过函数调用来获取TheLimit,因为TheLimit现在可以随时间变化。 编写函数很容易,但是进入用户代码并用函数调用替换所有出现的TheLimit并不容易:a)用户不允许您更改其代码,b)如果某些代码恰巧在用户中-如果将DLL写入全球,则所有现有的用户DLL必须替换为新的DLL。
最终结果是,您可能会被告知无法实现此功能。 全局变量有效地冻结了您的设计。 因此,除了破坏封装之外,全局变量还终止了对产品的改进。
全局变量的作用类似于硬编码的值,就像使用#define一样。 不建议在代码中使用硬编码的值。
最大化
变化的涟漪是解决这一问题的另一种方式。 通过使用全局变量,可以最大程度地减少更改设计的工作量。这里的解决方案是首先不要拥有全局变量,而要具有通过调用函数获得的限制。 您始终可以重写函数,并且如果您保留相同的函数原型,则用户只需要重新编译和重新链接即可。
灾难4:没有多线程可以从许多函数访问全局变量。 无法防止在单独线程上运行的函数访问全局变量。 当发生这种情况时,每个线程都可以在其他线程不知道该变量的情况下访问该变量,并且如果此线程更改了全局值,则其他线程也可能这样做,并且如果这些更新同时发生,则这些更新可能会相互冲突。时间并在全局变量中产生不正确的值。
这称为竞争状态,可能导致程序无法正确运行,甚至崩溃。 因此,任何全局变量都表明该程序不能是多线程的。 考虑到大多数现代软件都是多线程的,因此使用全局变量的代码不是现代的,因此不能与现代软件一起使用。
甚至不能保护关键部分中的变量,因为不能保证所有功能在使用这些变量时都将使用关键部分,因此无法正常工作。 那将再次取决于可靠的程序员。
这些全局变量可以是可共享的,静态的或在名称空间中定义。 没有什么不同的。 它们的存在将程序限制为单线程执行。
灾难5:最大化维护当全局变量中的值搞砸了时,所有可以访问该变量的函数都是可疑的。 必须对每个人进行检查以找出罪魁祸首。 仅有几个功能可能还不错,但是在实际应用中,可能有数千个功能需要检查。 现在解决问题并不是那么容易。
由于您可能无法访问所有代码,这使情况更加复杂。 可能是用户功能引起了问题,并且用户可能不希望您研究他们的代码。 他们只会将您的问题放在他们的优先级列表上,并将在某个时候解决它。 同时,只需要忍受由不良价值引起的问题。
用户编写的代码可以弥补该错误,这是一个非常糟糕的后果。 如果您更正了错误,则补偿代码现在可能是使该值变差的原因。 在某些情况下,链接程序计算出的地址偏移了4位。 用户编写了补偿代码,当供应商固定链接器时,由于补偿代码,用户链接现在产生了错误的地址。 结果:用户无法使用链接器的任何进一步升级。
灾难6:无法保证将使用全局具有全局变量并不意味着将使用该变量。 程序员可能不知道全局变量,而是创建其他变量。
回想一下,与全局变量同名的局部变量将隐藏全局变量。 此外,程序员可能只是不相信全局变量而拒绝使用它。
再一次,最好提供一个返回值的函数。
通过避免没有首先可以忽略的全局变量来避免这种灾难。
灾难7:全球人员扩大了内存足迹全局变量必须在main()开始执行之前创建并初始化,并且它们必须一直存在,直到main()完成执行。 也就是说,它们在程序的整个生命周期中都存在。
这些变量中的每一个都会使您的内存占用量更大,从而消耗不必要的更多机器资源。
更糟糕的是,全局变量可能是
头文件中定义的const变量。 这意味着每当头文件包含在源文件中时,将创建一组这些const全局变量。 如果您的程序有500个源文件,则这些const全局变量有500套。 通常,由于多个定义,这将导致链接器失败。 但是,为避免这种情况,默认情况下const全局变量是静态的。静态全局变量无法在定义它们的文件外部访问,因此避免了多重定义错误。 如果这些常量变量需要在许多源文件中使用,那么您
在头文件中声明变量,然后在源文件中定义它们。绝对不要在头文件中定义变量。
//Header file
extern const double PI;
然后,在源文件中,将变量定义为可共享的(即,使用外部链接而不是静态链接)。
//Source file
extern const double PI = 3.14159;
您可以通过首先不使用全局变量来最大程度地减少程序占用空间。
灾难8:全局变量的内存可能有限
某些环境限制了可用于全局变量的内存量。 如果您的程序超出了这些限制,则它将不会执行。 为了获得最大的可移植性,请勿假设可接受多少全局内存。
通过不使用全局内存来避免此灾难。
灾难9:无法保证创建顺序 。C ++语言规范指定了
单个源文件将按照它们在文件中出现的顺序进行创建和初始化。请记住,C ++可以进行构造函数调用以初始化变量,以便这些全局变量可以由其构造函数初始化。 这是C ++中唯一在main()启动之前执行代码的情况。
如果定义了两个全局变量A和B,并且A出现在B之前,那么可以保证A在B之前创建和初始化。因此,可以使用对参数A进行构造的B进行初始化。
当在一个源文件中定义A而在另一个源文件中定义B时,发生灾难。 不能保证链接程序将源文件加载到可执行文件的顺序。 这意味着可以在创建A之前初始化B,并且B的构造函数将使用尚不存在的参数。 崩溃是立即的。
流行的名字是
全局初始化惨败 。因此,切勿定义依赖于其他全局变量进行初始化的全局变量。
实际上,最好的方法是首先不要使用全局变量。
避免灾难解决方案1:
最好的做法是首先不要使用全局变量。
解决方案2:
接下来,您可以使用匿名名称空间来保存全局变量,并在该源文件中定义一个函数来访问它。 这迫使每个人都调用函数来访问变量。
现在,只要函数原型不变,就可以重新设计全局变量以读取文件而不会破坏用户代码。 如果是这样,您可以仅添加读取文件的新功能。 然后,新用户可以使用新功能,而旧用户可以继续使用硬编码变量。 当所有用户都使用新功能时,可以淘汰旧功能,并删除全局变量。
解决方案3:
使用单例。
单例是一个只能有一个实例的对象,该实例表示全局变量。 通常,仅在需要时才创建单例。 使用全局可用的Instance()函数访问单例,就像上面解决方案2中的函数一样。
在需要多个单例的地方,可以创建,初始化这些单例,然后将其与密钥名称一起存储在数据库中。 您可以通过按名称要求访问这些单例。
在C / C ++文章论坛的Singleton Design Pattern上有一篇文章。
更多信息Erich Fromm等人的《设计模式》,Addison-Wesley,1994年。
有效的C ++ Scott Meyers第三版Addison-Wesley 1998
版权所有2007 Buchmiller Technical Associates美国北华盛顿本德
翻译自: https://bytes.com/topic/c/insights/737451-case-against-global-variables
全局变量 静态全局变量